通过源码了解ASP.NET MVC 几种Filter的执行过程 在Winform中菜单动态添加“最近使用文件”
通过源码了解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< string , object > 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 { get ; private 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 { get ; set ; } 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 == null ) return ; 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中菜单动态添加“最近使用文件”的更多相关文章
- 通过源码了解ASP.NET MVC 几种Filter的执行过程
一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源 ...
- ASP.NET MVC 几种 Filter 的执行过程源码解析
一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多 人觉得平时根本不需要知道这些,会用就行了.其实阅读 ...
- Linux下通过源码编译安装程序
本文简单的记录了下,在linux下如何通过源码安装程序,以及相关的知识.(大神勿喷^_^) 一.程序的组成部分 Linux下程序大都是由以下几部分组成: 二进制文件:也就是可以运行的程序文件 库文件: ...
- 在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 从 ...
- Kafka详解六:Kafka如何通过源码实现监控
问题导读: 1.kafka的消费者组的消费偏移存储,kafka支持两个版本? 2.ConsumerOffsetChecker类的作用是什么? 3.Kafka如何通过源码实现 ...
- 通过源码编译安装VIM
开发中使用的是Ubuntu 12.04 LTS,通过sudo apt-get install vim安装的版本较低,不支持YCM,所以,用源码编译并安装最新的Vim. 卸载旧版本的Vim: sudo ...
- echarts 通过源码方法 传入对应data数据获取分割步长值
通过源码方法获取这里的分割数字长度 /** * Quantity of a number. e.g. 0.1, 1, 10, 100 * * @param {number} val * @return ...
- 通过源码安装PostgresSQL
通过源码安装PostgresSQL 1.1 下载源码包环境: Centos6.8 64位 yum -y install bison flex readline-devel zlib-devel yum ...
- 如何通过源码包的方式在linux安装python36
背景: python34的安装非常简单,直接用yum就可以安装,但是安装最新版的python36通过yum方式是不行的,需要通过源码包进行安装 具体步骤如下: 1.安装openssl静态库[pip3安 ...
随机推荐
- C++代码统计工具
自己前几天写的C++代码统计工具. http://pan.baidu.com/s/17SnnH
- 金蝶KIS下载地址
升级方法: 您好,建议您先升级到标准版7.5,再升级到标准版8.1,直接用7.5的软件打开金蝶2000的账套,会提示升级,再用8.1的软件打开7.5的账套,升级前,需先备份账套. 金蝶KIS标准版和业 ...
- Informatica 常用组件Lookup之六 查询
PowerCenter 基于您在查找转换中配置的端口和属性来查询查找.当第一行输入到查找转换时,PowerCenter 运行一个默认的 SQL 语句.如果使用关系查找,您可以在"查找 SQL ...
- 在CentOS 7上使用Yum源安装和卸载 MongoDB 3.4
在CentOS 7上使用Yum源安装和卸载 MongoDB 3.4 1.配置Yum源 vim /etc/yum.repos.d/mongodb-org-3.4.repo [mongodb-org-3. ...
- 【LeetCode】Path Sum II 二叉树递归
Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given su ...
- 如何将DB2的数据库转换到mySQL中?
经过几次尝试最终找到解决的办法,因为网上并没有详细说明,所以下面我将详细的步骤记录如下: 1. 大前提是你已经安装好DB2服务器和mySQL服务器,现在只是借用Navicat for MySQL这个软 ...
- 向第一个 p 元素添加一个类
This is a heading This is a paragraph. This is another paragraph. 向第一个 p 元素添加一个类 111 <html> &l ...
- 11g relocate scan ip
今天安装了Oracle 11.2.0.4的数据库,由于在安装GRID软件是,跑脚本的时候是现在节点2上跑的,跑完之后然后在节点1上跑.发现我的scan_ip在节点2上,我想把scan_ip reloc ...
- JavaScript三(语法、关键保留字及变量)
基本概念 一.区分大小写 在ECMAScript中的一切(变量.函数名.操作符)都是区分大小写的. 如变量名test和Test分别表示两个不同的变量, 二.标识符 所谓标识符,就是指变量.函数.属性的 ...
- Jacoco覆盖率工具使用
Jacoco介绍 Jacoco是一个开源的覆盖率工具.Jacoco可以嵌入到Ant .Maven中,并提供了EclEmma Eclipse插件,也可以使用JavaAgent技术监控Java程序.很多第 ...