为ASP.NET MVC扩展异步Action功能(上)

开发 后端
异步请求处理是ASP.NET 2.0中引入的高级特性,它依托IO Complete Port,对于提高IO密集型应用程序的吞吐量非常重要。但是目前ASP.NET MVC框架缺少异步Action功能。本文作者给出了不错的扩展:完整,方便,并且非常轻巧——核心逻辑代码只有200行左右,这意味着绝大部分功能将会委托给框架中现成的内容,确保了扩展的稳定,高效并且拥有较好的向后兼容性。

编辑推荐:为ASP.NET MVC扩展异步Action功能(下)

请求处理方式的改变

在制定基本改造策略之前,我们需要了解ASP.NET MVC框架目前的架构及请求处理流程。如下:

◆ 在应用程序启动时(此时还没有接受任何请求),将针对MVC请求的Route策略注册至ASP.NET Routing模块。此时每个Route策略(即Route对象)中的RouteHandler属性为ASP.NET MVC框架中的MvcRouteHandler。

◆ 当ASP.NET Routing模块接收到一个匹配某个Route策略的HTTP请求时,将会调用该Route对象中RouteHandler对象的GetHttpHandler以获取一个HttpHandler,并交由ASP.NET执行。MvcRouteHandler永远将返回一个MvcHandler对象。

◆ MvcHandler在执行时,将取出RouteData中的controller值,并以此构建一个实现了IController接口的控制器对象,并调用IController接口的Execute方法执行该控制器。

◆ 对于一个ASP.NET MVC应用程序来说,大部分控制器将会继承System.Web.Mvc.Controller类型。Controller类将会从RouteData获取action值,并交给实现IActionInvoker接口的对象来执行一个Action。

◆ ……

如果我们要将这个流程改造成异步处理,那么就要让它符合ASP.NET架构中的异步处理方式。ASP.NET架构对于异步请求的处理可以体现在好几种方式上,例如异步页面,异步Http Module等,而最适合目前场合的做法自然是异步Http Handler。为实现一个异步Handler,我们需要让处理请求的Handler实现IHttpAsyncHandler接口,而不是传统的IHttpHandler接口。IHttpAsyncHandler接口中的BeginProcessRequest和EndProcessRequest两个方法构成了.NET中的APM(Aynchronous Programming Model,异步编程模型)模式,可以使用“二段式”的异步调用来处理一个HTTP请求。

您应该已经发现,如果我们要支持异步Action,就必须根据当前的请求信息来确认究竟是执行一个IHttpHandler对象还是IHttpAsyncHandler对象。而在ASP.NET MVC框架在默认情况下是在Http Handler(即MvcHandler对象)内部进行控制器的检查,构造和调用。这为时已晚,我们必须讲这些逻辑提前到Routing过程中才行。幸运的是,ASP.NET Routing所支持的IRouteHandler就像是ASP.NET中的IHttpHandlerFactory,可以根据情况生成不同的Handler来执行。因此,我们只要构建一个新的IRouteHandler类型即可。于是就诞生了AsyncMvcRouteHandler——可以想象的出,其中的部分代码与框架中的MvcHandler相同,因为在一定程度上我们的确只是把原本在MvcHandler里做的事情给提前了:

public class AsyncMvcRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        string controllerName = requestContext.RouteData.GetRequiredString("controller");

        var factory = ControllerBuilder.Current.GetControllerFactory();
        var controller = factory.CreateController(requestContext, controllerName);
        if (controller == null)
        {
            throw new InvalidOperationException(...);
        }

        var coreController = controller as Controller;
        if (coreController == null)
        {
            return new SyncMvcHandler(controller, factory, requestContext);
        }
        else
        {

            string actionName = requestContext.RouteData.GetRequiredString("action");
            return IsAsyncAction(coreController, actionName, requestContext) ?
                (IHttpHandler)new AsyncMvcHandler(coreController, factory, requestContext) :
                (IHttpHandler)new SyncMvcHandler(controller, factory, requestContext);
        }
    }

    internal static bool IsAsyncAction(
        Controller controller, string actionName, RequestContext requestContext)
    {
        ...
    }
}

在GetHttpHandler方法中,我们先从RouteData的controller字段中获取控制器的名字,并通过注册在ControllerBuilder上的Factory来创建一个实现了IController接口的控制器对象。由于我们需要使用Controller类中包含的ActionInvoker来辅助检测Action的异步需求,因此我们会设法将其转化为Controller类型。如果转换成功,就会取出RouteData中的action字段的值,并通过IsAsyncAction方法来确认当前Action是否应该异步执行。如果是,则返回一个实现了IHttpAsyncHandler的AsyncMvcHandler对象,否则就返回一个实现IHttpHandler的SyncMvcHandler对象。

至于AsyncMvcRouteHandler的使用,只需在MapRoute时将Route Handler重新设置一下即可:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 routes.MapRoute(
        "Default",                                              // Route name
        "{controller}/{action}/{id}",                           // URL with parameters
        new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
    ).RouteHandler = new AsyncMvcRouteHandler();
}

#p#

检查是否为异步Action

从上面的代码中我们已经形成了一个约定:如果要执行一个异步Action,那么控制器对象必须为Controller类型。这个约定的目的是为了使用Controller类中包含的IActionInvoker——确切地说,是ControllerActionInvoker类型里的功能。因此,另一个约定便是Controller的ActionInvoker对象必须返回一个ControllerActionInvoker的实例。

ControllerActionInvoker中有一些辅助方法,能够返回对于一个Controller或Action的描述对象。从一个Action描述对象中我们可以获取关于这个Action的各种信息,而它是否被标记了AsyncActionAttribute,就是我们判断这个Action是否应该被异步执行的依据。如下:

private static object s_methodInvokerMutex = new object();
private static MethodInvoker s_controllerDescriptorGetter;

internal static bool IsAsyncAction(
    Controller controller, string actionName, RequestContext requestContext)
{
    var actionInvoker = controller.ActionInvoker as ControllerActionInvoker;
    if (actionInvoker == null) return false;

    if (s_controllerDescriptorGetter == null)
    {
        lock (s_methodInvokerMutex)
        {
            if (s_controllerDescriptorGetter == null)
            {
                BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
                MethodInfo method = typeof(ControllerActionInvoker).GetMethod(
                    "GetControllerDescriptor", bindingFlags);
                s_controllerDescriptorGetter = new MethodInvoker(method);
            }
        }
    }

    var controllerContext = new ControllerContext(requestContext, controller);
    var controllerDescriptor = (ControllerDescriptor)s_controllerDescriptorGetter.Invoke(
        actionInvoker, controllerContext);
    var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
    return actionDescriptor == null ? false :
        actionDescriptor.GetCustomAttributes(typeof(AsyncActionAttribute), false).Any();
}

ControllerActionInvoker类型中有个protected方法GetControllerDescriptor,它接受一个ControllerContext类型的参数,并返回一个ControllerDescriptor对象来描述当前控制器,而从该描述对象中可以通过FindAction方法获得一个ActionDescriptor对象来描述即将执行的Action。如果是一个不存在的Action,那么就返回false,***就通过SyncMvcHandler对象来执行默认的行为。当且仅当该Action上拥有AsyncActionAttribute标记时,才说明它应该被异步执行,返回true。此外,这段代码中用到了MethodInvoker,这是一个辅助类,它来源于Fast Reflection Library,它实现了反射调用功能,但是它的性能十分接近于方法的直接调用,我在这篇文章中详细描述了这个项目的功能和使用。

这段代码便涉及到ASP.NET MVC RC版本在Beta版本基础上的改进。在原先的ControllerActionInvoker类中只有获取Action方法的MethodInfo,而没有RC中各描述对象这样的抽象类型。从目前的设计上来看,我们使用的都是基于反射的抽象描述类型的子类。例如默认情况下,我们通过ActionDescriptor抽象类型访问的实际上是ReflectedActionDescriptor类型的实例。这是一个很有用的改进,由于我们通过描述对象进行抽象,于是我们就可以:

◆ 使用不同的实现方式来描述各对象,默认情况下是使用基于反射(也就是“约定”)的实现,如果需要的话我们也可以使用基于配置文件的方式替换现有实现。

◆ 使用特定对象的描述方式可以不拘泥于内部细节,例如一个异步的Action可能就由两个方法组成。

◆ 有了特定的描述对象,也方便添加额外的属性,例如该Action是否应该异步执行,是否应该禁用Session State等等。

◆ ……

【编辑推荐】

  1. 为ASP.NET MVC扩展异步Action功能(下)
  2. 详解ASP.NET MVC的请求生命周期
  3. ASP.NET MVC实例和新RC版本中视图方面的改进
  4. ASP.NET MVC框架视频教程
责任编辑:杨鹏飞 来源: 博客园
相关推荐

2009-02-17 09:22:14

ActionMVCASP.NET

2009-07-22 10:13:31

异步ActionASP.NET MVC

2009-03-06 10:28:30

MVCASP.NET异步Action

2009-07-22 09:11:02

Action方法ASP.NET MVC

2009-07-22 16:02:39

ASP.NET MVCPagedList

2009-07-20 12:59:53

ASP.NET MVCASP.NET框架的功

2009-07-24 13:20:44

MVC框架ASP.NET

2009-07-31 12:43:59

ASP.NET MVC

2009-07-22 18:02:27

论坛应用程序ASP.NET MVC

2010-04-06 15:20:56

ASP.NET MVC

2010-02-05 08:32:32

ASP.NET MVC

2017-03-06 11:13:57

ASP.NETCoreMVC

2009-07-23 14:31:20

ASP.NET MVC

2009-07-20 10:53:59

ASP.NET MVC

2009-07-23 15:44:39

ASP.NET MVC

2009-07-22 10:09:59

ASP.NET MVC

2009-07-22 13:24:24

ASP.NET MVC

2009-07-28 16:40:11

ASP.NET异步页面

2015-06-17 17:01:48

ASP.NET

2021-03-08 07:32:05

Actionweb框架
点赞
收藏

51CTO技术栈公众号