• 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

  • 相关阅读:
    Tomcat基于MSM+Memcached实现Session共享
    Zabbix简介及安装
    redis简介
    Ansible详解(二)
    Ansible详解(一)
    WAMP3.1.10/Apache 设置站点根目录
    最长回文子串--轻松理解Manacher算法
    一篇文章彻底了解Java垃圾收集(GC)机制
    java内存模型详解
    Java中23种设计模式--超快速入门及举例代码
  • 原文地址:https://www.cnblogs.com/RobbinHan/p/2269340.html
Copyright © 2020-2023  润新知