• 深入理解ASP.NET MVC 中几种Filter的执行过程
  • 张琳 发表于 2016/3/14 21:46:00 | 分类标签: MVC 路由规则 Filter
  •  一、前言

    之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了。其实阅读源码是个很好的习惯,它不只停留在知道怎么用的阶段,而是让我们知道一系列的为什么,为什么这样设计,为什么这样使用...。很多朋友应该看过《asp.net x 框架揭秘》这本书,确实不错,特别是边看源码边看书,可以有不小的收获。Ok,我不是大神,我只是心血来潮想看一下源码!

    二、几种常见的Filter

    说到mvc里的Filter,自然会想到IAuthorizationFilter,IActionFilter,IResultFilter,IExceptionFilter,搜索一下也都知道怎么用了。其实说白了,这些接口定义了一系列方法,这些方法在请求的不同时机被执行,所谓Filter,就是让我们可以在不同时机进行拦截处理。

    这里还涉及到一个特性:FilterAttribute,例如常用的AuthorizeAttribute就继承了FilterAttribute和实现了IAuthorizationFilter接口。说到Attribute,马上会关联到:运行时、反射、性能。框架会在运行过程中,通过反射获取标记属性,并执行特定的操作;至于性能问题,通常可以通过缓存来优化。

    所以,我们可以做出猜测,以AuthorizeAttribute为例,msdn说它可以进行权限验证,也就是在Action执行前,框架会通过反射获取标记在Action(或Controller)上的FilterAttribute,并执行IAuthorizationFilter定义的OnAuthorization方法,在该方法内部进行权限验证。所以如果我们要在Action执行前做某些判断或处理,可以 1.定义一个Attribute继承FilterAttribute,并实现IActionFilter接口(与IAuthorizationFilter不同的是,这个时候ModelBinding已经完成);2.实现IActionFilter中的方法;3.标记在Action(或Controller上)。ok,下面就通过源码来验证这个过程。

    三、源码分析

    Action的执行是由ActionInvoker负责的,我们直接从这里出发。IActionInvoker定义了ActionInvoker要实现的方法,该接口定义如下:  

    1
    2
    3
    4
    public interface IActionInvoker
    {
        bool InvokeAction(ControllerContext controllerContext, string actionName);
    }

    ControllerActionInvoker  实现了该接口,顾名思义,它用于执行Controller 的 Action方法。它的 InvokeAction如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
    {
        ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
        ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
        if (actionDescriptor != null)
        {
            //标记1
            FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
     
            try
            {
                //标记2
                AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor);
     
                if (authenticationContext.Result != null)
                {
                    InvokeActionResult(controllerContext, authenticationContext.Result);
                }
                else
                {
                    IPrincipal principal = authenticationContext.Principal;
     
                    if (principal != null)
                    {
                        Thread.CurrentPrincipal = principal;
                        HttpContext.Current.User = principal;
                    }
     
                    //标记3
                    AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
                    if (authorizationContext.Result != null)
                    {
                        AuthenticationChallengeContext challengeContext =
                            InvokeAuthenticationFiltersChallenge(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor, authorizationContext.Result);                          
                        InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result);
                    }
                    else
                    {
                        if (controllerContext.Controller.ValidateRequest)
                        {
                            ValidateRequest(controllerContext);
                        }
     
                        //标记4
                        IDictionary<stringobject> parameters = GetParameterValues(controllerContext, actionDescriptor);
                        ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
                        //标记5
                        InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
                    }
                }
            }
            catch (Exception ex)
            {
                //标记6
                ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
                if (!exceptionContext.ExceptionHandled)
                {
                    throw;
                }
                InvokeActionResult(controllerContext, exceptionContext.Result);
            }
     
            return true;
        }
        return false;
    }

    其实这里的6个标记已经印证了我们的猜测,先获取各种Filter,然后在各个时机执行它们。上面标记2-6都是InvokeXXXFilters就是具体的执行方法。

    但是,到这里上面我们说到的FilterAttribute还没有出现。我们先把焦点放到标记1,GetFilters 上,它获取一个FilterInfo。GetFilters的定义如下:

    1
    2
    3
    4
    protected virtual FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        return new FilterInfo(_getFiltersThunk(controllerContext, actionDescriptor));
    }

      _getFiltersThunk 是一个私有变量:

    1
    private Func<ControllerContext, ActionDescriptor, IEnumerable<Filter>> _getFiltersThunk = FilterProviders.Providers.GetFilters;

    通过定义可以看出,_getFiltersThunk 会返回一个Filter 集合(这里的Filter是一个实际的类,而上面提到的是概念性的东西,或者叫过滤器更合适),Filter 对象包装了IXXXFilter接口对象,具体是在其Instance 属性中。这里有点绕,但不影响,简单的说就是 GetFilters 方法会根据 FilterProviders.Providers.GetFilters 返回的一个IEnumerable<Filter>包装一个 FilterInfo对象。

    我们先看 IEnumerable<Filter> 是如何获取的,它通过 FilterProviders.Providers.GetFilters 获得,FilterProviders 定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static class FilterProviders
    {
        static FilterProviders()
        {
            Providers = new FilterProviderCollection();
            Providers.Add(GlobalFilters.Filters);
            Providers.Add(new FilterAttributeFilterProvider());
            Providers.Add(new ControllerInstanceFilterProvider());
        }
     
        public static FilterProviderCollection Providers { getprivate set; }
    }

    这里可以注册自定义的FilterProvider,FilterProvider实际是实现了IFilterProvider(定义了GetFilters方法)的类。可以看到,mvc 默认已经准备两个FilterProvider。调用GetFilters实际会遍历每一个FilterProvider的GetFilters方法,以内置的FilterAttributeFilterProvider 为例,它的 GetFilters方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public virtual IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        ControllerBase controller = controllerContext.Controller;
        var typeFilters = GetControllerAttributes(controllerContext, actionDescriptor)
            .Select(attr => new Filter(attr, FilterScope.Controller, null));
        var methodFilters = GetActionAttributes(controllerContext, actionDescriptor)
            .Select(attr => new Filter(attr, FilterScope.Action, null));
     
        return typeFilters.Concat(methodFilters).ToList();
    }

      这里也可以看到,Filter对象包装了具体的过滤器。其中GetControllerAttributes,实际它会调用ControllerDescriptor的 GetFilterAttribute,该方法定义如下:

    1
    2
    3
    4
    public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
    {
        return GetCustomAttributes(typeof(FilterAttribute), inherit: true).Cast<FilterAttribute>();
    }

      ok,FilterAttribute 终于出现了!GetActionAttributes 也是类似的过程。

         获取到Controller和Action的FilterAttribute,并包装成Filter集合后,就会构建一个FilterInfo对象,该对象的作用可以从其构造函数看出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public FilterInfo(IEnumerable<Filter> filters)
    {
        // evaluate the 'filters' enumerable only once since the operation can be quite expensive
        var cache = filters.ToList();
     
        var overrides = cache.Where(f => f.Instance is IOverrideFilter);
     
        FilterScope actionOverride = SelectLastScope<IActionFilter>(overrides);
        FilterScope authenticationOverride = SelectLastScope<IAuthenticationFilter>(overrides);
        FilterScope authorizationOverride = SelectLastScope<IAuthorizationFilter>(overrides);
        FilterScope exceptionOverride = SelectLastScope<IExceptionFilter>(overrides);
        FilterScope resultOverride = SelectLastScope<IResultFilter>(overrides);
     
        _actionFilters.AddRange(SelectAvailable<IActionFilter>(cache, actionOverride));
        _authenticationFilters.AddRange(SelectAvailable<IAuthenticationFilter>(cache, authenticationOverride));
        _authorizationFilters.AddRange(SelectAvailable<IAuthorizationFilter>(cache, authorizationOverride));
        _exceptionFilters.AddRange(SelectAvailable<IExceptionFilter>(cache, exceptionOverride));
        _resultFilters.AddRange(SelectAvailable<IResultFilter>(cache, resultOverride));
    }

      很明显,它将Filter 按照各自IXXXFilter接口进行分类。在InvokeAction方法内,就是根据这个分类,在各个时机进行相应的调用的。

    四、总结

      通过一张图来简单总结一下:


  • 请您注意

    ·自觉遵守:爱国、守法、自律、真实、文明的原则

    ·尊重网上道德,遵守《全国人大常委会关于维护互联网安全的决定》及中华人民共和国其他各项有关法律法规

    ·严禁发表危害国家安全,破坏民族团结、国家宗教政策和社会稳定,含侮辱、诽谤、教唆、淫秽等内容的作品

    ·承担一切因您的行为而直接或间接导致的民事或刑事法律责任

    ·您在编程中国社区新闻评论发表的作品,本网站有权在网站内保留、转载、引用或者删除

    ·参与本评论即表明您已经阅读并接受上述条款

  • 感谢本文作者
  • 作者头像
  • 昵称:张琳
  • 加入时间:2013/6/13 0:00:00
  • TA的签名
  • 这家伙很懒,虾米都没写
  • +进入TA的空间
  • 以下内容也很赞哦
分享按钮