[转载]ASP.NET MVC 源码分析——巧用Aggregate和委托构造递归链 – P_Chou Go deep and Keep learning – 博客园.
在研究ASP.NET MVC2中IActionFilter和IResultFilter的执行逻辑的时候看到下面四个方法(你可以在ControllerActionInvoker.cs中找到它们)
- InvokeActionMethodWithFilters
- InvokeActionMethodFilter
- InvokeActionResultWithFilters
- InvokeActionResultFilter
事实上前两个和后两个的实现和逻辑几乎差不多,只不过一组处理IActionFilter,一组处理IResultFilter,这里我只讨论一下前两组。
在阅读本文前,建议您搞清楚.NET的Func<>委托、集合的Aggregate方法、匿名方法等相关知识。
先来看看InvokeActionResultWithFilters,它的含义是执行包含IActionFilter过滤器的Action:
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(); }
我不打算回溯到它的调用点讨论,因此这里需要解释一下这个函数被调用的背景和各个传入参数的意义。在函数调用之前,所有的准备工作已经做好,这些准备工作包括:定位Action;针对这个Action相关的所有IActionFilter和IResultFilter已经反射出来,并保存下来了;通过了IAuthorizationFilter验证;Action参数已经准备妥当。
- ControllerContext controllerContext:不用多说,整个Controller和Action的执行过程中封装的上下文参数,本文不涉及;
- IList<IActionFilter> filters:该Action相关的IActionFilter接口集合,本文将讨论到。需要注意的是这个集合里面将包含这个Action所在的Controller自身实现的IActionFilter,所以这个集合里面至少有一个IActionFilter;
- ActionDescriptor actionDescriptor:封装了该action的描述,通过其Execute来真正执行Action,本文不涉及;
- IDictionary<string, object> parameters:提供Action所需的参数,本文不涉及。
逐行解读InvokeActionResultWithFilters
ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
我们知道一个Aciton有不止一个IActionFilter,他们依照某种顺序执行,这里的preContext对象相当于依次接受这些IActionFilter的OnActionExecuting的结果。
Func<ActionExecutedContext> continuation = () => new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) { Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters) };
Func<ActionExecutedContext> continuation是个委托,指向返回ActionExecutedContext的函数,后半部分是个匿名方法
() => new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) { Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters) };
表示没有传入参数,返回一个ActionExecutedContext并且,其中的Result为真正的action执行后的结果。当continuation()执行时将执行这个匿名方法。
至此,continuation是一个委托对象,功能就是真正执行action。
Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation, (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));
又是一个Func<ActionExecutedContext>的委托 thunk,其定义为一个聚合(Aggregate)方法的结果(请记住,这里的Aggregate返回的是个委托对象),在聚合前先要反转一下IList<IActionFilter> filters,Aggregate第二个参数的含义是:将filters里面的每一个filter和前一次的聚合操作的结果作为参数,返回一个Func<ActionExecutedContext>的委托对象,它是:
() => InvokeActionMethodFilter(filter, preContext, next)
这个委托对象本身是执行InvokeActionMethodFilter,这个方法需要当前的filter和上一个聚合操作的结果(一个Func<ActionExecutedContext>的委托对象)作为参数。而这整个聚合操作的初始Func<ActionExecutedContext>委托对象是上面实例化的continuation。
我实在找不出其他的辞藻能够描述这段代码的含义了,请参考下图
途中左侧橘色表示filters集合中的IActionFilter,红色箭头表示聚合操作,每次聚合操作的结果是个 Func<ActionExecutedContext>委托对象,最终聚合结果赋给thunk。这个聚合对象都将invoke一个叫 InvokeAtionMethodFilter的函数,并且其中一个参数指向了上一个聚合对象的结果(就是代码中的next)。
注意,这里聚合的结果仅仅返回一个委托,其中的方法还没有执行,真正开始执行是这样的:
return thunk();
这将导致最后返回的委托对象所指向的函数被执行。
现在是时候看看InvokeAtionMethodFilter函数到底是怎么执行的了,我只先贴出上半部分:
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(); } …
首先调用filter的OnActionExecuting,然后判断preContext.Result是否被修改,假设现在为null,那么就表示这个filter通过了!接着在try中执行continuation。还记得continuation是什么吗?是指向上一个Func<ActionExecutedContext>委托对象的引用,在这里调用意味着递归调用上一个InvokeActionMethodFilter!如果这个递归链在执行过程中能通过每个filter的层层把关的话,调用到最后将是真正的执行action!
现在知道Aggregate方法实际上构造了一个递归调用链;现在明白为什么上一个聚合对象的结果要命名为next和continuation了: 这是相对于执行时而言当然是“下一个”或者是“继续”的意思啦;我不得不佩服微软的工程师巧妙的用几行代码构造了一个递归链。下面我们来看看下半部分 catch块的代码:
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; }
这里的catch包含两部分,第一个catch中的注释已经说明了问题。再仔细思考第二个catch块你就会明白在深入理解ASP.NET MVC(8)中提到的:为什么当前action异常会被上一个层次的捕获的原因了,因为这里是个递归调用被包含在try\catch中。
最后谈谈在Aggregate前执行的反转。如果你试着调试这段代码,或者你研究过之前的代码细节,我告诉你在刚进 InvokeActionMethodWithFilters的时候,参数filters的第一个对象是controller自己的filter,你一定 不会感到诧异。试想,如果没有这里的反转,直接聚合的结果将是controller自己的filter最后执行,而MVC的设计就是让 controller自己的filter优先于其他的Attribute。
为了理解这段代码着实花了我不少时间,当我想通的瞬间真是豁然开朗,拍案叫绝!寥寥几笔,便形成了一个优雅干净的递归调用,不仔细看都不能发现递归的痕迹!
劳动果实,转载请注明出处:http://www.cnblogs.com/P_Chou/archive/2010/12/18/asp-net-mvc-src-recursive-using-aggregate-delegate.html