• 转载深入ASP.NET MVC之四:Filter和Action的执行


    原文地址:深入ASP.NET MVC之四:Filter和Action的执行
    
     
    上文说到根据controller的名字正确的实例化了一个controller对象。回到MVCHandler的BeginProcessRequest方法,可以看到,当得到controller对象之后,首先判断它是不是IAsyncController,如果是则会创建委托用来异步执行。通常情况下,我们都是继承自Controller类,这不是一个IAsyncController,于是会直接执行Controller的Execute方法。Execute方法是在Controller的基类ControllerBase中定义的,这个方法除去一些安全检查,初始化了ControllerContext(包含了ControllerBase和Request的信息),核心是调用了ExecuteCore方法,这在ControllerBase是个抽象方法,在Controller类中有实现:
    protected override void ExecuteCore() {
    PossiblyLoadTempData();
                try {
                    string actionName = RouteData.GetRequiredString("action");
                    if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
                        HandleUnknownAction(actionName);
                    }
                }
                finally {
                    PossiblySaveTempData();
                }
            }
    这个方法比较简单,首先是加载临时数据,这仅在是child action的时候会出现,暂不讨论。接下来就是获取action的名字,然后InvokeAction, 这里的ActionInvoker是一个ControllerActionInvoker类型的对象,我们来看它的InvokeAction方法,
    
    public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) {
                if (controllerContext == null) {
                    throw new ArgumentNullException("controllerContext");
                }
                if (String.IsNullOrEmpty(actionName)) {
                    throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
                }
                ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
                ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
                if (actionDescriptor != null) {
                    FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
                    try {
                        AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
                        if (authContext.Result != null) {
                            // the auth filter signaled that we should let it short-circuit the request
                            InvokeActionResult(controllerContext, authContext.Result);
                        }
                        else {
                            if (controllerContext.Controller.ValidateRequest) {
                                ValidateRequest(controllerContext);
                            }
    
                            IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
                            ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
                            InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
                        }
                    }
                    catch (ThreadAbortException) {
                        // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
                        // the filters don't see this as an error.
                        throw;
                    }
                    catch (Exception ex) {
                        // something blew up, so execute the exception filters
                        ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
                        if (!exceptionContext.ExceptionHandled) {
                            throw;
                        }
                        InvokeActionResult(controllerContext, exceptionContext.Result);
                    }
                    return true;
                }
                // notify controller that no method matched
                return false;
            }
    这是一个非常核心的方法,有很多工作在这里面完成。ASP.NET MVC中有几个以Descriptor结尾的类型,首先获得ControllerDescriptor,这个比较简单,实际返回的是ReflectedControllerDescriptor对象。第二步实际上是调用了ReflectedControllerDescriptor的FindAction方法,获得ActionDescriptor,ActionDescriptor最重要的属性是一个MethodInfo,这就是当前action name对应的Action的方法。FindAction方法内部实际上是调用了ActionMethodSelector的FindActionMethod来获得MethodInfo,可以想象,这个方法将会反射controller的所有方法的名字,然后和action name匹配,实际上,ASP.NET还支持一些额外的功能,主要是: 1.通过ActionNameAttribute属性重命名action的名字;2.支持ActionMethodSelectorAttribute对action方法进行筛选,比如[HttpPost]之类的。下面简单看下ActionMethodSelector的实现,大致分为4步,首先是在构造函数中调用了如下方法反射controller中的所有action方法:
    
    private void PopulateLookupTables() {
                MethodInfo[] allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
                MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethod);
    
                AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute);
                NonAliasedMethods = actionMethods.Except(AliasedMethods).ToLookup(method => method.Name, StringComparer.OrdinalIgnoreCase);
            }
    FindActionMethod方法如下:
    
    public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName) {
                List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName);
                methodsMatchingName.AddRange(NonAliasedMethods[actionName]);
                List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName);
                switch (finalMethods.Count) {
                    case 0:
                        return null;
                    case 1:
                        return finalMethods[0];
                    default:
                        throw CreateAmbiguousMatchException(finalMethods, actionName);
                }
            }
    这个方法是很清晰的,找到重命名之后符合的,本身名字符合的,然后所有的方法判断是否满足ActionMethodSelectorAttribute的条件,最后或者返回匹配的MethodInfo,或者抛出异常,或者返回null。三个步骤的实现并不困难,不再分析下去。
    
    第三步是得到Filter。 FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);实际调用的是:
    
    FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor);
    这里的代码风格和之前的不太一样,特别喜欢用各种委托,读代码有点困难,估计不是同一个人写的。下面的分析都直接给出实际执行的代码。首先看下FilterProvider的构造函数:
    
    static FilterProviders() {
                Providers = new FilterProviderCollection();
                Providers.Add(GlobalFilters.Filters);
                Providers.Add(new FilterAttributeFilterProvider());
                Providers.Add(new ControllerInstanceFilterProvider());
            }
    回忆下ASP.NET给Action加上filter的方法一共有如下几种:
    
    1. 在Application_Start注册全局filter
    
    2. 通过属性给Action方法或者Controller加上filter
    
    3. Controller类本身也实现了IActionFilter等几个接口。通过重写Controller类几个相关方法加上filter。
    这三种方式就对应了三个FilterProvider,这三个Provider的实现都不是很困难,不分析了。到此为止,准备工作都好了,接下来就会执行Filter和Action,ASP.NET的Filter一共有4类:
    image
    
    下面看其源代码的实现,首先就是InvokeAuthorizationFilters:
    
    protected virtual AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor) {
                AuthorizationContext context = new AuthorizationContext(controllerContext, actionDescriptor);
                foreach (IAuthorizationFilter filter in filters) {
                    filter.OnAuthorization(context);
    if (context.Result != null) {
                        break;
                    }
                }
                return context;
            }
    注意到在实现IAuthorizationFilter接口的时候,要表示验证失败,需要在OnAuthorization方法中将参数context的Result设置为ActionResult,表示验证失败后需要显示的页面。接下来如果验证失败就会执行context的Result,如果成功就要执行GetParameterValues获得Action的参数,在这个方法内部会进行Model Binding,这也是ASP.NET的一个重要特性,另文介绍。再接下来会分别执行InvokeActionMethodWithFilters和InvokeActionResultWithFilters,这两个方法的结构是类似的,只是一个是执行Action方法和IActionFilter,一个是执行ActionResult和IResultFilter。以InvokeActionMethodWithFilters为例分析下:
    
    protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) {
                ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
                Func<ActionExecutedContext> continuation = () =>
                    new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) {
                        Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters)
                    };
    
                // need to reverse the filter list because the continuations are built up backward
                Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation,
                    (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));
                return thunk();
            }
    这段代码有点函数式的风格,不熟悉这种风格的人看起来有点难以理解。 用函数式编程语言的话来说,这里的Aggregate其实就是foldr,
    
    foldr::(a->b->b)->b->[a]->b
    
    foldr 接受一个函数作为第一个参数,这个函数的参数有两个,类型为a,b,返回类型为b,第二个参数是类型b,作为起始值,第三个参数是一个类型为a的数组,foldr的功能是依次将数组中的a 和上次调用第一个参数函数(f )的返回值作为f的两个参数进行调用,第一次调用f的时候用起始值。对于C#来说,用面向对象的方式表示,是作为IEnummerable的一个扩展方法实现的,由于C# 不能直接将函数作为函数的参数传入,所以传入的是委托。说起来比较拗口,看一个例子:
    
    static void AggTest()
            {
                int[] data = { 1, 2, 3, 4 };
                var res = data.Aggregate("String", (str, val) => str + val.ToString());
                Console.WriteLine(res);
            }
    最后输出的结果是String1234. 回到InvokeActionMethodWithFilters的实现上来,这里对应的类型a是IActionFilter,类型b是Func<ActionExecutedContext>,初始值是continuation。假设我们有3个filter,[f1,f2,f3],我们来看下thunk最终是什么,
    
    第一次: next=continue, filter=f1,  返回值 ()=>InvokeActionMethodFilter(f1, preContext, continue)
    
    第二次:next=()=>InvokeActionMethodFilter(f1, preContext, continue), filter=f2
    
                   返回值:()=>InvokeActionMethodFilter(f2, preContext,()=> InvokeActionMethodFilter(f1, preContext, continue)),
    
    最终: thunk= ()=>InvokeActionMethodFilter(f3,preContext,()=>InvokeActionMethodFilter(f2, preContext, ()=>InvokeActionMethodFilter(f1, preContext, continue)));
    
    直到 return thunk()之前,所有真正的代码都没有执行,关键是构建好了thunk这个委托,把thunk展开成上面的样子,应该比较清楚真正的调用顺序什么样的了。这里花了比较多的笔墨介绍了如何通过Aggregate方法构造调用链,这里有一篇文章专门介绍了这个,也可以参考下。想象下,如果filter的功能就是先遍历调用f的Executing方法,然后调用Action方法,最后再依次调用f的Executed方法,那么完全可以用迭代来实现,大可不必如此抽象复杂,关键是ASP.NET MVC对于filter中异常的处理还有一些特殊之处,看下InvokeActionMethodFilter的实现:
    
    internal static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func<ActionExecutedContext> continuation) {
                filter.OnActionExecuting(preContext);
                if (preContext.Result != null) {
                    return new ActionExecutedContext(preContext, preContext.ActionDescriptor, true /* canceled */, null /* exception */) {
                        Result = preContext.Result
                    };
                }
    
                bool wasError = false;
                ActionExecutedContext postContext = null;
                try {
                    postContext = continuation();
                }
                catch (ThreadAbortException) {
                    // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
                    // the filters don't see this as an error.
                    postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */);
                    filter.OnActionExecuted(postContext);
                    throw;
                }
                catch (Exception ex) {
                    wasError = true;
                    postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
                    filter.OnActionExecuted(postContext);
                    if (!postContext.ExceptionHandled) {
                        throw;
                    }
                }
                if (!wasError) {
                    filter.OnActionExecuted(postContext);
                }
                return postContext;
            }
    代码有点长,首先就是触发了filter的OnActionExecuting方法,这是方法的核心。接下来的重点是 postContext = continuation(); 最后是OnActionExecuted方法,结合上面的展开式,我们可以知道真正的调用顺序将是:
    
    f3.Executing->f2.Executing->f1.Exectuing->InvokeActionMethod->f1.Executed->f2->Executed->f3.Executed.
    
    那么,源代码中的注释  // need to reverse the filter list because the continuations are built up backward  的意思也很明了了。需要将filter倒序排一下之后才是正确的执行顺序。
    
    还有一类filter是当异常发生的时候触发的。在InvokeAction方法中可以看到触发它的代码放在一个catch块中。IExceptionFilter的触发流程比较简单,不多做解释了。唯一需要注意的是ExceptionHandled属性设置为true的时候就不会抛出异常了,这个属性在各种context下面都有,他们是的效果是一样的。比如在OnActionExecuted方法中也可以将他设置为true,同样不会抛出异常。这些都比较简单,不再分析其源代码,这篇文章比较详细的介绍了filter流程中出现异常之后的执行顺序。
    
    最后说下Action Method的执行,前面我们已经得到了methodInfo,和通过data binding获得了参数,调用Action Method应该是万事俱备了。asp.net mvc这边的处理还是比较复杂的,ReflectedActionDescriptor会去调用ActionMethodDispatcher的Execute方法,这个方法如下:
    
    public object Execute(ControllerBase controller, object[] parameters) {            
                  return _executor(controller, parameters);
            }
    此处的_executor是
    
    delegate object ActionExecutor(ControllerBase controller, object[] parameters);
    _exectuor被赋值是通过一个方法,利用Expression拼出方法体、参数,代码在(ActionMethodDispatcher.cs):
    
    static ActionExecutor GetExecutor(MethodInfo methodInfo)
    此处就不贴出了,比较复杂。这里让我比较费解的是,既然MethodInfo和parameters都有了,直接用反射就可以了,为什么还要如此复杂,我将上面的Execute方法改为:
    
    public object Execute(ControllerBase controller, object[] parameters) {   
                return MethodInfo.Invoke(controller, parameters);
               //return _executor(controller, parameters);
            }
    运行结果是完全一样的。我相信mvc源代码如此实现一定有其考虑,这个需要继续研究。
    
    最后附上一张函数调用图,以便理解,仅供参考。图片较大,点击可看原图。
    
    

    BeginProcessRequest

    Top
    收藏
    关注
    评论
  • 相关阅读:
    9-10-堆 Windows消息队列(25 分)
    9-7 二叉搜索树的结构(30 分)
    9-4 笛卡尔树(25 分)
    9-3 搜索树判断(25 分)
    7-9 堆中的路径(25 分)
    个人总结
    软工网络15个人作业4——alpha阶段个人总结
    软件工程网络15个人作业3——案例分析(201521123029 郑佳明)
    软件工程15 结对编程作业
    软件工程网络15个人阅读作业2(201521123029 郑佳明)
  • 原文地址:https://www.cnblogs.com/automation/p/2838537.html
Copyright © 2020-2023  润新知