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

来源:未知 责任编辑:智问网络 发表时间:2013-10-07 01:00 点击:

 

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

Controller.cs

 

 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

 

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

 

 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方法

 

 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:

 

 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的调用.

 


 

摘自 十一月的雨

    发表评论
    请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
    用户名: 验证码:点击我更换图片
    最新评论 更多>>

    推荐热点

    • 浅析.NET下XML数据访问新机制
    • asp.net 面试+笔试题目第1/2页
    • C# 邮件地址是否合法的验证
    • C#高级编程:数据库连接[1]
    • asp.net 设置GridView的选中行的实现代码
    • 经典C++程序1
    • IIS 自动回收导致后台定时器失效的问题解决
    • ASP.NET&#160;GridView列表代码示例
    • 微软ASP.NET站点部署指南(3):使用Web.Config文件的Transforma
    网站首页 - 友情链接 - 网站地图 - TAG标签 - RSS订阅 - 内容搜索
    Copyright © 2008-2015 计算机技术学习交流网. 版权所有

    豫ICP备11007008号-1