ASP.NET MVC Controller的激活
最近抽空看了一下ASP.NET MVC的部分源码,顺带写篇文章做个笔记以便日后查看。
在UrlRoutingModule模块中,将请求处理程序映射到了MvcHandler中,因此,说起Controller的激活,首先要从MvcHandler入手,MvcHandler实现了三个接口:IHttpAsyncHandler, IHttpHandler, IRequiresSessionState。 其处理逻辑主要实现在同步和异步的ProcessRequest方法中,总的来说,该方法在执行的时候,大致经历以下几个步骤:
- 预处理(在响应头中添加版本信息并去除未赋值的可选路由参数)
- 通过ControllerBuilder获取ControlerFactory,并使用Controller工厂创建Controller
- 根据是否是异步处理,调用Controller中相应的方法(ExecuteCore或BeginExecute)
- 释放Controller
其中第一步在ProcessRequestInit方法中进行处理,本文主要是分析第两步中的controller是如何创建出来的。
Controller的创建是通过ControllerFactory实现的,而ControllerFactory的创建又是在ControllerBuilder中完成的,因此我们先了解一下ControllerBuilder的工作原理。
ControllerBuilder
从源码中可以看出,在ControllerBuilder类中,并没有直接实现对controller工厂的创建,ControllerFactory的创建实际上是委托给一个继承自IResolver接口的SingleServiceResolver类的实例来实现的,这一点从GetControllerFactory方法中可以看出,它是通过调用SingleServiceResolver对象的Current属性来完成controller工厂的创建的。
public IControllerFactory GetControllerFactory()
{
return _serviceResolver.Current; //依赖IResolver接口创建工厂
}
并且在源码中还发现,SingleServiceResolver类是internal级别的,这意味着外部无法直接访问,那么ControllerBuilder是如何借助SingleServiceResolver来实现工厂的注册呢?继续看代码,ControllerBuilder类和SingleServiceResolver类都有一个Func<IControllerFactory>
类型的委托字段,我们姑且称为工厂委托,
//ControllerBuilder.cs
private Func<IControllerFactory> _factoryThunk = () => null; //工厂委托
//SingleServiceResolver.cs
private Func<TService> _currentValueThunk; //工厂委托
该委托实现了工厂的创建,而通过SetControllerFactory方法仅仅是更改了ControllerBuilder类的工厂委托字段,并没有更改SingleServiceResolver类的工厂委托字段,
public void SetControllerFactory(IControllerFactory controllerFactory)
{
if (controllerFactory == null)
{
throw new ArgumentNullException("controllerFactory");
}
_factoryThunk = () => controllerFactory; //更改ControllerBuilder的工厂委托字段
}
因此必须将相应的更改应用到SingleServiceResolver类中才能实现真正的注册,我们知道,如果是单纯的引用赋值,那么更改一个引用并不会对另外一个引用造成改变,比如:
Func<object> f1 = ()=>null;
Func<object> f2 = f1; //f1与f2指向同一个对象
object o = new object();
f1 = ()=>o; //更改f1后,f2仍然指向之前的对象
bool b1 = f1() == o; //true
bool b2 = f2() == null; //true, f1()!=f2()
所以,ControllerBuilder在实例化SingleServiceResolver对象的时候,并没有将自身的工厂委托字段直接赋值给SingleServiceResolver对象的对应字段(因为这样的话SetControllerFactory方法注册的委托无法应用到SingleServiceResolver对象中),而是通过委托来进行了包装,这样就会形成一个闭包,在闭包中进行引用,如下所示:
Func<object> f1 = ()=>null;
Func<object> f2 = ()=>f1(); //通过委托包装f1,形成闭包
object o = new object();
f1 = ()=>o; //更改f1后,f2与f1保持同步
bool b1 = f1() == o; //true
bool b2 = f2() == o; //true, f1()==f2()
//ControllerBuilder.cs
internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
{
_serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>(
() => _factoryThunk(), //封装委托,闭包引用
new DefaultControllerFactory { ControllerBuilder = this },
"ControllerBuilder.GetControllerFactory");
}
这样SingleServiceResolver对象中的工厂委托就会与ControllerBuilder对象中的对应字段保持同步了,SetControllerFactory方法也就达到了替换默认工厂的目的。
闭包引用测试代码:
using System;
class Program
{
public static void Main(string[] args)
{
Func<object> f1 = ()=>null;
Func<object> f2 = f1; //f1与f2指向同一个对象
object o = new object();
f1 = ()=>o; //更改f1后,f2仍然指向之前的对象
bool b1 = f1() == o; //true
bool b2 = f2() == null; //true, f1()!=f2()
Print("直接赋值:");
Print(f1(),"f1() == {0}");
Print(f2(),"f2() == {0}");
Print(f1() == f2(),"f1() == f2() ? {0}");
Func<object> ff1 = ()=>null;
Func<object> ff2 = ()=>ff1(); //通过委托包装f1,形成闭包
object oo = new object();
ff1 = ()=>oo; //更改f1后,f2与f1保持同步
bool bb1 = ff1() == oo; //true
bool bb2 = ff2() == oo; //true, f1()==f2()
Print("委托赋值:");
Print(ff1(),"ff1() == {0}");
Print(ff2(),"ff2() == {0}");
Print(ff1() == ff2(),"ff1() == ff2() ? {0}");
Console.ReadLine();
}
static void Print(object mess,string format = "{0}")
{
string message = mess == null ? "null" : mess.ToString();
Console.WriteLine(string.Format(format,message));
}
}
下面看一下SingleServiceResolver类是如何实现对象的创建的,该类是个泛型类,这意味着可以构造任何类型的对象,不仅限于ControllerFactory,实际上在MVC中,该类在很多地方都得到了应用,例如:ControllerBuilder、DefaultControllerFactory、BuildManagerViewEngine等,实现了对多种对象的创建。
SingleServiceResolver
该类实现了IResolver接口,主要用来提供指定类型的实例,在SingleServiceResolver类中有三种方式来创建对象:
1、private Lazy<TService> _currentValueFromResolver; //内部调用_resolverThunk
2、private Func<TService> _currentValueThunk; //委托方式
3、private TService _defaultValue; //默认值方式
private Func<IDependencyResolver> _resolverThunk; //IDependencyResolver方式
从Current方法中可以看出他们的优先级:
public TService Current
{
get { return _currentValueFromResolver.Value ?? _currentValueThunk() ?? _defaultValue; }
}
_currentValueFromResolver
实际上是对_resolverThunk
的封装,内部还是调用_resolverThunk
来实现对象的构造,所以优先级是:_resolverThunk > _currentValueThunk > _defaultValue
,即:IDependencyResolver方式 > 委托方式 > 默认值方式。
SingleServiceResolver在构造函数中默认实现了一个DefaultDependencyResolver对象封装到委托字段_resolverThunk
中,该默认的Resolver是以Activator.CreateInstance(type)的方式创建对象的,但是有个前提,指定的type不能是接口或者抽象类,否则直接返回null。
在ControllerBuilder类中实例化SingleServiceResolver对象的时候指定的是IControllerFactory接口类型,所以其内部的SingleServiceResolver对象无法通过IDependencyResolver方式创建对象,那么创建ControllerFactory对象的职责就落到了_currentValueThunk
(委托方式)和_defaultValue
(默认值方式)这两个方式上,前面说过,SingleServiceResolver类中的委托字段实际上是通过闭包引用ControllerBuilder类中的相应委托来创建对象的,而在ControllerBuilder类中,这个对应的委托默认是返回null,
private Func<IControllerFactory> _factoryThunk = () => null;
因此,默认情况下SingleServiceResolver类的第二种方式也失效了,那么此时也只能依靠默认值方式来提供对象了,在ControllerBuilder类中这个默认值是DefaultControllerFactory:
internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
{
_serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>(
() => _factoryThunk(),
new DefaultControllerFactory { ControllerBuilder = this }, //默认值
"ControllerBuilder.GetControllerFactory");
}
所以,在默认情况下是使用DefaultControllerFactory类来构造Controller的。
在创建SingleServiceResolver对象的时候,可以从三个地方判断出真正创建对象的方法是哪种:
new SingleServiceResolver<IControllerFactory>( //1、看泛型接口,如果为接口或抽象类,则IDependencyResolver方式失效
() => _factoryThunk(), //2、看_factoryThunk()是否返回null,如果是则委托方式失效
new DefaultControllerFactory { ControllerBuilder = this }, //3、以上两种都失效,则使用该默认值
"ControllerBuilder.GetControllerFactory");
通过以上创建对象的过程可以得知,有两种方式可以替换默认的对象提供器:
替换默认的DependencyResolver,可以通过DependencyResolver类的静态方法SetResolver方法来实现:
CustomDependencyResolver customResolver = new CustomDependencyResolver();
DependencyResolver.SetResolver(customResolver);
将以上语句放在程序启动的地方,例如:Application_Start
通过前面介绍的ControllerBuilder类的SetControllerFactory方法
注:第一种方式的优先级更高。
ControllerFactory
通过ControllerBuilder创建出ControllerFactory对象后,下面就要利用该对象完成具体Controller的创建,ControllerFactory都实现了IControllerFactory接口,通过实现CreateController
方法完成对Controller的实例化,CreateController的内部逻辑非常简单,就两步:获取Controller类型,然后创建Controller对象。
获取Controller类型
根据控制器名称获取控制器Type的过程,有必要深入了解一下,以便于我们在日后遇到相关问题的时候能够更好的进行错误定位。获取类型的逻辑都封装在GetControllerType方法中,该过程根据路由数据中是否含有命名空间信息,分为三个阶段进行类型搜索:
- 首先,如果当前路由数据中存在命名空间信息,则在缓存中根据控制器名称和命名空间搜索对应的类型,如果找到唯一一个类型,则返回该类型,找到多个直接抛异常
- 其次,如果当前路由数据中不存在命名空间信息,或在第一阶段的搜索没有找到对应的类型,并且
UseNamespaceFallback==true
,此时会获取ControllerBuilder中设置的命名空间信息,利用该信息和控制器名称在缓存中进行类型搜索,如果找到唯一一个类型,则返回该类型,找到多个直接抛异常 - 最后,如果路由数据和ControllerBuilder中都没有命名空间信息,或者在以上两个阶段都没有搜索到对应的Controller类型,那么会忽略命名空间,在缓存中仅按照控制器名称进行类型搜索,如果找到唯一一个类型,则返回该类型,找到多个直接抛异常
因此,命名空间的优先级是:RouteData > ControllerBuilder
在缓存中搜索类型的时候,如果是第一次查找,会调用ControllerTypeCache.EnsureInitialized方法将保存在硬盘中的Xml缓存文件加载到一个字典类型的内存缓存中。如果该缓存文件不存在,则会遍历当前应用引用的所有程序集,找出所有public权限的Controller类型(判断条件:实现IController接口、非抽象类、类名以Controller结尾),然后将这些类型信息进行xml序列化,生成缓存文件保存在硬盘中,以便于下次直接从缓存文件中加载,同时将类型信息分组以字典的形式缓存在内存中,提高搜索效率,字典的key为ControllerName(不带命名空间)。
Controller类型搜索流程如下图所示:
创建Controller对象
获取Controller类型以后,接下来就要进行Controller对象的创建。在DefaultControllerFactory类的源码中可以看到,同ControllerBuilder类似,该类的构造函数中也实例化了一个SingleServiceResolver对象,按照之前介绍的方法,我们一眼就可以看出,该对象是利用默认值的方式提供了一个DefaultControllerActivator对象。
_activatorResolver = activatorResolver ?? new SingleServiceResolver<IControllerActivator>( //1、泛型为接口,IDependencyResolver方式失效
() => null, //2、返回了null,委托方式失效
new DefaultControllerActivator(dependencyResolver), //3、以上两种方式均失效,则使用该提供方式
"DefaultControllerFactory constructor");
实际上DefaultControllerFactory类仅实现了类型的搜索,对象的真正创建过程需要由DefaultControllerActivator类来完成,默认情况下,DefaultControllerActivator创建Controller的过程是很简单的,因为它实际上使用的是一个叫做DefaultDependencyResolver的类来进行Controller创建的,在该类内部直接调用Activator.CreateInstance(serviceType)
方法完成对象的实例化。
从DefaultControllerFactory和DefaultControllerActivator这两个类的创建过程可以发现,MVC提供了多种方式(IDependencyResolver方式、委托方式 、默认值方式)来提供对象,因此在对MVC相关模块进行扩展的时候,也有多种方式可以采用。
Controller中的数据容器
Controller中涉及到几个给view传值的数据容器:TempData、ViewData和ViewBag。前两者的不同之处在于TempData仅存储临时数据,里面的数据在第一次读取之后会被移除,即:只能被读取一次;ViewData和ViewBag保存的是同一份数据,只不过ViewBag是动态对象,对ViewData进行了封装。
public dynamic ViewBag
{
get
{
if (_dynamicViewDataDictionary == null)
{
_dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData); //封装ViewData
}
return _dynamicViewDataDictionary;
}
}
下面简单说一下TempData的实现原理。
TempData
首先看下MSDN上是如何解释的:
你可以按使用 ViewDataDictionary 对象的相同方式使用 TempDataDictionary 对象传递数据。 但是,TempDataDictionary 对象中的数据仅从一个请求保持到下一个请求,除非你使用 Keep 方法将一个或多个键标记为需保留。 如果键已标记为需保留,则会为下一个请求保留该键。
TempDataDictionary 对象的典型用法是,在数据重定向到一个操作方法时从另一个操作方法传递数据。 例如,操作方法可能会在调用 RedirectToAction 方法之前,将有关错误的信息存储在控制器的 TempData 属性(该属性返回 TempDataDictionary 对象)中。 然后,下一个操作方法可以处理错误并呈现显示错误消息的视图。
TempData的特性就是可以在两个Action之间传递数据,它会保存一份数据到下一个Action,并随着再下一个Action的到来而失效。所以它被用在两个Action之间来保存数据,比如,这样一个场景,你的一个Action接受一些post的数据,然后交给另一个Action来处理,并显示到页面,这时就可以使用TempData来传递这份数据。
TempData实现了IDictionary<string, object>接口,同时内部含有一个IDictionary<string, object>类型的私有字段,并添加了相关方法对字典字段的操作进行了控制,这明显是代理模式的一个应用。因为TempData需要在Action之间传递数据,因此要求其能够对自身的数据进行保存,TempData依赖ITempDataProvider接口实现了数据的加载与保存,默认情况下是使用SessionStateTempDataProvider对象将TempData中的数据存放在Session中。
下面看一下TempData是如何控制数据操作的,TempDataDictionary源码中有这样一段定义:
internal const string TempDataSerializationKey = "__tempData";
private Dictionary<string, object> _data;
private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
私有字典字段_data是真正存放数据的地方,哈希集合_initialKeys和_retainedKeys用来标记数据,_initialKeys中存放尚未被读取的数据key,_retainedKeys存放可以被多次访问的key。
TempDataDictionary对数据操作的控制行为主要体现在在读取数据的时候并不会立即从_data中删除对应的数据,而是通过_initialKeys和_retainedKeys这两个hashset标记每条数据的状态,最后在通过ITempDataProvider进行保存的时候再根据之前标记的状态对数据进行过滤,这时才去除已访问过的数据。
相关的控制方法有:TryGetValue、Add、Keep、Peek、Remove、Clear
1、TryGetValue
public bool TryGetValue(string key, out object value)
{
_initialKeys.Remove(key);
return _data.TryGetValue(key, out value);
}
该方法在读取数据的时候,会从_initialKeys集合中移除对应的key,前面说过,因为_initialKeys是用来标记数据未访问状态的,从该集合中删除了key,之后在通过ITempDataProvider保存的时候就会将数据从_data字典中删除,下一次请求就无法再从TempData访问该key对应的数据了,即:数据只能在一次请求中使用。
2、Add
public void Add(string key, object value)
{
_data.Add(key, value);
_initialKeys.Add(key);
}
添加数据的时候在_initialKeys中打上标记,表明该key对应的数据可以被访问。
3、Keep
public void Keep(string key)
{
_retainedKeys.Add(key);
}
调用Keep方法的时候,会将key添加到_retainedKeys中,表明该条记录可以被多次访问,为什么可以被多次访问呢,可以从Save方法中找到原因:
public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
{
// Frequently called so ensure delegate is stateless
_data.RemoveFromDictionary((KeyValuePair<string, object> entry, TempDataDictionary tempData) =>
{
string key = entry.Key;
return !tempData._initialKeys.Contains(key)
&& !tempData._retainedKeys.Contains(key);
}, this);
tempDataProvider.SaveTempData(controllerContext, _data);
}
可以看出,在保存的时候,会从_data中取出每一条数据,判断该数据的key是否存在于_initialKeys和_retainedKeys中,如果都不存在才会从_data中移除,所以keep方法将key添加到_retainedKeys后,该数据就不会被删除了,即:可以在多个请求中被访问了。
4、Peek
public object Peek(string key)
{
object value;
_data.TryGetValue(key, out value);
return value;
}
从代码中可以看出,该方法在读取数据的时候,仅仅是从_data中进行了获取,并没有移除_initialKeys集合中对应的key,因此通过该方法读取数据不影响数据的状态,该条数据依然可以在下一次请求中被使用。
5、Remove 与 Clear
public bool Remove(string key)
{
_retainedKeys.Remove(key);
_initialKeys.Remove(key);
return _data.Remove(key);
}
public void Clear()
{
_data.Clear();
_retainedKeys.Clear();
_initialKeys.Clear();
}
这两个方法没什么多说的,只是在删除数据的时候同时删除其对应的状态。
ASP.NET MVC Controller的激活的更多相关文章
- ASP.NET MVC——Controller的激活
Controller的激活是根据在路由过程得到的Controller名称来创建对应的Controller对象.相关类如图: Controller激活的过程可通过如下序列图表示: 代码示例如下: str ...
- .NET/ASP.NET MVC Controller 控制器(IController控制器的创建过程)
阅读目录: 1.开篇介绍 2.ASP.NETMVC IControllerFactory 控制器工厂接口 3.ASP.NETMVC DefaultControllerFactory 默认控制器工厂 4 ...
- 三、ASP.NET MVC Controller 控制器(二:IController控制器的创建过程)
阅读目录: 1.开篇介绍 2.ASP.NETMVC IControllerFactory 控制器工厂接口 3.ASP.NETMVC DefaultControllerFactory 默认控制器工厂 4 ...
- ASP.NET MVC Controller激活系统详解1
一.引言 好久没有写博客了,前一段时间学习了Controller激活的一篇很好的博文(链接),在此做个学习总结. 二.Controller 2.1 IController Controller类型直接 ...
- .NET/ASP.NET MVC Controller 控制器(深入解析控制器运行原理)
阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...
- 二、ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)
阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...
- ASP.NET没有魔法——ASP.NET MVC Controller的实例化与执行
上一章节中对路由的注册和匹配过程进行了介绍,知道了MVC的Http请求最终是交由MvcHandler处理的,而其处理过程就是对Controller的创建.执行和释放. 本章将从以下几点进一步对上面提到 ...
- MVC Controller的激活
各Controller的继承关系 Controller最重要的是对Execute方法的调用,当目标Controller对象被激活后,对请求的后续处理和最终响应均是通过执行这个Execute方法来完成. ...
- NET/ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)
阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...
随机推荐
- [APUE]文件和目录(上)
一.文件权限 1. 各种ID 我在读这一章时遇到了各种ID,根据名字完全不清楚什么意思,幸好看到了这篇文章,http://blog.csdn.net/ccjjnn19890720/article/de ...
- 【原创】免费申请SSL证书【用于HTTPS,即是把网站从HTTP改为HTTPS,加密传输数据,保护敏感数据】
今天公司有个网站需要改用https访问,所以就用到SSL证书.由于沃通(以前我是在这里申请的)暂停了免费的SSL证书之后,其网站推荐了新的一个网站来申请证书,所以,今天因为刚好又要申请一个证书,所以, ...
- 快递Api接口 & 微信公众号开发流程
之前的文章,已经分析过快递Api接口可能被使用的需求及场景:今天呢,简单给大家介绍一下微信公众号中怎么来使用快递Api接口,来完成我们的需求和业务场景. 开发语言:Nodejs,其中用到了Neo4j图 ...
- 《你不知道的JavaScript》整理(四)——原型
一.[[Prototype]] JavaScript中的对象有一个特殊的[[Prototype]]内置属性,其实就是对于其他对象的引用. var myObject = { a: 2 }; myObje ...
- bzoj3207--Hash+主席树
题目大意: 给定一个n个数的序列和m个询问(n,m<=100000)和k,每个询问包含k+2个数字:l,r,b[1],b[2]...b[k],要求输出b[1]~b[k]在[l,r]中是否出现. ...
- BPM公文管理解决方案分享
一.方案概述 公文作为一种规范性文书,具有法律性.指导性.政令性强的特点,是企事业单位政令上通下达的重要方式.及时.准确.安全地处理.控制和管理公文,方能保障企事业单位正常运转,确保组织权威和政令畅通 ...
- 【干货分享】流程DEMO-补打卡
流程名: 补打卡申请 业务描述: 当员工在该出勤的工作日出勤但漏打卡时,于一周内填写补打卡申请. 流程相关文件: 流程包.xml 流程说明: 直接导入流程包文件,即可使用本流程 表单: 流程: 图片 ...
- Android—基于GifView显示gif动态图片
android中显示gif动态图片用到了开源框架GifView 1.拷GifView.jar到自己的项目中. 2.将自己的gif图片拷贝到drawable文件夹 3.在xml文件中设置基本属性: &l ...
- Atitit 软件工程概览attilax总结
Atitit 软件工程概览attilax总结 1.1. .2 软件工程的发展 进一步地,结合人类发展史和计算机世界演化史来考察软件工程的发展史. 表2 软件工程过程模型 表2将软件工程的主要过程模型做 ...
- (整理)MyBatis入门教程(一)
本文转载: http://www.cnblogs.com/hellokitty1/p/5216025.html#3591383 本人文笔不行,根据上面博客内容引导,自己整理了一些东西 首先给大家推荐几 ...