Gunnar Peipman's ASP.NET blog

ASP.NET, C#, SharePoint, SQL Server and general software development topics.

Sponsors

News

 
 
 
DZone MVB

Links

Social

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 Experiments.WebAppThreading.2.zip
VS2010 solution | 113KB
Source code @ GitHub 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.

Comments

DotNetShoutout said:

Thank you for submitting this cool story - Trackback from DotNetShoutout

# September 24, 2010 8:57 PM

DotNetKicks.com said:

You've been kicked (a good thing) - Trackback from DotNetKicks.com

# September 24, 2010 9:01 PM

progg.ru said:

Thank you for submitting this cool story - Trackback from progg.ru

# September 24, 2010 9:03 PM

Phil Bolduc said:

To me it appears the following code has a race condition:

if (AsyncManager.Parameters.ContainsKey("result"))

{

list = (List<string>) AsyncManager.Parameters["result"];

}

else

{

list = new List<string>();

AsyncManager.Parameters["result"] = list;        

}

Two async operations should complete near the same time, both check 'Parameters.ContainsKey("result")' and return false. Both would create a new list and set it in the Parameters collection. This kind of bug would be silently swallowed.  You could change the code to

AsyncManager.Parameters.Add("result", list);

and if there were a race condition, your controller would throw ArgumentException due to duplicate key.

Better would be to check if the action completed synchronously or not. If did not complete synchronously, you should synchronize access through the AsyncManager.Sync(Action action) method.

See the following reference msdn.microsoft.com/.../ee728598.aspx for more information.

"If an asynchronous action method calls a service that exposes methods by using the BeginMethod/EndMethod pattern, the callback method (that is, the method that is passed as the asynchronous callback parameter to the Begin method) might execute on a thread that is not under the control of ASP.NET. In that case, HttpContext.Current will be null, and the application might experience race conditions when it accesses members of the AsyncManager class such as Parameters. To make sure that you have access to the HttpContext.Current instance and to avoid the race condition, you can restore HttpContext.Current by calling Sync() from the callback method."

"The callback that you pass to the Begin method might be called using a thread that is under the control of ASP.NET. Therefore, you must check for this condition before you call Sync(). If the operation completed synchronously (that is, if CompletedSynchronously is true), the callback is executing on the original thread and you do not have to call Sync(). If the operation completed asynchronously (that is, CompletedSynchronously is false), the callback is executing on a thread pool or I/O completion port thread and you must call Sync()."

# September 26, 2010 4:35 PM

Mattia Baldinger's Blog said:

.NET Links of the Week #38

# September 27, 2010 12:47 PM

.NET Links of the Week #38 said:

Pingback from  .NET Links of the Week #38

# September 27, 2010 12:47 PM

Chris Love said:

So does the page render and the user have the ability to do things or does it just sit and spin while you are waiting on background service calls to complete? If it just sits and spins it just seems like a much wiser choice to kick off those lethargic service calls either using AJAX to call directly or wrapping them behind another controller action to map the request.

# September 29, 2010 11:21 AM

DigiMortal said:

Nice to hear about you again, Chris! :)

Asynchronous stuff described here is purely server side topic. How you make AJAX requests from client to server is really up to you to decide. It is possible to call web services directly from client side but it is not always option.

There may be web services that you pay for and you cannot show your authentication information to client. Also you may have calls to different services and you want to form one result from answers you got. Third thing is you may want these requests to perform by your web application because this way your clients behind slow connections does not have to wait long.

# September 29, 2010 1:28 PM

software web app development said:

What is classic synchronous controller? when we implement?

# October 9, 2010 5:55 AM

DigiMortal said:

All controllers that are not marked as asynchronous are synchronous. :)

# October 9, 2010 3:10 PM

Konstantin said:

For some reason no one is describing situation, when asynchronous action would throw exception. It's quite difficult to get back Exception data in "Completed" method.

# November 8, 2010 11:14 AM

neverwhereis said:

Is there a way to make the same call from client side as a validation check mechanism based on the user's input. For example, validation to check a user's subscription code from a web service without doing a POST to the controller.

# February 7, 2012 4:42 PM