Monday, February 6, 2012

ASP.NET MVC 3 - Accessing Session inside Task of AsyncController

Before getting into the core of this post, let us have a quick look at Async controller. The base thing that we need to understand is that, Async controllers are implemented to achieve the efficient service of the incoming requests rather than faster processing of individual request. Scenarios like the case of having a network call which consumes some time might block the thread (that is processing the current request) from processing other requests. For understanding the need for using the AsyncController, there exists several articles over the web. One such nicely explained article is here.

In order to implement the Async controller, we need to do the following.
Derive the controller from AsyncController. 
Specify an <ActionName>Async and <ActionName>Completed methods for each Actions, where <ActionName>Async is of return type void and the relevant <ActionName>Completed returns the ActionResult.
Usually the network calls will be made in the <ActionName>Async method and the resultant is passed over to the <ActionName>Completed method as arguments.

Above specified info are fine enough to implement the Async controller. Apart from that, we also need to think of effectively handling the network calls and other time consuming items inside the <ActionName>Async method. In real case, usually we'll call the method in Model / ViewModel which then initiates the network call,  call to the Service Locator or Service Agent. For achieving this, we can alter the Model / ViewModel to expose the relevant methods async mode. Instead we can also seek help from the .Net built in library named Task Parallel Library (TPL). Instead of explaining further, I directly dive into the code sample from which you can easily understand the base.

Snippet 1:

public void IndexAsync(int id)
{
    AsyncManager.OutstandingOperations.Increment();

    Task.Factory.StartNew(() => {
        InfoViewModel moreViewModel = new InfoViewModel();
        InfoViewModel.MoreModel moreModel = moreViewModel.GetDetails(id);

        AsyncManager.Parameters["moreModel"] = moreModel;
        AsyncManager.OutstandingOperations.Decrement();
    });
    
}

public ActionResult IndexCompleted(InfoViewModel.MoreModel moreModel)
{
    return View("Info", moreModel);
}

The AsyncManager related operations specified in the above code helps us to achieve handling of  aysnc operations effectively. For example, the Increment(), Decrement() methods and Parameters property helps the controller to identify, when to make the call to relevant <ActionName>Completed method with specific parameters. As you guess, the Task.Factory.StartNew(Action) will be executed in a separate thread and in the mean time, statements following it got executed and waits until the AsyncManager.OutstandingOperations becomes zero after which <ActionName>Completed gets called.

OK, now comes the core of the post. Think of a situation where we need to use access the Session values inside the method of Model / ViewModel which is initiated from Task.Factory.StartNew(Action).  Since the Task is executed in a separate thread, that thread won't hold the HttpContext that is System.Web.HttpContext.Current will be null. To make it work, we can assign the System.Web.HttpContext.Current with the HttpContext got from ControllerContext as follows.

Snippet 2:

System.Web.HttpContext.Current = ControllerContext.HttpContext.ApplicationInstance.Context;


Please note that the above specified fix is just a workaround. For such scenarios, we need to alter the method in the Model in such a way to receive the value retrieved from session as one of the parameter. 

As we might use this repeatedly, we can create a base class and make use it in places wherever required.

Snippet 3: (BaseAsyncController) 

public class BaseAsyncController : AsyncController
{
    protected void StartContextEnabledTask(Action action)
    {
        Task.Factory.StartNew(() => {
            System.Web.HttpContext.Current = ControllerContext.HttpContext.ApplicationInstance.Context;
            action();
        });
    }
}

Snippet 4: (Full Implementation)

public class InfoController : BaseAsyncController
{
    public void IndexAsync(int id)
    {
        AsyncManager.OutstandingOperations.Increment();
    
        StartContextEnabledTask(() => {
            InfoViewModel moreViewModel = new InfoViewModel();
            InfoViewModel.MoreModel moreModel = moreViewModel.GetDetails(id);
    
            AsyncManager.Parameters["moreModel"] = moreModel;
            AsyncManager.OutstandingOperations.Decrement();
        });
        
    }
    
    public ActionResult IndexCompleted(InfoViewModel.MoreModel moreModel)
    {
        return View("Info", moreModel);
    }    
}

References:
http://www.aaronstannard.com/post/2011/01/06/asynchonrous-controllers-ASPNET-mvc.aspx
http://msdn.microsoft.com/en-us/library/dd537609.aspx
http://craigcav.wordpress.com/2010/12/23/asynchronous-mvc-using-the-task-parallel-library/

No comments:

Post a Comment

Creative Commons License
This work by Tito is licensed under a Creative Commons Attribution 3.0 Unported License.