views:

1660

answers:

3

In this question & answer, I found one way to make ASP.NET MVC support asynchronous processing. However, I cannot make it work.

Basically, the idea is to create a new implementation of IRouteHandler which has only one method GetHttpHandler. The GetHttpHandler method should return a IHttpAsyncHandler implementation instead of just IHttpHandler, because IHttpAsyncHandler has Begin/EndXXXX pattern API.

public class AsyncMvcRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new AsyncMvcHandler(requestContext);
    }

    class AsyncMvcHandler : IHttpAsyncHandler, IRequiresSessionState
    {
        public AsyncMvcHandler(RequestContext context)
        {
        }

        // IHttpHandler members
        public bool IsReusable { get { return false; } }
        public void ProcessRequest(HttpContext httpContext) { throw new NotImplementedException(); }

        // IHttpAsyncHandler members
        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            throw new NotImplementedException();
        }

        public void EndProcessRequest(IAsyncResult result)
        {
            throw new NotImplementedException();
        }
    }
}

Then, in the RegisterRoutes method of file Global.asax.cs, register this class AsyncMvcRouteHandler. public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.Add(new Route("{controller}/{action}/{id}", new AsyncMvcRouteHandler())
        {
            Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }),
        });
    }

I set breakpoint at ProcessRequest, BeginProcessRequest and EndProcessRequest. Only ProcessRequest is executed. In another word, even though AsyncMvcHandler implements IHttpAsyncHandler. ASP.NET MVC doesn't know that and just handle it as an IHttpHandler implementation.

How to make ASP.NET MVC treat AsyncMvcHandler as IHttpAsyncHandler so we can have asynchronous page processing?

A: 

I have tried to do this in the past, I manage to either get the view to render and then all the async tasks would finish. Or the async tasks to finish but the view would not render.

I created a RouteCollectionExtensions based on the original MVC code. In my AsyncMvcHandler, I had an empty method (no exception) for ProcessMethod.

MrJavaGuy
Hi MrJavaGuy,When I leave ProcessRequest empty, it just doesn't do any rendering on returned web page. How do you do to make asynchronous BeginProcessRequest/EndProcessRequest get invoked?
Morgan Cheng
Do to an NDA, I can only give a hint, look at IActionInvoker.
MrJavaGuy
+1  A: 

After hours of hassle with the code, I found out the issue.

In my Visual Studio 2008, when I press Ctrl+F5, the Application Development Server is launched and IE is popped up to access "http://localhost:3573/". In this case, the sync API ProcessRequest is invoked. The stack trace is like this.

MyMvcApplication.DLL!MyMvcApplication.AsyncMvcRouteHandler.AsyncMvcHandler.ProcessRequest(System.Web.HttpContext httpContext = {System.Web.HttpContext}) Line 59 C# System.Web.Mvc.dll!System.Web.Mvc.MvcHttpHandler.VerifyAndProcessRequest(System.Web.IHttpHandler httpHandler, System.Web.HttpContextBase httpContext) + 0x19 bytes
System.Web.Routing.dll!System.Web.Routing.UrlRoutingHandler.ProcessRequest(System.Web.HttpContextBase httpContext) + 0x66 bytes
System.Web.Routing.dll!System.Web.Routing.UrlRoutingHandler.ProcessRequest(System.Web.HttpContext httpContext) + 0x28 bytes
System.Web.Routing.dll!System.Web.Routing.UrlRoutingHandler.System.Web.IHttpHandler.ProcessRequest(System.Web.HttpContext context) + 0x8 bytes
MyMvcApplication.DLL!MyMvcApplication._Default.Page_Load(object sender = {ASP.default_aspx}, System.EventArgs e = {System.EventArgs}) Line 13 + 0x1a bytes C#

However, when I change the URL in IE to be "http://localhost:3573/whatever.mvc", it hits the BeginProcessRequest. The stack trace is like this.

MyMvcApplication.DLL!MyMvcApplication.AsyncMvcRouteHandler.AsyncMvcHandler.BeginProcessRequest(System.Web.HttpContext context = {System.Web.HttpContext}, System.AsyncCallback cb = {Method = {Void OnAsyncHandlerCompletion(System.IAsyncResult)}}, object extraData = null) Line 66 C# System.Web.dll!System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() + 0x249 bytes System.Web.dll!System.Web.HttpApplication.ExecuteStep(System.Web.HttpApplication.IExecutionStep step = {System.Web.HttpApplication.CallHandlerExecutionStep}, ref bool completedSynchronously = true) + 0x9c bytes
System.Web.dll!System.Web.HttpApplication.ApplicationStepManager.ResumeSteps(System.Exception error) + 0x133 bytes
System.Web.dll!System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext context, System.AsyncCallback cb, object extraData) + 0x7c bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest wr = {Microsoft.VisualStudio.WebHost.Request}) + 0x17c bytes System.Web.dll!System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest wr) + 0x63 bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequest(System.Web.HttpWorkerRequest wr) + 0x47 bytes
WebDev.WebHost.dll!Microsoft.VisualStudio.WebHost.Request.Process() + 0xf1 bytes WebDev.WebHost.dll!Microsoft.VisualStudio.WebHost.Host.ProcessRequest(Microsoft.VisualStudio.WebHost.Connection conn) + 0x4e bytes

It seems that only url with ".mvc" suffix can make asynchronous API invoked.

Morgan Cheng
+2  A: 

I had the same issue, however I found that it was because my catch all route handler:

routes.MapRoute(
    "Default",                                                  
    "{controller}/{action}",                           
    new { controller = "Home", action = "Index" }  
);

Was picking up the request, not the custom route I added that dealt with the async route handler. Perhaps by using the .mvc in your custom route defintion you created a distinction so that it was used rather than the synchronous catch-all.

Brehtt