Using Unobtrusive JavaScript in ASP.NET Applications
Mitigate potential issues with JavaScript in apps and provide a rich experience for users
September 20, 2011
Nothing is a worse than when an application breaks because of a strange or unknown reason. A customer might call and mention errors that say "Sys is undefined" or "x is null or not an object". And when one JavaScript error happens, others follow, snowballing into a series of errors and notification pop-ups that causes tremendous grief and lost work for the user and potentially putting a grinding halt to the application.
Sometimes these errors are warranted: Errors that weren't caught during testing made their way (typically from the less commonly traveled paths) into the final product. Other times, these errors come from unknown circumstances. When it comes to transmitting JavaScript over the wire, at times there might be issues with dropped connections, differing browser versions (including updates and hotfixes), disabled browser settings, slow connection speeds, proxy servers, and so on. Regardless of the reason—even though it might not relate to the application—users tend to blame one common denominator: the developer.
The demands of high-performance applications are bringing JavaScript and other rich-client technologies back to the forefront of application development. Traditionally, all the needs of an application relied on the server. Even as Ajax worked its way into the Microsoft .NET framework, Microsoft and other vendors employed controls such as the ASP.NET UpdatePanel to find an imperfect balance between server-side processing needs and a rich-application feel. As the ASP.NET Ajax framework has matured, vendors have chosen to leverage more JavaScript in developing components that provide this rich feel with less server reliance (at least for visual effects and appearance).
When it comes to problematic issues with JavaScript, a variety of techniques can be employed to mitigate potential issues. One of these techniques is Unobtrusive JavaScript. This technique marries the best of both worlds by leveraging the strengths of client-side scripting with a backup plan to gracefully degrade to server-side processing if JavaScript failures occur.
The overall goal is to design your application to load itself initially from the server, then replace the functionality that causes postbacks with client-side functionality. This client-side functionality communicates to the server via web services, ASP.NET Ajax callbacks, or get and post operations through a script library. For example, suppose that a web form has a LinkButton control. A LinkButton typically posts back to the server, using the client-side __doPostBack framework method. This method sets two form-field values, __EVENTTARGET and __EVENTARGUMENT, which the server recognizes and uses to invoke the IPostBackEventHandler.RaisePostBackEvent method for that control (if defined). The LinkButton then fires the server-side click event and command event (if a command name is defined), which developers tap into.
With an unobtrusive mindset, the developer doesn't want to break this functionality. Adherents to Unobtrusive JavaScript choose instead to write additional JavaScript, separate from the markup that taps into the client-side rendering of the LinkButton, replacing the attributes that post back to the server with a call to a client-side method. Clicking the button fires the client-side method and uses a script framework to make a call to the server; the data comes back via a callback, which injects the data into the UI by using a technique that we'll examine later in the article.
This implementation is specific and intentional: Unobtrusive JavaScript aims to ensure that the server-side portion of the application works. If the JavaScript that is associated with our sample LinkButton control succeeds in its implementation and manages to download from the server, then the initialization script of this component replaces the server-side link with a client-side link that calls a web service. If the JavaScript fails, the default LinkButton still posts back to the server.
This example is simple but illustrates that Unobtrusive JavaScript involves several design assumptions. First (and most simply), we're assuming that the application works in both server- and client-side modes. When the application initially renders, the server renders the UI and its initial data sets; client-side scripting takes over for future requests (if loaded successfully). This approach can take the edge off applications by using a Representational State Transfer (REST) approach, as Facebook does. The goal is to not completely render with client-side JavaScript. Additionally, implementing this type of approach takes more work and carefully planning.
Unobtrusive JavaScript is not solely a technique for dealing with failures. Adherents also advocate that the approach provides a proper separation of concerns between markup and behavior. Rather than directly tying an object to its event handler in markup, attaching to the event in client-side JavaScript provides a proper separation and keeps all related JavaScript together.
Throughout this article, we'll examine the use of Unobtrusive JavaScript and implementation in the ASP.NET MVC 3 framework, compared to how this technique can be applied traditionally. Each approach has benefits and drawbacks, which I hope I can depict in a fair and accurate light.
Using Unobtrusive JavaScript Traditionally
Let's look at a sample of a form that posts a drop-down value to the server after the user clicks a Submit button. After making a selection, the form posts back to the Availability action method and returns the status of the availability, given by a variable stored in the ViewData collection. Figure 1 contains the form.
@{Html.BeginForm("Availability", "Traditional");}@ViewData["Availability"]@Html.DropDownList("Trip", new SelectListItem[]{new SelectListItem { Text = "Trip A", Value = "A" },new SelectListItem { Text = "Trip B", Value = "B" },new SelectListItem { Text = "Trip C", Value = "C" }})@{Html.EndForm();}
The controller receives the trip selection from the posted form data. However, the action method was architected specifically to process both full postbacks to the server and Ajax requests. A useful extension method, IsAjaxRequest, on the HttpRequestBase instance checks the header of the request to determine which action to take and works with both the ASP.NET Ajax framework and jQuery (both of which are used in this article). Figure 2 contains the code to process the requests.
[HttpPost]public ActionResult Availability(string Trip){if (Request.IsAjaxRequest()){return Json(new{available = ((Trip.Contains("A")) ? true : false)});}else{ViewData["Availability"] = "Trip from server is: " + (Trip.Contains("A") ? "Available" : "Not Available");return View("Index");}}
Each type of action yields a different response; we don't want to serve the full view when posting data using Ajax. Instead, the action returns JavaScript Object Notation (JSON) to allow the client-side JavaScript to read its value and act accordingly. For a full postback, the availability of a trip is displayed by storing the result in ViewData. If for some reason that Ajax is unavailable or jQuery fails to load properly, then the in-place process of posting back to the server is the default fallback. This fallback is an added safeguard against several potential failures of client-side JavaScript.
Using MVC 3 Unobtrusive JavaScript
With the ASP.NET MVC 3 Unobtrusive JavaScript implementation, not much has changed. You don't need to implement any major features because the implementation works through the Ajax helpers. First, a feature must be enabled by using the property declaration in the application settings or via the HtmlHelper object. For this example, I chose to use application settings, as depicted in Figure 3.
The MVC framework makes asynchronous calls by using the Ajax helper object. Figure 4 shows the implementation of the form, using Ajax.
@using (Ajax.BeginForm("Availability", "Updated", new AjaxOptions { UpdateTargetId = "display" })){@ViewData["Availability"]@Html.DropDownList("Trip", new SelectListItem[]{new SelectListItem { Text = "Trip A", Value = "A" },new SelectListItem { Text = "Trip B", Value = "B" },new SelectListItem { Text = "Trip C", Value = "C" }})}
As you can see, the MVC framework encapsulates many of the Ajax calls and callbacks. This encapsulation is the first benefit to this approach: We don't need the entire jQuery script to make these features work. The specified Ajax options control the underlying behavior, in that the form sends the posted data to the server, receives the underlying content, and injects it into the HTML element with the "display" ID.
There are many obvious differences between these two examples. One of the primary differences is the output. If you look at the browser to determine what is rendered, the following form definition appears.
We begin to see the incorporation of HTML5, as shown in Figure 5. Although jQuery doesn't directly append any Ajax-specific attributes to the form, the MVC framework emits some new attributes.
The HTML5 specification states that any attribute prefixed with "data-" is a new attribute that permits storage of private data attached to an HTML tag (for more information, see John Resig's blog post). Listed in the previous code example are three Ajax-specific attributes that are a part of a set of attributes that the Ajax helper uses. (For an entire list, see Brad Wilson's blog post on Unobtrusive JavaScript.) Two of these attributes store the specific action that the posting to the form will take (i.e., the replacement of data content stored within the "display element"), whereas the latter simply enables the feature. The form posts back to the action method that is defined in Figure 6—with one complication.
[HttpPost]public ActionResult Availability(string Trip){if (Request.IsAjaxRequest()){return Content("Trip from client is: " + (Trip.Contains("A") ? "Available" : "Not Available"));}else{ViewData["Availability"] = "Trip from server is: " + (Trip.Contains("A") ? "Available" : "Not Available");return View("Index");}}
You might notice that when you implement these features, the unobtrusive aspects of the framework don't kick in. The view performs the full postback to the server as if no client-side scripting ever occurred, even when Unobtrusive JavaScript is enabled. The MVC framework created a few new sets of scripts that weren't included in the original set of MVC scripts, as shown in Figure 7. After this, everything begins to work as expected.
Traditionally Refreshing the UI
A common task in application development is keeping data up to date. Grids are bound and then rebound when data changes, to prevent the user from seeing stale data. The typical model for applications is this: A postback to the server is performed, and then the action method processes the post and returns an updated UI to the browser. As technology shifts to the client, the data is more commonly queried from the server by using a web service or an HTTP handler, or data is posted to the server by using jQuery or another script framework.
We're going to see these client-side features in action. The code in Figure 8 is a sample form; the crux of the content is infected into the view by using the Html.Action method, which renders the partial view that simply contains the table with data to bind, as Figure 8 shows.
@using (Html.BeginForm("UpdateGrid")){@(Html.Action("DisplayGrid"))}
Notice how the action method is surrounded by a DIV tag with a unique ID. Adding a DIV tag makes targeting the content to replace with jQuery much easier, as we'll soon see. (Remember this DIV tag as you read the next section.)
The goal with the accompanying jQuery script is to allow the default implementation to render from the server and to replace the Submit button click with a client-side script that will use $.post to post the data back to the server. In this example, we're swapping out only part of the form. However, the entire form can be replaced, depending on the view setup and requirements. Figure 9 shows the script to update the UI.
$("#UpdateFormSubmit").live("click", function (e) {if (e.preventDefault) e.preventDefault();if (e.stopPropagation) e.stopPropagation();var form = $(this).parents("form:first");$.post(form.attr("action"), { Key: $("#Key").val() }, function (data) {$("#UpdateForm").html(data);$("#Status").html("Request Succeeded");});});
The script in Figure 9 replaces the stale grid with a newer version. This process resembles the UpdatePanel control approach that is present in Web Forms development. The main difference is that jQuery posts only the necessary data within the form, rather than posting the form-related data and entire viewstate to the server, as happens when using the UpdatePanel control.
Swapping the new content for the old is one approach that can be taken. There are other techniques. For instance, the MVC controller could return JSON, and the callback could use that JSON data in some way.
MVC 3 Refreshing of the UI
Rather than requiring that you write explicit scripts to accomplish this feat, ASP.NET MVC 3 again makes use of the Ajax helpers to target the element to update. The form is the same as we saw with the traditional approach. Note the options that are chosen for the Ajax postback in Figure 10.
@using (Ajax.BeginForm("UpdateGrid", new AjaxOptions{InsertionMode = InsertionMode.Replace,UpdateTargetId = "UpdateForm",OnSuccess = "Success",OnFailure = "Failed"})){@(Html.Action("DisplayGrid"))}
The Ajax update targets a specific child element of the panel for replacement by its ID. Therefore, the contents of the form are posted to the server, but only the child UpdateForm DIV is actually replaced with the contents of the partial postback. The MVC framework automatically manages the wiping of old content and the injection of new content, a process that appears seamless to the user. Again, we have quite a different form structure on the client than with our jQuery counterpart. Let's take a look at the form markup in Figure 11 to see which new attributes we'll come across.
As you can see, the new HTML5 data attributes have become the norm for storing the additional related data to the Ajax updates.
Comparing Approaches
The MVC framework encapsulates many features into the framework for you, requiring a lot less JavaScript. This approach has its pros and cons. One benefit is that the developer no longer needs details about what happens under the scenes, barring some minor setup work. However, the developer becomes separated from the process, making any callback errors more difficult to fix.
With jQuery, the developer knows exactly what is happening during the process. Also, jQuery can support the development of plug-ins to write reusable scripts, at the cost of writing more JavaScript than with the ASP.NET MVC 3 framework. And jQuery can return a response in JSON or textual content, giving the developer more options.
At the root of both frameworks and approaches, both implement the UI in the same manner and both can replace the inner contents of a targeted element or inject a response in another element. I don't think you'll find too many scenarios in which one framework supports less than the other, barring some advanced scenarios.
For those who want to stick to a separation of markup and behavior, Ajax helpers fits this model perfectly, leveraging separate JavaScript files for the unobtrusive behavior that handles the crux of the work. And jQuery fits this mold because it attaches to the UI controls outside of the markup. Both frameworks and approaches work similarly, so it's up to you to determine which option you like better in your development model.
About the Author
You May Also Like