通过源码了解ASP.NET 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方法内,就是根据这个分类,在各个时机进行相应的调用的。

四、总结

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

最近在做文件处理系统中,要把最近打开文件显示出来,方便用户使用。网上资料有说,去遍历“C:\Documents and Settings\Administrator\Recent”下的最近文档本。文主要介绍在Winform界面菜单中实现【最近使用的文件】动态菜单的处理,实现一个较为常用的功能。

1 新建windform项目

在窗体中添加 menuStrip 控件 ,添加 ‘打开’与 ‘最近文件’

2 打开settings 文件,如下图添加相关参数

3 代码处理过程

添加一个FileHandler 类,用户处理配置文件中的类容熟悉以及菜单栏中的单项对象。代码如下

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
 
/******************************************************************* 
* Copyright (C)  版权所有
* 文件名称:FileHandler
* 命名空间:TestRecentMenu
* 创建时间:2018/12/18 10:27:52
* 作    者: wangyonglai
* 描    述:
* 修改记录:
* 修改人:
* 版 本 号:v1.0.0
**********************************************************************/
namespace TestRecentMenu
{
    public class FileHandler
    {
        /// <summary>
        /// 最近文件菜单项
        /// </summary>
        public ToolStripMenuItem RecentFileMenu { getset; }
 
        private StringCollection fileList;
 
        private int fileNumbers;
 
        public FileHandler()
        {
            fileNumbers = Properties.Settings.Default.FileNember;
            fileList = Properties.Settings.Default.FilePaths;
            if (fileList == null)
            {
                fileList = new StringCollection();
            }
             
        }
 
 
        /// <summary>
        /// 更新最近菜单单项
        /// </summary>
        public void UpdateMenu()
        {
            if (RecentFileMenu == nullreturn;
            int i;
            //清除当前菜单项
            for (i = RecentFileMenu.DropDownItems.Count - 1; i >= 0; i--)
            {
                RecentFileMenu.DropDownItems.RemoveAt(i);
            }
 
            for (i = 0; i < fileList.Count; i++)
            {
                ToolStripItem menuItem = new ToolStripMenuItem();
                menuItem.Text = Path.GetFileName(fileList[i]);
                menuItem.Tag = fileList[i];
                menuItem.Click += menuItem_Click;
 
                RecentFileMenu.DropDownItems.Add(menuItem);
            }
        }
 
        void menuItem_Click(object sender, EventArgs e)
        {
            //点击最近打开菜单项要执行的动作。
        }
 
 
        /// <summary>
        /// 添加最近文件路径(每次打开文件时,调用该方法)
        /// </summary>
        /// <param name="filePath"></param>
        public void AddRecentFile(string filePath)
        {
            fileList.Insert(0, filePath);
 
            //从最后位置开始倒着找,如果找到一致名称,则移除旧记录
            for (int i = fileList.Count - 1; i > 0; i--)
            {
                for (int j = 0; j < i; j++)
                {
                    if (fileList[i] == fileList[j])
                    {
                        fileList.RemoveAt(i);
                        break;
                    }
                }
            }
 
            //最后,仅保留指定的文件列表数量
            for (int bynd = fileList.Count - 1; bynd > fileNumbers - 1; bynd--)
            {
                fileList.RemoveAt(bynd);
            }
 
            Properties.Settings.Default.FilePaths = fileList;
            Properties.Settings.Default.Save();
 
            UpdateMenu();
        }
 
    }
}

4 在主界面中调用FileHandler相关函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FileHandler filehandler;
        private void Form1_Load(object sender, EventArgs e)
        {
            filehandler = new FileHandler();
            filehandler.RecentFileMenu = this.最近文件ToolStripMenuItem;//指定 最近文件 的菜单值,方便动态创建文件菜单
            filehandler.UpdateMenu();
        }
 
        private void 打开ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog fls = new OpenFileDialog();
            if (fls.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                filehandler.AddRecentFile(fls.FileName);
            }
        }

  效果图如下

通过源码了解ASP.NET MVC 几种Filter的执行过程 在Winform中菜单动态添加“最近使用文件”的更多相关文章

  1. 通过源码了解ASP.NET MVC 几种Filter的执行过程

    一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源 ...

  2. ASP.NET MVC 几种 Filter 的执行过程源码解析

    一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多 人觉得平时根本不需要知道这些,会用就行了.其实阅读 ...

  3. Linux下通过源码编译安装程序

    本文简单的记录了下,在linux下如何通过源码安装程序,以及相关的知识.(大神勿喷^_^) 一.程序的组成部分 Linux下程序大都是由以下几部分组成: 二进制文件:也就是可以运行的程序文件 库文件: ...

  4. 在centos6.7通过源码安装python3.6.7报错“zipimport.ZipImportError: can't decompress data; zlib not available”

    在centos6.7通过源码安装python3.6.7报错: zipimport.ZipImportError: can't decompress data; zlib not available 从 ...

  5. Kafka详解六:Kafka如何通过源码实现监控

    问题导读: 1.kafka的消费者组的消费偏移存储,kafka支持两个版本?        2.ConsumerOffsetChecker类的作用是什么?        3.Kafka如何通过源码实现 ...

  6. 通过源码编译安装VIM

    开发中使用的是Ubuntu 12.04 LTS,通过sudo apt-get install vim安装的版本较低,不支持YCM,所以,用源码编译并安装最新的Vim. 卸载旧版本的Vim: sudo ...

  7. echarts 通过源码方法 传入对应data数据获取分割步长值

    通过源码方法获取这里的分割数字长度 /** * Quantity of a number. e.g. 0.1, 1, 10, 100 * * @param {number} val * @return ...

  8. 通过源码安装PostgresSQL

    通过源码安装PostgresSQL 1.1 下载源码包环境: Centos6.8 64位 yum -y install bison flex readline-devel zlib-devel yum ...

  9. 如何通过源码包的方式在linux安装python36

    背景: python34的安装非常简单,直接用yum就可以安装,但是安装最新版的python36通过yum方式是不行的,需要通过源码包进行安装 具体步骤如下: 1.安装openssl静态库[pip3安 ...

随机推荐

  1. crtmpserver实现防盗流和流推送验证 之二

    IV. Catching the thieves 抓住小偷 Well, we have just added a secure mechanism to our little streaming se ...

  2. [转贴] 数字证书及 CA 的扫盲介绍

    [略有删节] 为了达到普及的效果,俺会尽量用比较浅显,非技术的语言来讲清楚. ★先说一个通俗的例子 考虑到证书体系的相关知识比较枯燥.晦涩.俺先拿一个通俗的例子来说事儿.   ◇普通的介绍信 想必大伙 ...

  3. 一个常见下拉菜单的样式:一体化小三角(纯css手写解决)

    类似下拉菜单2个一体化小三角,习惯上用字体图标加jQuery处理,比较方便,但是下面纯css手写解决方式,效果也还不错,对CSS知识也是一个比较好的孔固. 小三角用了2种不同处理方式:1.利用bord ...

  4. Android开发之Drag&Drop框架实现拖放手势

    Android3.0提供了drag/drop框架,利用此框架可以实现使用拖放手势将一个view拖放到当前布局中的另外一个view中.本文将介绍如何使用拖放框架. 一.实现拖放的步骤 首先,我们先了解一 ...

  5. Android -- Fragment注意事项

    ViewPager+Fragment 让Fragment成为ViewPager的一页时,FragmentManager会一直保存管理创建好了的Fragment,即使当前不是显示的这一页,Fragmen ...

  6. Sqlserver存储过程生成日期维度

    话不多说,之前已经有一篇日志是利用oracle的存储过程生成日期维度表,接下来我们就用sqlserver来实现这个操作,如下面的步骤所示 1:创建日期维度表(Dim_time) USE [DW] GO ...

  7. HDU 1247 Hat’s Words (字典树 &amp;&amp; map)

    分析:一開始是用递归做的,没做出来.于是就换了如今的数组.即,把每个输入的字符串都存入二维数组中,然后创建字典树.输入和创建完成后,開始查找. 事实上一開始就读错题目了,题目要求字符串是由其它两个输入 ...

  8. (数据挖掘-入门-6)十折交叉验证和K近邻

    主要内容: 1.十折交叉验证 2.混淆矩阵 3.K近邻 4.python实现 一.十折交叉验证 前面提到了数据集分为训练集和测试集,训练集用来训练模型,而测试集用来测试模型的好坏,那么单一的测试是否就 ...

  9. Linux:编译动态库时遇到的错误relocation R_X86_64_32 against `a local symbol'

    编译动态库时遇到如下错误: ... ... relocation R_X86_64_32 against `a local symbol' can not be used when making a ...

  10. C#.NET常见问题(FAQ)-abstract抽象类如何理解

    例如有太多相似,但是不一样的类,他们都继承自同一个基类(比如大型游戏有各个种族,每个种族有各种人物,加起来几百种类型,然后基本上他们都是一个角色,都有基本相同的属性和方法,比如都会走,只是速度不同,都 ...