Asp.net MVC源码分析Action Filter的链式调用

上一篇中我们介绍了asp.net MVC 的Filter的种类,以及调用的时点.今天我们来看一下ActionFilter/ResultFilter 调用的细节以及源码中令人叫绝的代码实现.首先我们看到在Contoller这个类中已经实现了IActionFilter/IResultFilter,并且它们的接口实现是调用两个虚函数来实现的,这就为我们提供了便利,可以在我们的Controller中重写这些虚函数来截获并实现我们自己的逻辑.

Controller.cs

View Code
 1   protected virtual void OnActionExecuting(ActionExecutingContext filterContext) {
2 }
3
4 protected virtual void OnActionExecuted(ActionExecutedContext filterContext) {
5 }
6
7 protected virtual void OnAuthorization(AuthorizationContext filterContext) {
8 }
9
10 protected virtual void OnException(ExceptionContext filterContext) {
11 }
12
13 protected virtual void OnResultExecuted(ResultExecutedContext filterContext) {
14 }
15
16 protected virtual void OnResultExecuting(ResultExecutingContext filterContext) {
17 }
18
19
20 #region IActionFilter Members
21 void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext) {
22 OnActionExecuting(filterContext);
23 }
24
25 void IActionFilter.OnActionExecuted(ActionExecutedContext filterContext) {
26 OnActionExecuted(filterContext);
27 }
28 #endregion
29
30 #region IResultFilter Members
31 void IResultFilter.OnResultExecuting(ResultExecutingContext filterContext) {
32 OnResultExecuting(filterContext);
33 }
34
35 void IResultFilter.OnResultExecuted(ResultExecutedContext filterContext) {
36 OnResultExecuted(filterContext);
37 }
38 #endregion

但是我们如何来获取这个由Controller类实现的Filter接口呢?请看ControllerInstanceFilterProvider,这个已经默认注册的Filter Provider可以得到这些Filter的接口实现.

ControllerInstanceFilterProvider.cs

View Code
1  public class ControllerInstanceFilterProvider : IFilterProvider {
2 public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
3 if (controllerContext.Controller != null) {
4 // Use FilterScope.First and Order of Int32.MinValue to ensure controller instance methods always run first
5 yield return new Filter(controllerContext.Controller, FilterScope.First, Int32.MinValue);
6 }
7 }
8 }

---------------------------------------------------------------------------------------------

但是请大家考虑一个问题,如果我们在Action 上也同时标记了Action Filter的时候会产生一次调用需要调用两个或多个Action filter的时候MVC 怎么处理这样的情况呢? 请看在ControllerActionInvoker.InvokeAction方法中的InvokeActionMethodWithFilters 方法.  我们今天的主角登场了,嘿嘿.

ControllerActionInvoker.cs

View Code
 1    protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) {
2 ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
3 Func<ActionExecutedContext> continuation = () =>
4 new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) {
5 Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters)
6 };
7
8 // need to reverse the filter list because the continuations are built up backward
9 Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation,
10 (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));
11 return thunk();
12 }

首先我们看到代码中声明了Func<ActionExecutedContext> continuation, 的这样一个委托, 注意这里只是声明还没有调用哦.这个委托是用来调用Action方法的. 接下来又声明了 Func<ActionExecutedContext> thunk 这样一个委托, 这个委托的内容是用filters.Aggregate 方法来合并我们的Filter执行链,(这里不理解的话,我们需要看Aggregate 方法的说明). InvokeActionMethodFilter的方法是执行IActionFilter中的接口实现.

InvokeActionMethodFilter方法

View Code
 1  internal static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func<ActionExecutedContext> continuation) {
2 filter.OnActionExecuting(preContext);
3 if (preContext.Result != null) {
4 return new ActionExecutedContext(preContext, preContext.ActionDescriptor, true /* canceled */, null /* exception */) {
5 Result = preContext.Result
6 };
7 }
8
9 bool wasError = false;
10 ActionExecutedContext postContext = null;
11 try {
12 postContext = continuation();
13 }
14 catch (ThreadAbortException) {
15 // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
16 // the filters don't see this as an error.
17 postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */);
18 filter.OnActionExecuted(postContext);
19 throw;
20 }
21 catch (Exception ex) {
22 wasError = true;
23 postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
24 filter.OnActionExecuted(postContext);
25 if (!postContext.ExceptionHandled) {
26 throw;
27 }
28 }
29 if (!wasError) {
30 filter.OnActionExecuted(postContext);
31 }
32 return postContext;
33 }

---------------------------------------------------------------------------------------------
让我们来尝试理解上面的代码,首先请看Aggregate 方法的调用说明以及Demo:

View Code
 1 public static TAccumulate Aggregate<TSource, TAccumulate>(
2 this IEnumerable<TSource> source,
3 TAccumulate seed,
4 Func<TAccumulate, TSource, TAccumulate> func
5 )
6 //demo
7
8 int[] ints = { 4, 8, 8, 3, 9, 0, 7, 8, 2 };
9 // Count the even numbers in the array, using a seed value of 0.
10 int numEven = ints.Aggregate(0, (total, next) =>
11 next % 2 == 0 ? total + 1 : total);
12
13 Console.WriteLine("The number of even integers is: {0}", numEven);
14
15 // This code produces the following output:
16 //
17 // The number of even integers is: 6

我们看到Aggregate 第一个参数是初始值同时也决定了Aggregate 方法的返回类型, 第二个参数是一个委托,委托的第一个参数是上一次调用的返回,这里返回的类型也是一个委托.

---------------------------------------------------------------------------------------------

这时我们再看我们的InvokeActionMethodWithFilters 方法中 thunk 变量的声明:

第一个参数是continuation,它的类型是Func<ActionExecutedContext>,这就决定了我们Aggregate 方法的返回类型也是Func<ActionExecutedContext>, 再看第二个参数也是一个委托(注意这里这个方法也没有调用哦.)它的实现是调用InvokeActionMethodFilter方法. 这个委托会做为第三次迭代(如果有的话)Next参数.

所以最后在InvokeActionMethodFilter 方法的Next参数第一次是continuation变量,第二次是() => InvokeActionMethodFilter(filter, preContext, next));

.如果有第三次或第N次(有三个或N个Filter的情况)会同样第二次的操作, 最后返回给thunk变量的是最后一次生成的调用委托.

---------------------------------------------------------------------------------------------

哎这里好饶哦,对不起,我实在找不到其它词汇了.只能靠最后的一张图来帮助大家理解了.

声明时的伪代码:

var fun1 = (next, filter) => () => InvokeActionMethodFilter(filter, preContext, continuation);
var fun2 = (next, filter) => () => InvokeActionMethodFilter(filter, preContext, fun1)
var fun3 = (next, filter) => () => InvokeActionMethodFilter(filter, preContext, fun2)

最终thunk = fun3;

当thunk()调用的时候它的执行顺序是这样的.

调用时的伪代码:

invoke (next, filter) => () => InvokeActionMethodFilter(filter, preContext, fun3)

invoke  (next, filter) => () => InvokeActionMethodFilter(filter, preContext, fun2)

invoke  (next, filter) => () => InvokeActionMethodFilter(filter, preContext, continuation);

最后ResultFilter的调用和以上的分析是一样的,理解了这个就理解的ResultFilter的调用.

后记;

相关文章:

1.巧用Aggregate和委托构造递归链

转载请注明出处:http://www.cnblogs.com/RobbinHan/archive/2011/12/05/2270707.html 

本文作者: 十一月的雨 http://www.cnblogs.com/RobbinHan

原文地址:https://www.cnblogs.com/RobbinHan/p/2269340.html