白话学习MVC(七)Action的执行一
一、概述
在此系列开篇的时候介绍了MVC的生命周期 , 对于请求的处理,都是将相应的类的方法注册到HttpApplication事件中,通过事件的依次执行从而完成对请求的处理。对于MVC来说,请求是先 经过路由系统,然后由一个MvcHandler来处理的,当请求到来时,执行此MvcHandler的ProcessRequest方法(因为已将 MvcHandler类的ProcessRequest方法注册到HttpApplication的事件中,所以事件的执行就触发了此方法)。详细请看之前介绍MVC生命周期的两篇博客。
下面我们就以MVC声明周期为主线,来分析下MVC源码
public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState { protected virtual void ProcessRequest(HttpContext httpContext) { //使用HttpContextWrapper对HttpContext进行封装,封装的目的是为了解耦以获得可测试性.然后从RequestContext.RouteData中提取Controller名称. HttpContextBase httpContext2 = new HttpContextWrapper(httpContext); this.ProcessRequest(httpContext2); } protected internal virtual void ProcessRequest(HttpContextBase httpContext) { IController controller; IControllerFactory controllerFactory; this.ProcessRequestInit(httpContext, out controller, out controllerFactory);//获取到Controler和ControllerFactory实例,并赋值给局部变量 try { //Action的调用、View的呈现 controller.Execute(this.RequestContext); } finally { //释放当前Controler对象 controllerFactory.ReleaseController(controller); } } }
MVC中Action的调用,就是通过调用Contrller对象的Execute方法触发执行的!这个Controller对象是Controller激活的产物,Controller激活请参考上一篇博客。
二、Action的调用
我们知道Action的执行就是调用通过Controller激活得到的Controller对象的Execute方法,这个Controller对象就是我们创建的Controller(例如:HomeController)类的实例,而我们创建的HomeController等控制器都继承自Controller类、Controller抽象类继承ControllerBase抽象类、ControllerBase抽象类实现了IController接口。继承和实现关系为:
我们创建的控制器通过Controller的激活创建了实例,然后执行该实例的Execute方法,Execute方法定义在接口IController中,实现在类ControllerBase中,而该Excute方法内又调用ControllerBase类的抽象方法ExecuteCore,抽象方法ExecuteCore又在Controller类中实现。所以Action调用的流程为:先执行ControllerBase类的Execute方法,再执行Controller类的ExcuteCore方法。所以执行过程为:【ControllerBase类中的Execute方法】-->【Controller类中的ExecuteCore方法】
namespace System.Web.Mvc { public interface IController { void Execute(RequestContext requestContext); } }
namespace System.Web.Mvc { public abstract class ControllerBase : IController { //省略其他成员 protected virtual void Execute(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (requestContext.HttpContext == null) { throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext"); } VerifyExecuteCalledOnce(); Initialize(requestContext); using (ScopeStorage.CreateTransientScope()) { //执行ExecuteCore方法 ExecuteCore(); } } //该方法在派生类Controller类中实现 protected abstract void ExecuteCore(); } }
namespace System.Web.Mvc { public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer { //省略其他成员 //实现ControllerBase中的抽象方法 protected override void ExecuteCore() { // If code in this method needs to be updated, please also check the BeginExecuteCore() and // EndExecuteCore() methods of AsyncController to see if that code also must be updated. PossiblyLoadTempData(); try { //从路由中获取Action的名字 string actionName = RouteData.GetRequiredString("action"); //根据Action名字和上下文对Action进行调用 if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { PossiblySaveTempData(); } } } }
由上述代码可以看出Action的执行最终实现在Controller类的ExecuteCore方法中,而其中ActionInvoker就是实现Action调用的组件,执行ActionInvoker的InvokeAction方法实现对Action的调用。
整个执行过程的功能为:【检查对请求只做一次处理】-->【封装请求上下文】-->【获取上一次没有被使用的TempData】-->【过滤器、Action的执行】-->【View的呈现(下一节介绍)】-->【将没有被使用的TempData放入Session中】
//整个流程 public abstract class ControllerBase : IController { protected virtual void Execute(RequestContext requestContext) { //检查对请求只做一次处理 VerifyExecuteCalledOnce(); //封装请求上下文(RequestContext对象是在路由系统中创建的。其中封装了请求上下文和路由信息。) Initialize(requestContext); //定义用于包含临时作用域存储的类。 基于 CurrentScope 属性中的作用域,返回用于存储临时作用域内的数据的字典。 //这个的作用暂时还没有弄清楚,不过通过重写Execute方法,在using块内可以获取ScopeStorage的属性CurrentScope的三个键值对。 using (ScopeStorage.CreateTransientScope()) { //执行Controller类中的ExecuteCore方法 ExecuteCore(); } } } public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer { protected override void ExecuteCore() { //获取上一次没有被使用的TempData PossiblyLoadTempData(); try { //从路由数据中获取请求的Action的名字(路由系统从请求地址中获取) string actionName = RouteData.GetRequiredString("action"); //过滤器、Action的执行、View的呈现 if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { //将没有被使用的TempData放入Session中 PossiblySaveTempData(); } } }
==从以上的执行过程中各代码的功能可以看出ExecuteCore()方法是Action执行的主操作,而VerifyExecuteCalledOnce()、Initialize(requestContext)两个方法是前戏了。我们就先来分析下这两个前戏的方法,主操作ExecuteCore()方法留着最后,并对其内部操作再进行详细分析。
1、VerifyExecuteCalledOnce()
此方法保证了一次Http请求只进行一次处理
public abstract class ControllerBase : IController { private readonly SingleEntryGate _executeWasCalledGate = new SingleEntryGate(); //RequestContext对象是在路由系统中创建的。其中封装了请求上下文和路由信息。 protected virtual void Execute(RequestContext requestContext) { //判断对当期的请求是否是第一次执行处理 VerifyExecuteCalledOnce(); //执行Controller类的Initialize方法。(Initialize方法在Controller类中被重写) Initialize(requestContext); //定义用于包含临时作用域存储的类。 基于 CurrentScope 属性中的作用域,返回用于存储临时作用域内的数据的字典。 //这个的作用暂时还没有弄清楚,不过通过重写Execute方法,在using块内可以获取ScopeStorage的属性CurrentScope的三个键值对。 using (ScopeStorage.CreateTransientScope()) { //执行Controller类中的ExecuteCore方法 ExecuteCore(); } } internal void VerifyExecuteCalledOnce() { //如果是TryEnter方法是第一次执行_executeWasCalledGate.TryEnter()=true //否则_executeWasCalledGate.TryEnter()=false if (!_executeWasCalledGate.TryEnter()) { string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType()); throw new InvalidOperationException(message); } } }
internal sealed class SingleEntryGate { private const int NotEntered = 0; private const int Entered = 1; private int _status; // returns true if this is the first call to TryEnter(), false otherwise public bool TryEnter() { //Interlocked静态类定义在System.Threading命名空间中且Exchange方法是extern的(定义在别的文件中)! //方法的概述为:以原子操作的形式,将 32 位有符号整数设置为指定的值并返回原始值。原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束 //Interlocked.Exchange方法将Extered的值设置给_status,并返回值为第一个参数_status的原始值 int oldStatus = Interlocked.Exchange(ref _status, Entered); return (oldStatus == NotEntered); } }
==上述代码,在ControllerBase类中私有只读字段_executeWasCalledGate创建了一个SingleEntryGate对象,而VerifyExecuteCalledOnce方法的功能就是通过 这个对象的TryTryEnter方法来实现的!如果TryTryEnter方法返回值为:true,则表示是第一次执行;否则非第一次执行,那么就抛出非法操作异常了。从而保证了一次Http请求只进行一次处理。
而内部到底是如何实现的呢?我们就在来看看SingleEntryGate类,其中的TeyEnter方法中调用了Interlocked.Exchange(ref int1,int2)方法,此方法定义的System.Threading命名空间内,方法的功能为:将第二个参数的值赋值给第一个参数,并将第一个参数原来的值作为方法的返回值。
例如:
如果是第一次调用TryEnter方法【Entered=1,_status=0,NotEntered = 0】执行完Interlocked.Exchange方法后【Entered = 1,_status=1,NotEntered = 0,oldStatus=0】,此时oldStatus=NotEntered = 0,返回true
如果是第n次调用TryEnter方法,那么此时的变量值还是第一次执行完的状态【Entered = 1,_status=1,NotEntered = 0】,而执行完Interlocked.Exchange方法后【Entered = 1,_status=1,NotEntered = 0,oldStatus=1】,此时NotEntered = 0,oldStatus=1,不相等,返回false
注意,在ControllerBase类中私有只读字段_executeWasCalledGate是非静态的字段,所以实现的功能是【检查每一次请求只执行一次】,如果是静态字段,那么就变成了程序只执行一次请求的处理。(这功能的实现值得收藏)
2、Initialize(requestContext)
将 requestContext 和 当前请求的控制对象 封装到一个 ControllerContext对象中!其中requestContext是已封装了请求上下文和当前请求的路由信息的一个上下文。
public abstract class ControllerBase : IController { public ControllerContext ControllerContext { get; set; } //RequestContext对象是在路由系统中创建的。其中封装了请求上下文和路由信息。 protected virtual void Execute(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (requestContext.HttpContext == null) { throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext"); } //判断是否为第一次执行 VerifyExecuteCalledOnce(); //执行Controller类的Initialize方法。(Initialize方法在Controller类中被重写) Initialize(requestContext); //定义用于包含临时作用域存储的类。 基于 CurrentScope 属性中的作用域,返回用于存储临时作用域内的数据的字典。 //这个的作用暂时还没有弄清楚,不过通过重写Execute方法,在using块内可以获取ScopeStorage的属性CurrentScope的三个键值对。 using (ScopeStorage.CreateTransientScope()) { //执行Controller类中的ExecuteCore方法 ExecuteCore(); } } protected virtual void Initialize(RequestContext requestContext) { ControllerContext = new ControllerContext(requestContext, this); } }
public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer { public UrlHelper Url { get; set; } protected override void Initialize(RequestContext requestContext) { base.Initialize(requestContext); Url = new UrlHelper(requestContext); } }
public class ControllerContext { internal const string ParentActionViewContextToken = "ParentActionViewContext"; private HttpContextBase _httpContext; private RequestContext _requestContext; private RouteData _routeData; // parameterless constructor used for mocking public ControllerContext() { } protected ControllerContext(ControllerContext controllerContext) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } Controller = controllerContext.Controller; RequestContext = controllerContext.RequestContext; } public ControllerContext(HttpContextBase httpContext, RouteData routeData, ControllerBase controller) : this(new RequestContext(httpContext, routeData), controller) { } //【Action的执行】过程中,通过请求上下文和请求的当期控制器对象(如:HomeController)创建控制器上下文。 public ControllerContext(RequestContext requestContext, ControllerBase controller) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (controller == null) { throw new ArgumentNullException("controller"); } RequestContext = requestContext;//上下文参数设置给私有变量 Controller = controller; //控制器(如:HomeController)对象设置给属性 } public virtual ControllerBase Controller { get; set; } public IDisplayMode DisplayMode { get { return DisplayModeProvider.GetDisplayMode(HttpContext); } set { DisplayModeProvider.SetDisplayMode(HttpContext, value); } } public virtual HttpContextBase HttpContext { get { if (_httpContext == null) { _httpContext = (_requestContext != null) ? _requestContext.HttpContext : new EmptyHttpContext(); } return _httpContext; } set { _httpContext = value; } } public virtual bool IsChildAction { get { RouteData routeData = RouteData; if (routeData == null) { return false; } return routeData.DataTokens.ContainsKey(ParentActionViewContextToken); } } public ViewContext ParentActionViewContext { get { return RouteData.DataTokens[ParentActionViewContextToken] as ViewContext; } } public RequestContext RequestContext { get { if (_requestContext == null) { // still need explicit calls to constructors since the property getters are virtual and might return null HttpContextBase httpContext = HttpContext ?? new EmptyHttpContext(); RouteData routeData = RouteData ?? new RouteData(); _requestContext = new RequestContext(httpContext, routeData); } return _requestContext; } set { _requestContext = value; } } public virtual RouteData RouteData { get { if (_routeData == null) { _routeData = (_requestContext != null) ? _requestContext.RouteData : new RouteData(); } return _routeData; } set { _routeData = value; } } private sealed class EmptyHttpContext : HttpContextBase { } }
==上述代码,对于Initialize方法的执行,由于ControllerBase中的Initialize方法在派生类Controller类中被重写,所以要执行Controller类中的Initialize方法。方法内首先调用了父类ControllerBase中的Initialize方法创建了一个控制器上下文对象,并赋值给一个公有属性。之后又创建了UrlHelper对象也赋值给了一个公有属性。这个控制器上下文对象包含了从请求到现在的所有有用的数据,所以在之后对请求处理的步骤中随处可见!这个UrlHelper对象还没用到,暂且不议。
3、ExecuteCore()
ControllerBase类中定义了抽象方法ExecuteCore,该方法被派生类Controller类中实现!所以,要执行Controller类中的ExecuteCore方法,如上所言,此方法是Action执行的主操作,其中先对上一次操作没有被TempData做处理,然后执行过滤器和请求的Action,最后进行View的呈现(下一节再对View的呈现做分析,现在只需知道这个执行的流程即可)。下面,我们就来用分析所有过程的代码!
public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer { protected override void ExecuteCore() { //获取上次处理过程中没有被使用的TempData PossiblyLoadTempData(); try { //从路由数据中获取请求的Action的名字 string actionName = RouteData.GetRequiredString("action"); //ActionInvoker为一个AsyncControllerActionInvoker对象 if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { //将TempData保存到Session中。等待之后将Session的key【__ControllerTempData】发送到响应流中! PossiblySaveTempData(); } } }
3-1、PossiblyLoadTempData()
此方法创建保存TempData的集合并获取上一次请求中没有被使用TempData添加到之前创建的那个集合中!
在介绍这个方法之前,有必要先了解下MVC框架下TempData的机制:
客户端向服务发请求时,程序在执行本次请求的Action前,会先创建一个用来保存TempData的集合A,然后根据key=__ControllerTempData去服务器Session中获取值并转换为Dictionary<string, object>类型,如果得到的值为null,表示三种情况(1、当前是客户端第一次向服务端发送请求。2、上次请求中没有定义TempData值。3、上次请求中的TempData被View中用完了。);如果得到的值不为null,则表示上一次对请求的处理时TempData没有被使用完,此时获取的值就是上次处理请求时没有被使用的TempData集合,然后将这集合赋值给我们开始创建的用于保存TempData的集合A中,再将key=__ControllerTempData的Session移除;之后执行Action方法内的代码时,将本次的请求的Action中定义的TempData[""]=XXX也添加到最开始创建的那个集合A中,执行完Action方法后,在View的呈现中,如果使用了TempData,就将这个TempData从集合A中移除,执行完View后,则将保存TempData的集合A当作value,key=__ControllerTempData保存到服务器Session中。最后将所有Session的keys发送到响应流中!
上述提到那个保存TempData的集合A实际上是TempDataDictionary类型中的一个变量,该变量是一个字典类对象(new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase))
TempData是ControllerBase类中的一个类型为TempDataDictionary的属性,TempData属性其实就是一个TempDataDictionary对象,该对象中有一个字典类型的私有字段保存着所有TempData的值!
我们在Action方法中使用的TempData["kk"]=XX,其实就是调用的TempDataDictionary对象的索引器,将该键值对添加到保存所有TempData的私有字典表中!
扩展:Session的机制,Session保存在服务器,但是请求的最后服务端会将Session的所有key发送到响应流中!当再次发来请求时,可以从请求上下文中获取到所有的keys。
public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer { protected override void ExecuteCore() { //获取上次处理过程中没有被使用的TempData PossiblyLoadTempData(); try { //从路由数据中获取请求的Action的名字 string actionName = RouteData.GetRequiredString("action"); //ActionInvoker为一个AsyncControllerActionInvoker对象 if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { //将TempData保存到Session中。等待之后将Session的key【__ControllerTempData】发送到响应流中! PossiblySaveTempData(); } } private ITempDataProvider _tempDataProvider; internal IDependencyResolver Resolver { get { return _resolver ?? DependencyResolver.CurrentCache; } set { _resolver = value; } } public ITempDataProvider TempDataProvider { get { if (_tempDataProvider == null) { _tempDataProvider = CreateTempDataProvider(); } return _tempDataProvider; } set { _tempDataProvider = value; } } protected virtual ITempDataProvider CreateTempDataProvider() { //Resolver是Controller的一个属性,返回值为:DependencyResolver.CurrentCache //Resolver.GetService<ITempDataProvider>() 可以不用看,因为返回的是null。这个方法其实先根据key=ITempDataProvider从全局的缓存表中查找值,如果有值,则返回。如果没有,则利用DefaultDependencyResolver对象通过反射 //根据类型ITempDataProvider创建对象。但是由于这里的ITempDataProvider是接口,而DefaultDependencyResolver对象反射创建对象时,如果是接口或抽象类均返回null。详细请看DependencyResolver类! //所以这段代码,返回值就是 new SessionStateTempDataProvider(); return Resolver.GetService<ITempDataProvider>() ?? new SessionStateTempDataProvider(); } internal void PossiblyLoadTempData() { //检查当前请求的Action是否为子Action。 //即:在View中是否应用了@Html.Action(),如果在View中嵌套了一个@Html.Action(action2),则这个action2也是需要再被执行一次的!即使Action的View中嵌套了一个Action,整个过程还是被 // 认为是一次请求的,此处判断条件的目的是防止被嵌套的Action再去执行。 if (!ControllerContext.IsChildAction) { //TempData是基类ControllerBase中的属性 //此处是TempData属性被第一次使用,得到TempDataDictionary对象后,再执行此对象的Load方法,这个方法就是去实现获取上一次处理请求时没使用的TempData功能的! //参数TempDataProvider是关键,它是Controller类的一个属性(就是一个SessionStateTempDataProvider对象),正如参数名的翻译这个参数就是负责去请求上下文中检查并获取值的! TempData.Load(ControllerContext, TempDataProvider); } } }
public abstract class ControllerBase : IController { private TempDataDictionary _tempDataDictionary; //此时的ControllerContext是已经封装 请求上下文、路由、当情请求的控制器对象。(在执行ExecuteCore方法之前的Execute方法中设置的!) public ControllerContext ControllerContext { get; set; } //第一次使用TempData属性时(即:PossiblyLoadTempData方法是第一次使用),会创建一个TempDataDictionary对象并赋值给变量_tempDataDictionary,属性的返回值就是该_tempDataDictionary变量。 //以后再使用TempData属性时,返回都是第一次创建的那个TempDataDictionary对象,所以我们在Action方法中使用TempData[""]时,就是调用TempDataDictionary对象的索引器,向 //保存TempData集合添加一个键值对。 public TempDataDictionary TempData { get { if (ControllerContext != null && ControllerContext.IsChildAction) { return ControllerContext.ParentActionViewContext.TempData; } if (_tempDataDictionary == null) { //实例化TempDataDictionary,在构造函数中会创建一个不区分key大小写的字典表,并赋值给一个私有字段。 _tempDataDictionary = new TempDataDictionary(); } return _tempDataDictionary; } set { _tempDataDictionary = value; } } }
public class TempDataDictionary : IDictionary<string, object> { 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); public TempDataDictionary() { //初始化key不区分大小写的字典类 _data = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); } public ICollection<string> Keys { get { return _data.Keys; } } //TempData["MyKey"]=MyValue,就是调用此索引器 //使用TempData时候,也是用这个索引,之后介绍! public object this[string key] { get { object value; if (TryGetValue(key, out value)) { _initialKeys.Remove(key); return value; } return null; } set { _data[key] = value; _initialKeys.Add(key); } } public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) { //从Session中获取上一次请求中没有被使用的TempData IDictionary<string, object> providerDictionary = tempDataProvider.LoadTempData(controllerContext); //如果从Session中获取的值不为null,则将Session获取的值赋值给_data(将上次没使用的TempData再次添加到集合中)。 //这里的判断providerDictionary != null实际上是判断取值是否出错。 //因为tempDataProvider.LoadTempData(controllerContext)方法从Session中获取值时,如果有值,则返回一个其中有键值对IDictionary<string, object>对象,否则返回一个没有值的IDictionary<string, object>对象 _data = (providerDictionary != null) ? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); //并将TempData的所有key保存到_initialKeys中! _initialKeys = new HashSet<string>(_data.Keys, StringComparer.OrdinalIgnoreCase); //清空_retainedKeys中的值,所以默认情况下,_retainedKeys的值为空 _retainedKeys.Clear(); } }
public class SessionStateTempDataProvider : ITempDataProvider { internal const string TempDataSessionStateKey = "__ControllerTempData"; public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext) { HttpSessionStateBase session = controllerContext.HttpContext.Session; //这里不是判断上下文中是否含有Session的key,应该是请求是否出现异常! if (session != null) { //根据key=__ControllerTempData去服务器的Session中获取值 Dictionary<string, object> tempDataDictionary = session[TempDataSessionStateKey] as Dictionary<string, object>; //如果Session中保存key为__ControllerTempData的值不为空,表示:上次处理请求时存在么有被使用的TempData,那么就返回这个字典对象(之后再添加到保存TempData的集合中,以供本次请求使用)。 //并将服务器的key=__ControllerTempData的Session移除! if (tempDataDictionary != null) { session.Remove(TempDataSessionStateKey); return tempDataDictionary; } } return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); } }
public class DependencyResolver { //静态字段,所以在程序执行前就已经执行了该类的构造函数。 private static DependencyResolver _instance = new DependencyResolver(); private IDependencyResolver _current; private CacheDependencyResolver _currentCache; internal static IDependencyResolver CurrentCache { get { return _instance.InnerCurrentCache; } } internal IDependencyResolver InnerCurrentCache { get { return _currentCache; } } //构造函数 public DependencyResolver() { InnerSetResolver(new DefaultDependencyResolver()); } //构造函数调用此方法 public void InnerSetResolver(IDependencyResolver resolver) { //参数resolver=new DefaultDependencyResolver() //DefaultDependencyResolver类的功能为:通过反射根据类型创建对象! _current = resolver; _currentCache = new CacheDependencyResolver(_current); } //因为DependencyResolver只在静态字段中进行了实例化,所有这个CacheDependencyResolver也只实例化一次。所以,ConcurrentDictionary字典也只实例化一次,故,在一次请求中这个集合是一直存在的! private sealed class CacheDependencyResolver : IDependencyResolver { private readonly ConcurrentDictionary<Type, object> _cache = new ConcurrentDictionary<Type, object>(); private readonly ConcurrentDictionary<Type, IEnumerable<object>> _cacheMultiple = new ConcurrentDictionary<Type, IEnumerable<object>>(); private readonly Func<Type, object> _getServiceDelegate; private readonly Func<Type, IEnumerable<object>> _getServicesDelegate; private readonly IDependencyResolver _resolver; public CacheDependencyResolver(IDependencyResolver resolver) { _resolver = resolver; //这个委托就是DefaultDependencyResolver实例的GetService方法,利用类型创建实例。 //目的就是当集合中没有类型对应的值(实例)时,就通过DefaultDependencyResolver的GetService方法去再去反射创建实例。 _getServiceDelegate = _resolver.GetService; _getServicesDelegate = _resolver.GetServices; } public object GetService(Type serviceType) { //先根据serviceType去集合的key中找,如果对应的key有值,则直接将值返回。如果没有,则将serviceType作为参数执行_getServiceDelegate委托,然后将执行委托后的返回值(利用类型创建对象)返回,并将此serviceType和执行委托后的返回值添加到集合中! return _cache.GetOrAdd(serviceType, _getServiceDelegate); } public IEnumerable<object> GetServices(Type serviceType) { // Use a saved delegate to prevent per-call delegate allocation return _cacheMultiple.GetOrAdd(serviceType, _getServicesDelegate); } } private class DefaultDependencyResolver : IDependencyResolver { public object GetService(Type serviceType) { if (serviceType.IsInterface || serviceType.IsAbstract) { return null; } try { return Activator.CreateInstance(serviceType); } catch { return null; } } public IEnumerable<object> GetServices(Type serviceType) { return Enumerable.Empty<object>(); } private Func<Type, object> _getService; private Func<Type, IEnumerable<object>> _getServices; public DelegateBasedDependencyResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices) { _getService = getService; _getServices = getServices; } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method might throw exceptions whose type we cannot strongly link against; namely, ActivationException from common service locator")] public object GetService(Type type) { try { return _getService.Invoke(type); } catch { return null; } } public IEnumerable<object> GetServices(Type type) { return _getServices(type); } } }
上述代码,TempDataDictionary类就是程序中使用的TempData的类型,SessionStateTempDataProvider类用于服务器Session中获取上一次没有被使用的TempData的集合,DependencyResolver类在上一节的Controller激活中已经介绍过了,它主要是用于根据类型通过反射创建实例(还具有缓存的功能)。
补充:对于我们定义的TempData["kk"]=value,如果在使用时也是执行的TempDataDictionary类的索引器,在索引器的Set中可看到,如果使用TempData之后,是在【_initialKeys】中将key移除,而不是直接在保存所有TempData的集合【_data】中移除。
猜想:MVC3和MVC4中对TempData的使用不一样
3-2、ActionInvoker.InvokeAction(ControllerContext, actionName)
此代码实现了:Action的执行、过滤器的执行、View的呈现。
由于这部分的内容太多,为避免影响知识点混乱,将再开一篇博文来详细介绍!《白话学习MVC(八)Action的执行2》
3-3、PossiblySaveTempData()
从保存着所有TempData的集合中移除已经被使用的TempData,最后再将所有没有被使用TempData集合保存在key=__ControllerTempData的Session中!以便下次请求中使用。
public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer { protected override void ExecuteCore() { //获取上次处理过程中没有被使用的TempData PossiblyLoadTempData(); try { //从路由数据中获取请求的Action的名字 string actionName = RouteData.GetRequiredString("action"); //ActionInvoker为一个AsyncControllerActionInvoker对象 if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { //将TempData保存到Session中。等待之后将Session的key【__ControllerTempData】发送到响应流中! PossiblySaveTempData(); } } private ITempDataProvider _tempDataProvider; internal IDependencyResolver Resolver { get { return _resolver ?? DependencyResolver.CurrentCache; } set { _resolver = value; } } public ITempDataProvider TempDataProvider { get { if (_tempDataProvider == null) { _tempDataProvider = CreateTempDataProvider(); } return _tempDataProvider; } set { _tempDataProvider = value; } } protected virtual ITempDataProvider CreateTempDataProvider() { //Resolver是Controller的一个属性,返回值为:DependencyResolver.CurrentCache //Resolver.GetService<ITempDataProvider>() 可以不用看,因为返回的是null。这个方法其实先根据key=ITempDataProvider从全局的缓存表中查找值,如果有值,则返回。如果没有,则利用DefaultDependencyResolver对象通过反射 //根据类型ITempDataProvider创建对象。但是由于这里的ITempDataProvider是接口,而DefaultDependencyResolver对象反射创建对象时,如果是接口或抽象类均返回null。详细请看DependencyResolver类! //所以这段代码,返回值就是 new SessionStateTempDataProvider(); return Resolver.GetService<ITempDataProvider>() ?? new SessionStateTempDataProvider(); } internal void PossiblySaveTempData() { if (!ControllerContext.IsChildAction) { //最后一次使用TempData,TempData属性定义在基类ControllerBase中 TempData.Save(ControllerContext, TempDataProvider); } } }
public abstract class ControllerBase : IController { private TempDataDictionary _tempDataDictionary; //此时的ControllerContext是已经封装 请求上下文、路由、当情请求的控制器对象。(在执行ExecuteCore方法之前的Execute方法中设置的!) public ControllerContext ControllerContext { get; set; } //第一次使用TempData属性时(即:PossiblyLoadTempData方法是第一次使用),会创建一个TempDataDictionary对象并赋值给变量_tempDataDictionary,属性的返回值就是该_tempDataDictionary变量。 //以后再使用TempData属性时,返回都是第一次创建的那个TempDataDictionary对象,所以我们在Action方法中使用TempData[""]时,就是调用TempDataDictionary对象的索引器,向 //保存TempData集合添加一个键值对。 //此时不是第一次使用TempData,应该是最后一次了 public TempDataDictionary TempData { get { if (ControllerContext != null && ControllerContext.IsChildAction) { return ControllerContext.ParentActionViewContext.TempData; } if (_tempDataDictionary == null) { //实例化TempDataDictionary,在构造函数中会创建一个不区分key大小写的字典表,并赋值给一个私有字段。 _tempDataDictionary = new TempDataDictionary(); } return _tempDataDictionary; } set { _tempDataDictionary = value; } } }
public class TempDataDictionary : IDictionary<string, object> { private Dictionary<string, object> _data; private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); //没有执行Action之前,对TempData的操作 public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) { //从Session中获取上一次请求中没有被使用的TempData IDictionary<string, object> providerDictionary = tempDataProvider.LoadTempData(controllerContext); //如果从Session中获取的值不为null,则将Session获取的值赋值给_data(将上次没使用的TempData再次添加到集合中)。 //这里的判断providerDictionary != null实际上是判断取值是否出错。 //因为tempDataProvider.LoadTempData(controllerContext)方法从Session中获取值时,如果有值,则返回一个其中有键值对IDictionary<string, object>对象,否则返回一个没有值的IDictionary<string, object>对象 _data = (providerDictionary != null) ? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); //并将TempData的所有key保存到_initialKeys中! _initialKeys = new HashSet<string>(_data.Keys, StringComparer.OrdinalIgnoreCase); //清空_retainedKeys中的值 _retainedKeys.Clear(); } public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider) { //RemoveFromDictionary方法是MVC中为IDictionary<TKey, TValue>的创建的一个扩展方法。 //RemoveFromDictionary方法作用:检查TempData是否被使用过,如果已被使用,则将其在保存所有TempData集合(_data)中移除。 //须知:当使用TempData时,只是在_initialKeys中将key移除,并没有在保存所有TempData集合(_data)中移除(见TempDataDictionary类的索引器) _data.RemoveFromDictionary((KeyValuePair<string, object> entry, TempDataDictionary tempData) => { string key = entry.Key; //只有当前TempDataDictionary对象的_initialKeys和_retainedKeys中都不含有_data中的key时,委托才返回true。 //_initialKeys和_retainedKeys初始化都是在 Load 方法中,_initialKeys中的值为所有TempData的key,而_retainedKeys是为空的集合 //目前所知,因为retainedKeys是为空的集合,所以tempData._retainedKeys.Contains(key)为:false //而当使用TempData时,只是在_initialKeys中将key移除。所以,如果是被使用过的TempData,则tempData._initialKeys.Contains(key)为:false,否则都为true //最终得出:如果当前循环的TempData被使用了,则返回ture,否则返回false return !tempData._initialKeys.Contains(key)&& !tempData._retainedKeys.Contains(key); }, this); //将所有没有被使用的TempData的集合添加到服务器Session中,以便下次请求再使用 tempDataProvider.SaveTempData(controllerContext, _data); } }
internal static class DictionaryExtensions { //此时的TState类型为:TempDataDictionary public static void RemoveFromDictionary<TKey, TValue, TState>(this IDictionary<TKey, TValue> dictionary, Func<KeyValuePair<TKey, TValue>, TState, bool> removeCondition, TState state) { Contract.Assert(dictionary != null); Contract.Assert(removeCondition != null); int removeCount = 0; TKey[] keys = new TKey[dictionary.Count]; //循环所有的TempData,让每个循环项执行委托removeCondition foreach (var entry in dictionary) { //对于委托执行removeCondition如果当前循环的TempData被使用了,则返回ture,否则返回false if (removeCondition(entry, state)) { //将已经被使用的TempData的key添加到keys数组中 //removeCount++表示已经被使用的TempData个数。 keys[removeCount] = entry.Key; removeCount++; } } //循环被删除的TempData个数,将该键值对在保存所有TempData的集合中移除。 for (int i = 0; i < removeCount; i++) { dictionary.Remove(keys[i]); } } }
public class SessionStateTempDataProvider : ITempDataProvider { internal const string TempDataSessionStateKey = "__ControllerTempData"; public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } HttpSessionStateBase session = controllerContext.HttpContext.Session; bool isDirty = (values != null && values.Count > 0); //这里的判断:不是没有Sesson,因为即使是第一次请求到来时(无任何Session),这个对象也不为空。 //而是根据上下文由于创建HttpSessionStateBase对象时出问题,该对象为空。 if (session == null) { if (isDirty) { throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled); } } //HttpSessonStateBase对象创建没问题 else { //如果保存TempData的字典类实例中有值(也就是定义的TempData没有在view中被全使用),将值设置给key为__ControllerTempData的Session。 if (isDirty) { session[TempDataSessionStateKey] = values; } //TempData在view中全部已被使用 else { //并且Session中该可以__ControllerTempData还有对应的值 //如果用户在Action中自己定义一个key为__ControllerTempData的Session,这里移除Session中的这个值,防止了将这个key发送到相应流,以伪造成未被利用的TempData if (session[TempDataSessionStateKey] != null) { session.Remove(TempDataSessionStateKey); } } } } }
上述代码,PossiblySaveTempData方法执行 TempData.Save(ControllerContext, TempDataProvider)来完成所有的功能,TempData得到的之前创建的TempDataDictionary对象(该对象的_data字段保存这定义的所有TempData键值对),参数TempDataProvider是创建的SessionStateTempDataProvider对象(该对象的SaveTempData方法的作用就是将没有被使用的TempData保存到Session中)。所以,TempDataDictionary对象的Save方法首先去遍历所有的TempData,检查TempData是否被使用过了,如果已被使用,则将该TempData在保存所有TempData的集合中移除,最后执行SessionStateTempDataProvider对象的SaveTempData方法将经过处理后的集合添加到Session中,以便这些TempData在写一次请求中使用!
如3-1中补充到,对于我们定义的TempData["kk"]=value,如果在使用时是执行的TempDataDictionary类的索引器,在索引器的Set中可看到,如果使用TempData之后,是在【_initialKeys】中将key移除,而不是直接在保存所有TempData的集合【_data】中移除。此处做的就是在根据【_initialKeys】将被使用的TempData在【_data】中移除。
以上就是全部内容,如有不合适之处请指教!
遗留问题:
1、如何利用各扩展点暂没有分析。
疑问:
1、既然利用DependencyResolver通过反射创建对象时,接口和抽象类都不可以,MVC中为什么还在使用GetService方法使用接口来创建对象呀?明知是Null
更正:
1、以上的Controller类的继承关系是MVC5中的,在MVC4中应该是如下,MVC4中没有引进IAuthenticationFilter过滤器。
public abstract class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer { ... }