Introduction
Whenever you call action methods in an ASP.NET MVC application, by default they are executed in synchronous fashion. In order to improve the overall performance and responsiveness of your application you may wish to execute the code asynchronously. You may execute code asynchronously from two distinct places – server-side and client-side. This article discusses both of these scenarios and shows how to create asynchronous controllers and use SessionState attribute.
What are Asynchronous Operations?
Before you write any code that runs asynchronously, it would be nice to quickly brief what asynchronous operations are and the benefit of such operations. Suppose that you have a piece of code as shown below :
public void ParentMethod() { ChildMethod1(); ChildMethod2(); ChildMethod3(); }
The ParentMethod() is a method that you are supposed to call from the rest of your application. The ParentMethod() in turn calls three child methods viz. ChildMethod1(), ChildMethod2() and ChildMethod3(). Also assume that all the child methods are remote methods. Under synchronous execution (default as far as server-side code in ASP.NET is concerned) the three child methods will execute serially i.e. one after the other. Thus ChildMethod1() will execute first, will return the control back to the parent method and only then will ChildMethod2()be executed. If the child methods are taking say 5, 10 and 15 seconds to complete, the effective time taken for executing all of them will be 5 + 10 + 15 i.e. 30 seconds.
Under the asynchronous scheme of code execution, the child methods are called in parallel i.e. ChildMethod2() will start its execution without waiting for ChildMethod1() to complete. So, if the three methods are taking 5, 10 and 15 seconds to complete, the effective time taken will not be 30 seconds as in the former case but 15 seconds (the time taken for the longest running method).
Of course, there are other factors that affect the execution of asynchronous code but the above example highlights the main difference between synchronous and asynchronous operations.
As far as ASP.NET MVC is concerned there are two distinct places where asynchronous operations can be invoked:
- Server-side
- Client-side
In the former case you will use asynchronous controllers and in the latter case you will go for session-less controllers on the server along with AJAX code at the client-side.
Creating Asynchronous Controllers
Whenever you add a controller to your ASP.NET MVC application, you will see a class declaration like this:
public class HomeController : Controller { ... }
By default, the newly added controller class is inherited from Controller base class. If you wish to code asynchronous action methods you should inherit the controller class from AsyncController class instead.
public class HomeController : AsyncController { ... }
When you inherit your controller from AsyncController base class, an instance of AsyncManager class is made available to your code. The AsyncManager class allows you to flag asynchronous operations and also helps you to store pieces of data that are passed to the method handling asynchronous operation completion. Use of AsyncManager will be clear when you develop an example in later sections.
Creating Asynchronous Action Methods
The asynchronous controller declared, as shown in the preceding section, can contain asynchronous action methods. ASP.NET MVC follows certain patterns for creating asynchronous methods. For every action that you wish to execute in asynchronous fashion there will be two methods – one that initiates the operation and other that completes the operation. For example, say you wish to create Index() action in asynchronous fashion. So, your code will resemble that shown below :
public void IndexAsync() { ... } public ActionResult IndexCompleted(...) { ... }
As you can see the first method takes the form XXXXAsync() and the second method takes the form XXXXCompleted(). The second method can have a list of parameters depending on what you put inside AsyncManager.
Let’s say you are developing an application that fetches RSS news feeds from different sources (say Google, MSN, Yahoo etc.) and then aggregates them to produce a single feed. So your skeleton methods will look like this:
public class HomeController : AsyncController { private void GetGoogleNews() { } private void GetMSNNews() { } public void IndexAsync() { } public ActionResult IndexCompleted() { } }
The IndexAsync() method needs to call GetGoogleNews() and GetMSNNews() helper methods in asynchronous fashion. So, the complete code of IndexAsync() method looks like this:
public void IndexAsync() { AsyncManager.OutstandingOperations.Increment(2); Task.Factory.StartNew(() => GetGoogleNews()); Task.Factory.StartNew(() => GetMSNNews()); }
As you can see, before calling GetGoogleNews() and GetMSNNews() methods you use AsyncManager instance to flag the number of pending operations. The OutstandingOperations.Increment() method allows you to specify the number of outstanding operations. Notice how the code uses Task class to call the methods asynchronously. The outstanding operation count needs to be decremented once the individual operations are done with. This can be done inside the GetGoogleNews() and GetMSNNews() methods as shown below:
private void GetGoogleNews() { XmlReader reader = XmlReader.Create("http://news.google.co.in/news?pz=1&cf=all&ned=in&hl=en&output=rss"); Rss20FeedFormatter formatter = new Rss20FeedFormatter(); formatter.ReadFrom(reader); reader.Close(); AsyncManager.Parameters.Add("GoogleNews", formatter.Feed.Items); AsyncManager.OutstandingOperations.Decrement(); } private void GetMSNNews() { XmlReader reader = XmlReader.Create("http://msn.com/rss/news.aspx"); Rss20FeedFormatter formatter = new Rss20FeedFormatter(); formatter.ReadFrom(reader); reader.Close(); AsyncManager.Parameters.Add("MSNNews", formatter.Feed.Items); AsyncManager.OutstandingOperations.Decrement(); }
As you can see, both of the methods essentially make use of classes from System.ServiceModel.Syndication namespace to fetch RSS feed items from a specified source. Notice the code marked in bold letters. It uses the Parameters collection of AsyncManager object to store application specific pieces of data. In this case you store feed items with key names GoogleNews and MSNNews respectively. These parameters will be passed to the completion method. The code then calls the OutstandingOperations.Decrement() method so as to decrement the pending operation count.
When all the outstanding operations are completed, the async completion method is called and all the parameters are passed to it. In our example, the async completion method looks like this:
public ActionResult IndexCompleted(IEnumerable<SyndicationItem> GoogleNews, IEnumerable<SyndicationItem> MSNNews) { List<SyndicationItem> allItems = new List<SyndicationItem>(); allItems.AddRange(GoogleNews); allItems.AddRange(MSNNews); allItems.Sort(CompareDates); ViewBag.FeedItems = allItems; return View(); }
As you can see, the IndexCompleted() method accepts two parameters of type IEnumerable<SyndicationItem>. It then aggregates all the news feed items and passes them to Index view via ViewBag (FeedItems property).
Consuming Asynchronous Action Methods
Consuming asynchronous action methods is no different than synchronous ones. You will still refer the action method as Index in your URLs and Views. For example, consider the Index view that renders the aggregated news feed items in an HTML table.
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %> <%@ Import Namespace="System.ServiceModel.Syndication" %> <!DOCTYPE html> <html> <head runat="server"> <title>Index</title> </head> <body> <div> <table border="1" cellpadding="6" cellspacing="0"> <tr><th>Latest News from Google and MSN</th></tr> <%foreach (SyndicationItem item in ViewBag.FeedItems) {%> <tr><td><a href="<%= item.Links[0].Uri %>"><%= item.Title.Text %></a></td></tr> <%}%> </table> </div> </body> </html>
To run this view, you will still use Index as the action name in the URL and not IndexAsync. So, your URL will be:
Figure 1:Use Index as the action name in the URL
Session-less Controllers and Client-Side Asynchronous Calls
In the preceding example you invoked methods asynchronously at the server end. Many modern web applications make use of AJAX to call server methods asynchronously from the client-side. For example, jQuery makes use of $.ajax() to make remote calls in asynchronous fashion. If you are making a single remote call using $.ajax() nothing special needs to be done. However, if you wish to make multiple $.ajax() calls to server at one go then you need to be aware of the session state behavior of ASP.NET MVC. By default, ASP.NET session is available to all the controller methods and you can read / write data to the Session storage. However, ASP.NET needs to ensure that at a time only one call from a client is accessing the session. In many cases you don’t need session state at all but due to this default behavior multiple $.ajax() calls cannot work in an efficient manner. In such cases it is recommended to turn off the session state altogether. This way ASP.NET need not bother about access to session store and multiple client-side AJAX calls can perform efficiently.
To disable session state for controller actions you use [SessionState] attribute. The following controller class shows how this attribute is used.
[SessionState(SessionStateBehavior.Disabled)] public class RssController : Controller { ... }
Notice how SessionState attribute sets session state behavior to Disabled. Also notice that the controller class (RssController) is not inherited from AsyncController because you will be invoking asynchronous operations from the client-side.
The remaining methods of RssController class are similar to what you already developed for HomeController with a few modifications.
[HttpPost] public JsonResult GetGoogleNews() { XmlReader reader = XmlReader.Create("http://news.google.co.in/news?pz=1&cf=all&ned=in&hl=en&output=rss"); Rss20FeedFormatter formatter = new Rss20FeedFormatter(); formatter.ReadFrom(reader); reader.Close(); return Json(formatter.Feed.Items); } [HttpPost] public JsonResult GetMSNNews() { XmlReader reader = XmlReader.Create("http://msn.com/rss/news.aspx"); Rss20FeedFormatter formatter = new Rss20FeedFormatter(); formatter.ReadFrom(reader); return Json(formatter.Feed.Items); }
As you can see the methods return JsonResult because jQuery code will be handling the returned data. Also notice the use of Json() method to serialize feed items in JSON format.
Invoking Action Methods from Client-Side
To invoke the above action methods from the client-side, you will make use of $.ajax(). The following jQuery code from the Index view of RssController shows how.
$(document).ready(function () { $.ajax({ url: '/Rss/GetGoogleNews', type: 'POST', dataType: 'json', success : ShowGoogleNews }); $.ajax({ url: '/Rss/GetMSNNews', type: 'POST', dataType: 'json', success: ShowMSNNews }); }) function ShowGoogleNews(results) { $("#googleDiv").append("<table border='1' cellpadding='6' cellspacing='0'></table>"); $("#googleDiv table").append("<tr><th>Google News</th></tr>"); for (var i = 0; i < results.length; i++) { $("#googleDiv table").append("<tr><td><a href='" + results[i].Links[0].Uri + "'>" +results[i].Title.Text + "</a></td></tr>"); } } function ShowMSNNews(results) { $("#msnDiv").append("<table border='1' cellpadding='6' cellspacing='0'></table>"); $("#msnDiv table").append("<tr><th>MSN News</th></tr>"); for (var i = 0; i < results.length; i++) { $("#msnDiv table").append("<tr><td><a href='" + results[i].Links[0].Uri + "'>" + results[i].Title.Text + "</a></td></tr>"); } }
As you can see, the $.ajax() calls invoke GetGoogleNews() and GetMSNNews() action methods from the RssController in asynchronous fashion. Upon successful completion of the $.ajax() calls ShowGoogleNews() and ShowMSNNews() client-side functions will be called. These functions essentially display the news feed items in an HTML table.
That’s it! You can run the Index view and see how the feed items are rendered.
Summary
Asynchronous operations are helpful to improve the overall performance and responsiveness of your web application. As far as ASP.NET MVC is concerned you can program asynchronous operations from two distinct places – server-side and client-side. Programming server-side asynchronous operations involve inheriting your controller class from the AsyncController base class and then create action methods of the form XXXXAsync() and XXXXCompleted() where XXXX is the name of the action method. The AsyncManager object allows you to flag outstanding operations and to store data as key-value pairs. The parameters stored in AsyncManager are passed to the async completion method. The client-side asynchronous operation are invoked using jQuery $.ajax() calls. To serve multiple AJAX calls efficiently, you can go for session-less controllers. Session-less controllers make use of [SessionState] attribute to disable the session state.