Archive for the 'ASP.NET 3.5 Extensions' Category

08
Feb

ASP.NET AJAX History Part 3: Server-Side + Client-Side

Having the option to manage AJAX history from both the server-side and client-side lends itself really nicely to being able to accomodate a large number of situations. If you’re looking to add some history functionality to an existing web page that was developed using server controls, it is pretty painless to drop in a ScriptManager and UpdatePanel and go off on your merry way. If you’re creating a web page from scratch and you’re looking to get as streamlined as possible, then using the Sys.Application class in the ASP.NET AJAX library can make all of your history dreams come true. Unfortunately it isn’t always that cut-and-dry though, and sometimes a single application, or even a single page, might have multiple peices of functionality in it that have different needs. The question that arises is this: can both history approaches be easily leveraged within the same application or page and functionality properly? The answer to that can be derived from a 70’s English rock band:

yeslogo1.jpg

Not only can the two approaches co-exist, in many ways they feel like they were meant to live together. Remember how it was mentioned that in the state URL all server-side history points are appended after a double ampersand whereas all client-side history points preceed the ampersands? That fact alone makes the intermingling of the two to successfully work.

What we’re going to do is take both versions of the quarterback selector app (client and server editions) and mash them together onto the same page. We’re not even going to touch a single character of code. I’ve added some extra text and HTML to make the overall demo look a little more compelling. If I run the app, this is what we’ll see:

lit-2-7-2008-34116-pm.png

This should obviously look pretty familiar to you by now. The client-side portion is 100% JavaScript code, while the server-side porition is leaning on the ScriptManager and UpdatePanel server controls. What would you expect to happen if we began selecting quarterbacks from both lists at the same time and then trying to use the back and forward buttons? If you are the type of person who doesn’t like to take risks I would recommend you stop reading here. If you are completely insane and love living life on the edge, the continue reading because extreme awesomeness is about the ensue.

Let’s start by selecting a quarterback from the client-side list to make sure it still works. Let’s go with David Garrard:

lit-2-7-2008-34157-pm.png

So far so good. Let’s now try to select a quarterback from the server-side list to ensure that implementation still works. Let’s go with Brett Farve:

lit-2-7-2008-34322-pm.png

Alright so both implementations seem to work beautifully, but that doesn’t prove that our history is working. I notice that my browser’s back button is active which makes me wonder what will happen if I click it’s pretty blue little face. What do you think?

lit-2-7-2008-34522-pm.png

Is that cool or what? The back button undid the last action, which in this case was the server history point, but didn’t affect the client history point. You might have originally thought that hitting the back button would have removed both selections, because it would be backing out of both the client and server side history “buckets”. So how does this work? I’m going to hit the browser forward button to redo our server-side selection and then examine the current URL:

http://localhost:49652/#FavoriteQuarterback=David%20Garrard%20&&FavoriteQuarterback=Brett+Farve

Doesn’t get much clearer than that. You can easily see the two history points seperated by client/server. The point before the double ampersand is the client history point and the one following the double ampersand is the server history point. What that means is that I could bookmark my page right now and when I return it will recreate both selections. Because the URL contains state information for both the client and the server approaches, the ScriptManager’s Navigate event will be fired as well as the Sys.Application class’s navigate event. I think that is just too cool. I could sit here and click on both lists all day long, repeatedely hit the back button, and then watch it undo every one of my selections from each list individually. I would recommend going crazy with it, it really drives the point home just how awesome this is.

At the conclusion of this three-part series on ASP.NET AJAX History, hopefully your minds are running wild with ways you can begin implementing it into your applications. While this may not be the most groundbreaking feature to be released this year, it certainly shouldn’t be thrown to the wayside. It’s a very powerful little feature that can really increase the useability of a web page for a user with little effort from the developer.

07
Feb

ASP.NET AJAX History Part 2: Client-Side

Now that we’ve gone through the basics of the ASP.NET AJAX History feature and seen how it works server-side, we’re better equipped to see how to leverage this functionality from a client-side perspective. The last article highlighted the easability of adding AJAX History in circumstances where using the UpdatePanel makes sense, but in pure AJAX applications, that kind of logic needs to be achieved via JavaScript, which the previous article didn’t satisfy. Luckily, all the behavior that you saw in the last post can be fully replicated using the ASP.NET AJAX libraries. The second part of this series will show how to develop the exact same application as we did in part 1, but doing so completely on the client-side. We’re going to be adding the new functionality to the same page as the last article, and as such, reusing the existing code (ScriptManager, CSS, etc.)

We begin by creating the same list of quarterbacks that we saw in part 1, but in this case we’re not going to use a server control, but rather straight HTML:

lit-2-7-2008-93548-am.png

If I ran this right now I’d see the following:

lit-2-6-2008-41754-pm.png

Notice that we don’t have to deal with LinkButtons as we did in our last article, but rather we just have a nice and simple unordered list. Now we need to enable the ability for users to select their favorite quarterback. Since we can’t lean on the BulletedList control’s Click event, we’ll have to write some JavaScript code. We’ll add a new JavaScript file to our project, and reference it using our ScriptManager:

ajaxupdate.png

Keep in mind that the EnableHistory property also applies to client-side history, so make sure you have that set to true regardless which approach you take (client/server). The EnableSecureHistoryState properperty however does not apply to client-side history, so make sure you only store trivial data in history when using the client-side approach. Let’s add some initialization code to make the list selection work. We’ll add the ubiquitous pageLoad method to our new script file:

var quarterbackItems;              
 
function pageLoad() 
{ 
    quarterbackItems = $get("quarterbacks-list").getElementsByTagName("li");              
 
    for (var i = 0; i < quarterbackItems.length; i++) 
    { 
        $addHandler(quarterbackItems[i], "click", quarterbackClicked); 
    } 
}

All we’re doing here is grabbing the list items from our quarterbacks list and wiring up their individual click events. The logic for the quarterbackClick method looks like so:

function quarterbackClicked(e) 
{ 
    var name = e.target.innerHTML; 
    selectQuarterback(name); 
}

We’re just grabbing the inner content (the quarterback’s name) of the item that fired this event, which is the list item, and then calling a method called selectQuarterback, passing it the selected quarterback’s name. The code for the selectQuarterback method looks like so:

function selectQuarterback(name) 
{ 
    for (var i = 0; i < quarterbackItems.length; i++) 
    { 
        var element = quarterbackItems[i];             
 
        if (element.innerHTML == name) 
        { 
            Sys.UI.DomElement.addCssClass(element, "selected"); 
        } 
        else 
        { 
            Sys.UI.DomElement.removeCssClass(element, "selected"); 
        } 
    } 
}

Here we’re simply looping through every list item in the quarterbacks list and marking the proper item as being selected and ensuring that any previously selected items are unselected. You should notice that the logic for this exercise is practically 1:1 from the previous article, we’ve just ported it to use JavaScript and the ASP.NET AJAX libraries. We’ll reuse the same stylesheet from the last post since we’re leveraging the same HTML output and class names. If I were to run this right now and click on Tom Brady, I’d see the following:

lit-2-6-2008-42534-pm.png

Since we’re reusing the same stylesheet from the last article, this looks exactly like before, only without the LinkButtons. If I select Carson Palmer I’d see the following:

lit-2-6-2008-42644-pm.png

So it’s clear that our app functions perfectly now. Unfortunately, regardless how many times I select a different quarterback I still don’t get any love from my back and forward buttons:

lit-2-6-2008-45536-pm.png

Up to this point we’ve just created a neat little JavaScript app. The browser has no clue what we consider state in this scenario, and we certainly haven’t made any effort to tell it. It would make the most sense to add our custom history point logic in the quarterbackClicked event handler. The way we did this in our last article was by calling the AddHistoryPoint method on the ScriptManager control. Well instead of using the ScriptManager, we’re going to use the Sys.Application class in the ASP.NET AJAX libraries. It also has a method called addHistoryPoint that serves the exact same purpose as it’s server-side counterpart. Let’s modify the quarterbaclkClicked event handler to include the call to addHistoryPoint:

function quarterbackClicked(e) 
{ 
    var name = e.target.innerHTML; 
    Sys.Application.addHistoryPoint({FavoriteQuarterback: name}, name); 
    selectQuarterback(name); 
}

Notice that instead of passing it two parameters for the key/value pair that represents our state, we’re passing it a JSON object. “FavoriteQuarterback” is our history key, and the value is the name of the selected quarterback. The second parameter represents the browser title associated to this history point.

If I run the app, and then select a list item, I see the following:

lit-2-7-2008-95745-am.png

Great, so we’ve got our list selection and history points working, but now we need to tell the app how to recreate the user’s state upon request. In the last article we used the ScriptManager’s Navigate event, and as you might have already guessed, in this article we’re going to use the Sys.Application class’s navigate event. Let’s modify our pageLoad event to include a handler for the navigate event:

function pageLoad() 
{ 
    quarterbackItems = $get("client-side").getElementsByTagName("li");          
 
    for (var i = 0; i < quarterbackItems.length; i++) 
    { 
        $addHandler(quarterbackItems[i], "click", quarterbackClicked); 
    }          
 
    Sys.Application.add_navigate(function(sender, e) 
                                 { 
                                    selectQuarterback(e.get_state()["FavoriteQuarterback"]); 
                                 }); 
}

The event arguments that is passed into the event handler has a property called “state” that returns back the key/value pair of history points. In this case we’re simply retrieving the FavoriteQuarterback value and sending it to the selectQuarterback method.

So what does our URL look like when using AJAX History from the client-side? If I run the app and select Donnovan McNabb, the URL looks like the following:

http://localhost:49652/Default.aspx#FavoriteQuarterback=Donnovan%20McNabb

Notice that the structure of the URL is identical to what we saw in the last article, with a few exceptions. You may notice that the double ampersand is gone. Like was mentioned previously, the double ampersand is used only to prefix server-side history points, which in this case we don’t have any of.

There is still one minor change that we need to make to our JavaScript code before moving on, and it is related to the main difference between the client and server approaches. When you call the ScriptManager’s AddHistoryPoint method, all it does it modify the current URL, which effectively registers the history point with the browser. The Navigate event doesn’t fire until you use the back or forward buttons, or return from a bookmarked history point. The Sys.Application class’s addHistoryPoint method on the other hand does trigger the navigate event after being called. So what does that mean for our appication? Well, when I add the history point in the quarterbackClicked event handler, we are also then calling the selectQuarterback method. The “problem” is that the call to addHistoryPoint will trigger the navigate event, which in turns calls selectQuarterback. When the user manually selects a list item, selectQuarterback is being called twice, whereas when they navigate between history points (via back/forward buttons or bookmarks), it would only be called once. To fix this, we can just modify the quarterbackClicked method by removing the call to selectQuarterback, since we know our navigate event handler will take care of that for us:

function quarterbackClicked(e) 
{ 
    var name = e.target.innerHTML; 
    Sys.Application.addHistoryPoint({FavoriteQuarterback: name}, name); 
}

Perfect! Now I’ll run the app and select Carson Palmer:

 lit-2-7-2008-13246-pm.png

I then select Drew Brees:

lit-2-7-2008-13602-pm.png

I then select Peyton Manning:

lit-2-7-2008-13708-pm.png

I hit the back button twice, and then hit the forward button once:

lit-2-7-2008-13801-pm.png

Beautiful. Our app works perfectly, and it was written completely using JavaScript and HTML. Hopefully this exercise highlights how easily you could developer very rich and compelling web applications using the ASP.NET AJAX libraries in conjunction with the new AJAX History feature. It is so rediculously easy to add history to an AJAX application that it really opens up the realistic oppurtunities developers have to enhance web applications, whether they’re existing or brand new.

The final scenario of interest is intermingling server-side and client-side history together. Can it be done gracefully? How exactly does it work? Why would I ever use it? In the final part of this series, we’ll examine the migration points between each approach.

06
Feb

ASP.NET AJAX History Part 1: Server-Side

A really cool feature that is part of the ASP.NET 3.5 Extensions release is the ability to add custom history points to a web application, both server-side and client-side. What do I mean by “history point”? We all know that when a user navigates through a website, each page they visit is logged by the browser (in their history), which allows them to go backwards and forwards (unless of course you used location.replace). This functionality is great and all, but the problem lies in the fact that history points are 1:1 with visited pages. With the advent of sophisticated JavaScript libraries, developers are more commonly providing advanced AJAX behavior in their web applications, many times performing lots of functionality on a single page. So really what is needed here is the ability to associate a history point with the state of a page, rather than just the page itself. This would enable two key scenarios:

  1. Users could use the browser’s back and forward buttons to essentially undo and redo work they’ve performed on the current page. Instead of navigating them to previously visited pages, it would navigate them to previously visited states of the current page.
  2. Users could bookmark the page at any state, allowing them to revisit the page at a later time and be presented with the same state they left off on.

The good news is that ASP.NET AJAX History easily enables both of those scenarios. In this article series, we’ll see how to leverage this new feature to create really cool user-experiences.

The scenario we’re going to be looking at is very a simple web application that allows users to pick their favorite NFL quarterback, out of a list of 10. Tom Brady is clearly the right answer, but because we live in a democratic nation, we’re going to leave the decision up to the user. It’s a pretty trivial exercise, but the cool part is that we’ll let people use the back and forward buttons to undo/redo their selections, as well as create a bookmark for their selection. In this article we’ll look at how to achieve this using the server-side API, then in the next article we’ll tackle performing this purely client-side.

I’m going to start by creating a simple BulletedList:

lit-2-6-2008-95248-am.png

I obviously could have done this a number of different ways, but I haven’t used the BulletedList control in a while so I figured I’d throw caution to the wind and do something crazy. This code will render like so:

lit-2-6-2008-95600-am.png

What I need to do is hook up some code to respond to the clicking of one of the links in the list, that will somehow visually display which quarterback is currently selected. I’ll add an event handler during the page’s initialization period:

protected override void OnInit(EventArgs e) 
{ 
    quarterbacksBulletedList.Click += this.quarterbacksBulletedList_Click; 
    base.OnInit(e); 
}

And the code for the click event handler looks like so:

void quarterbacksBulletedList_Click(object sender, BulletedListEventArgs e) 
{ 
    string name = quarterbacksBulletedList.Items[e.Index].Text; 
    SelectQuarterback(quarterbacksBulletedList.Items[e.Index].Text); 
}

All I’m doing here is grabbing the name of the user-selected quarterback, and then calling a method called SelectQuarterback that is responsible for actually visually depicting the quarterback as selected. The code for that method looks like so:

void SelectQuarterback(string name) 
{ 
    foreach (ListItem item in quarterbacksBulletedList.Items) 
    { 
        if (item.Text == name) 
            item.Attributes["class"] = "selected"; 
        else if (item.Attributes["class"] != null) 
            item.Attributes.Remove("class"); 
    } 
}

All I’m doing is finding the item in the list that matches the user-selected name and adding a CSS class to it called “selected”. Because the user can change their selection, I have to also remove the “selected” class from any list items that were previously selected. There are probably numerous better ways to achieve this, but because this logic isn’t the focus of this article I’ll just leave it at that.

I create a new stylesheet and add the following style:

li 
{ 
	width: 10em; 
}          
 
li.selected 
{ 
	background: url(Selected.png) no-repeat center right; 
}

This will add some formatting to our bulleted list, and display a nice graphic to the item that the user selects. Let’s run what we have so far and see how it looks. I select Tom Brady and I see the following:

lit-2-6-2008-102103-am.png

I then select Tony Romo and I see the following:

lit-2-6-2008-102221-am.png

I’d like to make this a little more “interactive” so I’ll add a ScriptManager control to the page and wrap our BulletedList control in an UpdatePanel:

lit-2-6-2008-110800-am.png

If I run this, the app still runs the same, but without postbacks. Better, but not great. Because we are not posting back anymore, the browser no longer registers each “page visit” as a history point. Regardless how many different quarterbacks I select, I still see this (in IE7):

lit-2-6-2008-110528-am.png

So how do we make it so that when we select a quarterback in the list it creates a new history point that can used to navigate back and forwards as well as bookmark a selection? The ScriptManager control includes a few new members, compliments of the AJAX History feature:

  1. Three properties: EnableHistory, EnableSecureHistoryState, and IsNavigating
  2. One method: AddHistoryPoint
  3. One event: Navigate

By setting EnableHistory to true you are telling the ScriptManager that you want the ability to add custom history points (we’ll cover EnableSecureHistoryState later). The IsNavigating property is a simple boolean that indicates whether a postback has occured due to a history navigation. Now that history is turned on, how do we actually add a history point? We use the ScriptManager’s new AddHistoryPoint method. The simplest version of this method takes a key and a value (or state). It requires a key because you can add many different history points to a page, and it needs a way to seperate them. In our case we are only adding a single history point to the page: favorite quarterback. We need to determine what user action represents a good place to create a history point. For this exercise, the clicking of an item in the bulleted list makes the most sense, so let’s modify the list’s click event handler:

void quarterbacksBulletedList_Click(object sender, BulletedListEventArgs e) 
{ 
    string name = quarterbacksBulletedList.Items[e.Index].Text;        
 
    scriptManager.AddHistoryPoint("FavoriteQuarterback", name); 
    SelectQuarterback(quarterbacksBulletedList.Items[e.Index].Text); 
}

Notice that the only thing we changed is that we’re now calling the AddHistoryPoint method, which we’re just passing the name of the selected quarterback. Now every time a user selects a different quarterback, we’ll be registering a history point for that selection which allows them to move back and forwards. Note that the AddHistoryPoint has two other overloads, both of which also accept a title parameter. The title parameter allows you to associate a page title with a history point, that will be displayed as the browser’s title when the user navigates to that history point via the back and forward buttons. This is a really cool feature, so we’ll modify our call to AddHistoryPoint like so:

scriptManager.AddHistoryPoint("FavoriteQuarterback", name, "Favorite Quarterback: " + name);

The AddHistoryPoint method has one final overload which allows you to pass a NameValueCollection instead of a single key/value pair. This is good if you have numerous history points you want to add (i.e. favorite quarterback, favortie running back, etc). It also takes a page title as well.

If we run this app right now and click on Tom Brady, our browser navigation buttons suddenly come alive:

lit-2-6-2008-112951-am.png

Awesome, now my app is working perfectly and registering each selection as a point in history. If I were to click the back button at this point nothing would happen, and you might be wondering why. We’re successfully adding history points now, but since the data we provide as the history state is completely arbitrary, the app has no idea what to do with it. This is an important point to note: the history points we create are not coupled to the actual page in any way. When we call AddHistoryPoint, we’re not snapshoting the current page’s look and feel, we’re storing just enough data that allows us to recreate the page state, and letting the ScriptManager tell the browser about it. This is one of the reasons why I love the AJAX History because it’s so clean and simple. In fact, this raises the question, how exactly is it registering the history points? How does the AJAX History feature make the browser treat your user-interactions as history points?

Let’s take a look at the URL of the app when we first run it: http://localhost:49652/Default.aspx. Pretty standard stuff. Now let’s click on Tom Brady and then look at the URL:

http://localhost:49652/Default.aspx#&&/wEXAQUTRmF2b3JpdGVRdWFydGVyYmFjawUJVG9tIEJyYWR5gAd6kNVm8xxyN9OxXaQE0P8MvLY=.

Wow, that is a big difference! If you notice, directly after the page name (Default.aspx) we see a hash mark (#) which is followed by a long string a funky looking text. You may recall that the hash mark is used to navigate to a specific <a> on a page. So if I had a webpage with an <a> element whose ID was set to “link”, I could request the page with “#link” and it would scroll down the page to where the <a> element resided. Nothing new here. What’s great is that using the hash mark to navigate the current page doesn’t cause a postback to occur, since you’re essentially telling the browser that you’d like to move to another place on this page and there is no need for a server request. This is how AJAX History is able to create custom history points for you, and navigate through them without posting back. It’s a simple yet elegant solution if you ask me :)

So what is the deal with the long string of text? By default when you call AddHistoryPoint, the ScriptManager will hash your key/value pair before creating the actual history point. This is in case you are storing sensitive data, at which point they’re opting for security by default. For the most part I couldn’t see myself putting secure data in a URL anyways, so I’d prefer to turn this hashing off. This is where the ScriptManager’s EnableSecureHistoryState property comes in. If I set that to false and rerun the app, selecting Tom Brady, my URL is now:

http://localhost:49652/Default.aspx#&&FavoriteQuarterback=Tom+Brady.

That’s certainly a lot cleaner. It’s pretty clear now how our call to AddHistoryPoint translates into a URL. “FavoriteQuarterback” was our key, and “Tom Brady” was our value. All it did was URL encode it and add it to the URL’s hash. The last point of interest is the double ampersand (&&) in the URL. This is there because you can set history points from both the client and server side, and they need to be differentiated. All server-side history points follow the double ampersand, and client-side history points preceed the double ampersand. We’ll see more of this in the next article.

Alright, back to business. Now that we’ve got our history points in place, and we understand how the ScriptManager is translating those points into something the browser can understand, how do we tell our app how to interpret the history points when they are navigated to. When I hit the back button, how does my app know how to recreate the page at the specified state? This is where the ScriptManager’s new Navigate event comes in. This event is fired whenever a page is requested with state data in the URL (#&&key=value) and state is turned on (via the EnableHistory property). This means that when a user hits the back or forward button, which navigates to a URL with state information, the Navigate event will fire. This also means that if a user bookmarks a history point and then comes back to it later, the Navigate event will fire (because the bookmarked URL would have the state in it).

I’m going to modify the page’s initialization method to include the registration of the Navigate event like so:

protected override void OnInit(EventArgs e) 
{ 
    quarterbacksBulletedList.Click += this.quarterbacksBulletedList_Click; 
    scriptManager.Navigate += this.Navigate; 
    base.OnInit(e); 
}

And I’ll implement the Navigate method like so:

void Navigate(object sender, HistoryEventArgs e) 
{ 
    SelectQuarterback(e.State["FavoriteQuarterback"]); 
}

All I’m doing here is calling the same method that is called when a list item is clicked, but I’m passing in the value from the state. This is where the key of your state comes into play, because you later need some way to retrieve that value.

I run the app and select Tom Brady:

lit-2-6-2008-15009-pm.png

I then select Eli Manning:

lit-2-6-2008-15129-pm.png

I then realize that Eli Manning is actually a really lame quarterback, so I hit the browser’s back button, and once again Tom Brady is selected. If I hit the forward button, you’ll notice that Eli Manning is once again selected. I could select different quarterbacks all day long and I’d still be able to move backwards and forwards with ease.

I’m going to select Matt Hasselbeck (go Seattle!) and bookmark the page, which stores this URL: http://localhost:49652/Default.aspx#&&FavoriteQuarterback=Matt+Hasselbeck. I’ll open up another instance of IE and navigate directly to my bookmark:

lit-2-6-2008-15451-pm.png

How sweet is that! With just a few lines of code we’ve turned our little list into an easy to use app complete with AJAX and history points. In the next article we’ll see how to achieve this using the ASP.NET AJAX library.

06
Jan

ADO.NET Data Services Part 9: Data Modification - AJAX (Action Sequences)

Is it just me or is this article’s title a little ridiculous? I think anytime you have a name that makes use of a colon, a hyphen, and parentheses, that’s a good sign you’ve gone too far. I was really tempted to throw an exclamation point at the end, but I was afraid that would cause this entire blog to implode on itself. But you know what? That’s just the kind of grammatical insanity you can expect from this site.

In the previous article we saw the Sys.Data.DataService class’s insert, update, and remove methods, which beautifully allow us to perform data modification operations against a data service using the ASP.NET AJAX libraries. We also learned that as powerful as that method trifecta is, it lacks a unit of work behavior that is sometimes needed, and instead makes individual service requests for each call. I made the statement that the DataService class doesn’t support the unit of work pattern, and that statement is in fact true. But, that doesn’t mean that you can’t achieve a unit of work by other means. There are two classes in the ADO.NET Data Services AJAX libraries that we haven’t yet covered that offer the ability to group together data modification operations into a batch, and execute them all at once. These two classes are Sys.Data.ActionSequence, and Sys.Data.ActionResult.

An ActionSequence object is simply a wrapper on top of a DataService. It maintains a list of data operations, and then uses its underlying DataService reference to execute the operations one by one. There are two ways to create an ActionSequence:

  1. Using its constructor, which takes a DataService instance
  2. Calling the DataService class’s createActionSequence method

Of the two choices, the later would be the preferred method (by me at least). You’re already going to have a reference to a DataService object, so once you determine that you need to create an operation batch, you can simply generate an ActionSequence object for your respective DataService like so:

var sequence = service.createActionSequence();

Now instead of using the DataService reference directly, by calling its insert/update/remove methods, I’ll work with the newly created ActionSequence instance to build my operations batch. At any time when working with an ActionSequence, if you need a reference to its internal DataService, you can use its service property.

var service = sequence.get_service();

That way you can pass around a reference to an ActionSequence and still be able to access its underlying DataService if needed. This property is read-only, so you’re stuck with the DataService used when the ActionSequence was created.

The ActionSequence class has methods that mirror the insert/update/remove methods of the DataService class, with a few differences:

  1. The ActionSequence methods don’t return a Sys.Net.WebRequest instance. This is because as opposed to the DataService, each individual operation isn’t served by a specific WebRequest.
  2. The ActionSequence methods don’t take success/failure callbacks as parameters. This is because you aren’t concerned with responding to the success or failure of the individual operations, but rather the entire batch.
  3. The ActionSequence methods don’t execute immediately.

Note that throughout the rest of the article, the variable “sequence” will simply refer to the ActionSequence instance created above.

Inserting

To add an insert operation to the ActionSequence, you simply call its addInsertAction method. This method takes 4 parameters: the entity object to insert, the name of the entity set to insert it into, arbitrary context data, and a boolean that determines whether you want to pass the refreshed entity object data back to the sequence’s execution callback (the default value is true). Only the first two parameters are required, and the last two are optional, and familiar from the DataService’s insert method.

var manufacturer = { Name: "JC's Awesome Phones" }; 
sequence.addInsertAction(manufacturer, "Manufacturers");

As you can see, nothing special is going on here. The logic to create a new entity object hasn’t changed, and we’re working with our ActionSequence pretty similarly to the way we did with our DataService in the last article. If you don’t need context data, and you want the ActionSequence to pass back your refreshed entity data, your call to addInsertAction can be as simple as seen above.

Our ActionSequence now contains a single command to insert the new manufacturer upon submission. No action has been taken yet, we’re simply building up our batch.

Updating

In order to add an update operation to the ActionSequence, you call its addUpdateAction method. This method takes the exact same parameters as the addInsertAction method. The only differences are:

  1. The URI parameter isn’t required.
  2. The URI parameter refers to a specific entity resource, not an entity set.
  3. The fourth boolean parameter defaults to false instead of true.

I’m going to query for a specific manufacturer:

sequence.get_service().query("Manufacturers?$filter=Name eq 'Nokia'", onSuccess);

Note how I’m accessing the underlying DataService from my ActionSequence reference. This is great because you only have to maintain a reference to your sequence, and not also to your DataService.

I’m now going to change their name, and add that modification to the ongoing sequence:

function onSuccess(result) 
{ 
    result[0].Name = "Nokia is cool"; 
    sequence.addUpdateAction(result[0]); 
}

Note that because I don’t have any context data, and don’t care about the data being passed back to the sequence’s callback, I can simply call addUpdateAction, passing it the modified entity object instance, and nothing else. For the above to work, my ActionSequence instance obviously has to be global. If you don’t prefer to go that route, I could just as easily have passed the ActionSequence reference around as my context data:

sequence.get_service().query("Manufacturers?$filter=Name eq 'Nokia'", onSuccess, null, sequence);

And modified my success callback like so:

function onSuccess(result, context) 
{ 
    result[0].Name = "Nokia is cool"; 
    context.addUpdateAction(result[0]); 
}

You can begin to see the uses of the context data parameter. You can get pretty creative with it and do some pretty cool stuff. The only down side to the above approach is that you might loose some explicitness in your code. The above success callback doesn’t make it clear that the context parameter is an ongoing ActionSequence object. You could obviously rename the parameter to something like “sequence” instead, which would help, but still might not be as clear as you want. If you want to get really cryptic you could even do this all in one step:

sequence.get_service().query("Manufacturers?$filter=Name eq 'Nokia'", 
                            function(result, context) 
                            { 
                                result[0].Name = "Nokia is cool"; 
                                context.addUpdateAction(result[0]); 
                            }, 
                            null, 
                            sequence);

However you decide to approach this, you can see the flexibility you have, which is always a good thing.

Deleting

If you want to add a delete operation, you simply call the ActionSequence’s addRemoveAction method. This method takes three parameters: the entity object instance to delete, the canonical URI of the entity instance to delete, and arbitrary context data. All three parameters are optional. Just like the DataService’s remove method, addRemoveAction allows you to either specify just the entity object reference, just the entity’s URI, or both. All three scenarios will successfully delete data. I prefer the URI approach, since it saves you a query to the data service.

sequence.addRemoveAction(null, "Manufacturers(2)");

If I already had a reference to the manufacturer instance I wanted to delete (or I reconstructed it), I could just call:

sequence.addRemoveAction(manufacturer);

Use whichever form works best for your situation.

Alright, so I’ve now got an ActionSequence that contains three operations:

  1. I’m inserting a new manufacturer called “JC’s Awesome Phones”.
  2. I’m renaming the Nokia manufacturer to “Nokia is cool”.
  3. I’m deleting the manufacturer with the Id of 2 (sorry #2)

Now that I’ve got this batch, how do I actually run it? You simply call the ActionSequence’s executeActions method. It takes two parameters: a callback, and arbitrary context data. Note I didn’t call it a “success callback”, and there is no failure callback. This is because you are executing a batch of operations, some of which may fail, and some of which may succeed. The callback you specify to the executeActions method is simply going to be called when all sequence operations have completed, regardless of their success or failure. Note also that the context data provided to the executeActions method is representative of the entire batch. As you recall, each individual operation has their own context data as well (if specified), so only provide context data to the executeActions method if you want some arbitrary data scoped to the entire group.

The signature for the callback method provided to the executeActions method takes three paramaters: an array of Sys.Data.ActionResult objects, a boolean that determines whether any of the sequence’s operations failed, and the context data provided to the call to the executeActions method (if any). The later two parameters are optional, so if you don’t care about context data, or checking if any errors occurred at the batch level, you can simply omit them.

I’m not concerned with any context data at this point, so I’ll just execute my sequence, passing it a callback:

sequence.executeActions(sequenceCompleted);

Since I’m not using any context data, my callback method could look like the following:

function sequenceCompleted(results, hasError) 
{ 
}

The hasError parameter will be true if an error occurred in any of the sequence’s operations. This isn’t necessarily useful, since you don’t know which operation failed, but it can be used to make a high-level check, before you do any further digging for error resolution. The real point of interest here is the results parameter, which holds an array of Sys.Data.ActionResult instances. The ActionResult class represents the result of an individual data modification operation that was a part of an ActionSequence. So for every operation you add to the sequence, you will have an ActionResult object. In our case, the results parameter will be an array of three ActionResult objects.

The ActionResult class contains three read-only properties and no methods:

  1. actionContext
  2. operation
  3. result

The actionContext property simply returns whatever arbitrary context data you specified (if any) when you called addInsertAction, addUpdateAction, or addRemoveAction. Between the individual context data for each ActionResult, and the context data for the overall batch, you can get pretty creative :)

The operation property returns a string that equals either: “insert”, “update”, or “remove”, depending on what the actual operation was.

The result property is the most interesting, as its value depends on the actual operation:

  1. If the operation was an insert or update, and you specified to pass back the refreshed entity data when adding the operation to the sequence, then the result property will return a refreshed version of the inserted/updated entity object.
  2. If the operation was an insert or update, and you specified not to pass back the refreshed entity data when adding the operation to the sequence, then the result property will return null.
  3. If the operation was a remove, then the result property will always return null.
  4. If the operation resulted in an exception, then the result property will return an instance of Sys.Data.DataServiceError that contains information about the error that occurred.

For an explanation of the DataServiceError class see part 6 of this series. Knowing the above result criteria will make it so you can diagnose just about every possible scenario. Below is an example of a callback method that handles the completion of an ActionSequence’s execution:

function sequenceCompleted(results, hasError) 
{ 
    if (hasError) 
    { 
        for (var i = 0; i &lt; results.length; i++) 
        { 
            var result = results[i]; 
 
            if (Sys.Data.DataServiceError.isInstanceOfType(result.get_result())) 
            { 
                alert("The following " + result.get_operation() + " operation failed."); 
            } 
        } 
    } 
    else 
    { 
        alert("All operations succeeded."); 
    } 
}

If no errors occurred, I alert the overall sequence success, otherwise I enumerate through every ActionResult instance to determine which operations erred, at which point I alert the error reason. You can see how you could do some pretty sophisticated exception handling if you wanted to. I’m not even checking for timeouts or examining the actual exception message, type, or stack trace. Needless to say, the ActionResult class, paired with the DataServiceError class, provides you with a nice amount of information.

Being able to use the ActionSequence class to create a unit of work is very beneficial. This coupled with the ability to make individual data modification requests, as we saw in the last article, makes for a very powerful and flexible environment for working with a data service using the ASP.NET AJAX libraries.

The ActionSequence class has one more method of interest: clearActions. If you for some reason need to flush your current ActionSequence, you can use the clearActions method like so:

sequence.clearActions();

You could imagine developing some pretty sweet AJAX applications that let the user create a series of data modifications, and then choose to undo them. I absolutely love the ADO.NET Data Services AJAX libraries. They provide you with some amazing power and functionality. Having an arsenal this strong in JavaScript makes it very compelling to move application logic to the client-side. Between WebDataContext, Sys.Data.DataService, and Sys.Data.ActionSequence, you can build some awesome data service client applications, regardless what the environment is. The only thing left to see is consuming a data service from within a Silverlight 2.0 application, which will be so beautiful I’ll most definitely shed a few tears.

In the last three articles we’ve seen how to perform data modification operations from a client’s perspective. What we haven’t seen yet are the service-side aspects that relate to data modification scenarios. In the next article, we will see just what has to be taken into consideration when creating a data service that needs to accept data modification requests.

04
Jan

ADO.NET Data Services Part 8: Data Modification - AJAX

In the last article, we discussed how to perform data modifications against a data service using the WebDataContext class. While that represents a great solution for most scenarios, the need for an AJAX story still exists. You may recall from part 6 that the WebDataContext class has a counterpart in the ASP.NET AJAX library, namely Sys.Data.DataService. Reason would lend us to believe that the same functionality exposed by the WebDataContext class, in terms of data modification, would be available and similarly exposed on the DataService class, and reason would be right.

In order to use the ADO.NET Data Services client library, you have to perform some minor setup. Please refer to part 6, if you haven’t done so already, and ensure that you can query your data service using the JavaScript DataService object. The following article is meant to be an extension of the previous two articles, and therefore won’t restate anything previously explained.

Before moving on, we need to mention a few differences between WebDataContext and DataService. The biggest difference between the two is that DataService does not act as a unit of work. What does that mean? It means that DataService doesn’t “cache” a series of data modification operation, and submit them all as a single unit. When you work with the WebDataContext class, and you add/update/delete entity objects, none of that takes affect until you call SaveChanges, at which point all changes made are submitted at once. When working with the JavaScript DataService object, all data modification requests are made immediately. In most cases, this behavior is what you would want anyways, but it’s important to expect this behavior, otherwise one might be confused when transitioning from using WebDataContext to DataService.

The second difference between the two environments is that when working with the DataService object in JavaScript, you don’t have a generated (or hand-written) client-side model. This means that we don’t have a Carrier, CellPhone, or Manufacturer class. This isn’t an issue though since DataService works purely with JSON objects. This means that in order to create instances of entity objects, all you have to know is their required properties. For instance, the Carrier model class only has two properties: Id, and Name. Id is a service generated identity property, so in order to create a new carrier, I’d only need to be concerned with the Name property.

var carrier = { Name: "Lost In Tangent Mobile" };

That’s all that it takes to create a new carrier instance. The same logic would be applied to create more complex entity instances. Without further ado, let’s jump right into how to perform each individual data modification operation.

Note: Throughout the rest of the article, the variable “service” simply represents a Sys.Data.DataService object that points at the data service.

Inserting

Using our DataService instance, how can we insert the carrier instance created above?

service.insert(carrier, "Carriers", onSuccess, onFailure, context, true);

The insert method takes six parameters: the object you want to insert, the name of the entity set to insert into, success callback, failure callback, arbitrary context data, and a boolean that determines whether you want the DataService to pass a refreshed entity object back to the success callback. Only the first two parameters are required. This will immediately submit the insert request to the data service, and then call the respective callback upon success of failure. Pretty easy stuff.

The success and failure callback methods have the exact same signature as the success/failure callback methods used with the DataService’s query method as seen in part 6. The operation parameter will always equal “insert”.

Now you may be wondering what happened to the Id property. We didn’t specify it when we created the carrier JSON object above, but we know that the data service was going to generate one for us. In the previous article we saw that the WebDataContext class would actually refresh any properties on entity instances it modified when it submitted changes (as long as its MergeOption was set correctly). That way any generated identity values would be present after the call to SaveChanges. DataService offers the same behavior. After you call its insert method, it will not only refresh, but create any properties that were omitted but affected as a result of the insertion (i.e. the Id property of our carrier instance). This means that if I modified our success callback like so:

function onSuccess(result, context, operation)
{
    alert(result.Id);
}

I would get an alert message stating 7, or whatever the generated Id would be. As long as we create a valid JSON object, with the proper property names, the DataService will take care of the rest and fill in the gaps :) Now, this behavior is dependent upon the sixth parameter of the insert method being set to true, or being omitted (because the default value is true). If I modified the call to insert like the follow:

service.insert(carrier, "Carriers", onSuccess, onFailure, null, false);

The above onSuccess method would result in an error, because we told the DataService not to pass the entity object to the success callback. This provides the equivalent of the MergeOption property of the WebDataContext class.

One more point of interest is that the insert method returns the instance of Sys.Net.WebRequest that is used internally by the DataService to make the actual service request. For the most part, you probably won’t need to use the object manually, but it’s important to know it’s there, in case you do. Also note that the insert method is making an HTTP POST request to the data service, not an HTTP GET request, like the query method uses.

Updating

Once you have an entity instance, whether you’ve queried for it, or reconstructed it, you can modify its property values. If you want to persist those changes back to the data service, all you have to do is call the DataService’s update method.

service.update(carrier, "Carriers(2)", onSuccess, onFailure, context, true);

The update method takes the exact same six parameters as the insert method. There are five differences though:

  1. The second parameter isn’t the name of the entity set, but rather the URI of the exact resource you’re updating. This is why I’m specifying “Carriers(2)”, because that is the canonical URI of the carrier I’m updating.
  2. The resource URI isn’t required, which means you could call the update method, passing only the entity object you wish to update.
  3. The sixth parameter is false by default. This means that the DataService won’t pass a refreshed entity instance to your success callback, unless you specifically tell it to.
  4. The update method is creating an HTTP PUT request.
  5. The operation parameter passed to the success/failure callbacks will always equal “update”.

Deleting

To insert data, you call the DataService’s insert method. To update data, you call the DataService’s update method. That must mean if I want to delete data I can call the DataService’s delete method! There’s only one problem with that: delete is a reserved keyword in JavaScript, and therefore can’t be used as a method name. Does this mean that the ADO.NET team decided to omit the ability to delete data when working with a data service using the DataService class in JavaScript? Well that would have been ridiculous. Fortunately for us they weren’t too partial to naming the delete method “delete”, and just went ahead and called it “remove”.

service.remove(carrier, "Carriers(2)", onSuccess, onFailure, context);

The remove method takes five parameters, all of which are optional. Does that mean I can just call remove with absolutely no parameters, and the DataService be smart enough to know what data I actually wanted to delete? That would be pretty sweet wouldn’t it?

// Perform rediculous magic data deletion :D
service.remove();

Yeah, not so much. The two callbacks, and the context data are completely optional, but the first two parameters are exclusively optional. There are three possible scenarios for calling the remove method:

  1. Provide the instance of the entity object you want to delete and omit the resource URI.
  2. Provide the resource URI and omit the entity object instance.
  3. Provide the entity object instance and resource URI, which would be overkill, but perfectly acceptable.

If I already had a reference to the carrier I wanted to delete I could use the following:

service.remove(carrier, null, onSuccess);

If I didn’t have a reference to the carrier I wanted to delete, but I knew its resource URI, I could use the following:

service.remove(null, "Carriers(2)", onSuccess);

The awesome thing about the above approach is that you don’t have to make an extra service call to retrieve the entity object you want to delete, you can simply target it by URI, deleting it in one request. This is one thing that the DataService class does better than its WebDataContext counterpart. Using the later always requires a reference to the entity object instance you want to delete.

As you may have guessed, just like insert and update, the remove method returns the Sys.Net.WebRequest object used internally to make the HTTP DELETE request. Also note, the operation parameter passed to the success/failure callbacks will always equal “remove”.

Being able to perform data modification operations against a data service using the ASP.NET AJAX is extremely useful, but having a unit of work is essential in a lot of cases, and the above DataService method’s don’t satisfy that need. Luckily the DataService class does in fact support unit of work behavior for performing a series of data modification operations as a group. In the next article we will discuss action sequences.