ASP.NET MVC: Using asynchronous controller to call web services
Lately I wrote about how to make a lot of asynchronous calls to web services during ASP.NET page processing. Now it’s time to make same thing work with ASP.NET MVC. This blog post shows you how to use asynchronous controllers and actions in ASP.NET MVC and also you will see more complex scenario where we need to gather results of different web service calls to one result set.
![]() | Experiments.WebAppThreading.2.zip VS2010 solution | 113KB |
![]() | Source code repository GitHub |
Asynchronous controllers
ASP.NET MVC has its own mechanism for asynchronous calls. I don’t know why but it seems strange when you start with it and completely logical when you finish. So prepare yourself for disappointment – here is nothing complex. My experiment was mainly guided by MSDN Library page Using an Asynchronous Controller in ASP.NET MVC.
As a first thing let’s compare two controllers that have basically same meaning and in default case also the same behavior. First one is our classic synchronous controller.
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
And here is asynchronous version of same thing.
public class HomeController : AsyncController
{
public void IndexAsync()
{
}
public ActionResult IndexCompleted()
{
return View();
}
}
In the case of asynchronous controller we have to use Async and Completes prefixes for same controller methods. I mean, instead of one method we have two methods - one that starts asynchronous processing and the other that is called when all processing is done.
AsyncManager
ASP.NET MVC solves asynchronous processing using its own mechanism. There is one thing we cannot miss and it is class called AsyncManager. From documentation we can read that this class provider asynchronous operations to controller. Well, not much said, but let’s see how we will use it. One thing that seems a little bit dangerous to me is that we have to deal with some kind of counter. Take a look at the following code and see how Increment() and Decrement() methods are called.
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment();
var service = new DelayedHelloSoapClient();
service.HelloWorldCompleted += HelloWorldCompleted;
service.HelloWorldAsync(delay);
}
public ActionResult IndexCompleted()
{
return View(result);
}
void HelloWorldCompleted(object sender, HelloWorldCompletedEventArgs e)
{
AsyncManager.OutstandingOperations.Decrement();
}
These methods belong to OperationCounter class that maintains count of pending asynchronous operations. Increment() method raises count and Decrement() decreases it. Why I don’t like the need to call these methods is the fact that there calls are easy to forget. Believe, I was more than once able for it during five minutes. Forget these methods and you may see mysterious and hard to catch errors.
Parameters collection
AsyncManager has also parameters collection. This is solution I like a lot because instead of keeping up some controller-wide field I can save my values from asynchronous calls to AsyncManager and therefore I avoid mess in my controller code.
Parameters collection is brilliant idea in my opinion. Parameters are indexed by name and when calling completed-part of controller action those parameters are given to this method as method arguments. This is how I add results to AsyncManager parameters collection.
if (AsyncManager.Parameters.ContainsKey("result"))
{
list = (List<string>) AsyncManager.Parameters["result"];
}
else
{
list = new List<string>();
AsyncManager.Parameters["result"] = list;
}
In my case I have only one parameter, named as result and this is how my completed-method looks.
public ActionResult IndexCompleted(List<string> result)
{
return View(result);
}
Asynchronous methods fill the list like shown in previous code fragment. And parameter called result is given to IndexCompleted() method. My job was only to get the values (and of course keep in mind those damn decrements).
Example of asynchronous controller
Here is the source of my asynchronous controller. In the IndexAsync() method I create 100 calls to my dummy web service. With each call I increment pending asynchronous operations counter by one (okay, it is also possible to do before this for loop because Increment() and Decrement() have overloads with count parameter).
HelloWorldCompleted() method is called when asynchronous call is done and data is here. In this method I add new line to result element in AsyncManager property bag. And I decrement the counter because when this method finishes there is one pending asynchronous call fewer then before.
public class HomeController : AsyncController
{
public void IndexAsync()
{
var random = new Random();
for (var i = 0; i < 100; i++)
{
AsyncManager.OutstandingOperations.Increment();
var delay = random.Next(1, 5) * 1000;
var service = new DelayedHelloSoapClient();
service.HelloWorldCompleted += HelloWorldCompleted;
service.HelloWorldAsync(delay);
Debug.WriteLine("Started: " + service.GetHashCode());
}
}
public ActionResult IndexCompleted(List<string> result)
{
return View(result);
}
void HelloWorldCompleted(object sender,
HelloWorldCompletedEventArgs e)
{
var hash = 0;
var service = e.UserState as DelayedHelloSoapClient;
if (service != null)
hash = service.GetHashCode();
List<string> list;
if (AsyncManager.Parameters.ContainsKey("result"))
{
list = (List<string>) AsyncManager.Parameters["result"];
}
else
{
list = new List<string>();
AsyncManager.Parameters["result"] = list;
}
list.Add(e.Result);
Debug.WriteLine("Finished: " + hash + " " + e.Result);
AsyncManager.OutstandingOperations.Decrement();
}
public ActionResult About()
{
return View();
}
}
Also notice that I did nothing to bind AsyncManager.Parameters["result"] to result parameter of IndexCompleted() method. This little thing was automated by ASP.NET MVC framework.
Conclusion
ASP.NET MVC supports asynchronous processing very well. It has thinner interface for that than ASP.NET Forms does and we have to write less code. The only not so good thing is incrementing and decrementing pending asynchronous operations counter. But after 15 minutes you don’t forget these two necessary lines anymore. AsyncManager keeps parameters collection that is way better solution than keeping class variables in controller because class variables used by one asynchronous method are may be not used by other methods that need their own variables in class scope. As a conclusion I want to say that writing this sample I found again some brilliant ideas behind ASP.NET MVC framework.