最近抽空看了一下ASP.NET MVC的部分源碼,順帶寫篇文章作個筆記以便往後查看。緩存
在UrlRoutingModule模塊中,將請求處理程序映射到了MvcHandler中,所以,提及Controller的激活,首先要從MvcHandler入手,MvcHandler實現了三個接口:IHttpAsyncHandler, IHttpHandler, IRequiresSessionState。 其處理邏輯主要實如今同步和異步的ProcessRequest方法中,總的來講,該方法在執行的時候,大體經歷如下幾個步驟:閉包
其中第一步在ProcessRequestInit方法中進行處理,本文主要是分析第兩步中的controller是如何建立出來的。less
Controller的建立是經過ControllerFactory實現的,而ControllerFactory的建立又是在ControllerBuilder中完成的,所以咱們先了解一下ControllerBuilder的工做原理。異步
從源碼中能夠看出,在ControllerBuilder類中,並無直接實現對controller工廠的建立,ControllerFactory的建立其實是委託給一個繼承自IResolver接口的SingleServiceResolver類的實例來實現的,這一點從GetControllerFactory方法中能夠看出,它是經過調用SingleServiceResolver對象的Current屬性來完成controller工廠的建立的。ide
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類的工廠委託字段,post
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對象中),而是經過委託來進行了包裝,這樣就會造成一個閉包,在閉包中進行引用,以下所示:ui
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方法也就達到了替換默認工廠的目的。this
閉包引用測試代碼:
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等,實現了對多種對象的建立。
該類實現了IResolver接口,主要用來提供指定類型的實例,在SingleServiceResolver類中有三種方式來建立對象:
一、private Lazy<TService> _currentValueFromResolver; //內部調用_resolverThunk 二、private Func<TService> _currentValueThunk; //委託方式 三、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>( //一、看泛型接口,若是爲接口或抽象類,則IDependencyResolver方式失效 () => _factoryThunk(), //二、看_factoryThunk()是否返回null,若是是則委託方式失效 new DefaultControllerFactory { ControllerBuilder = this }, //三、以上兩種都失效,則使用該默認值 "ControllerBuilder.GetControllerFactory");
經過以上建立對象的過程能夠得知,有兩種方式能夠替換默認的對象提供器:
替換默認的DependencyResolver,能夠經過DependencyResolver類的靜態方法SetResolver方法來實現:
CustomDependencyResolver customResolver = new CustomDependencyResolver(); DependencyResolver.SetResolver(customResolver);
將以上語句放在程序啓動的地方,例如:Application_Start
經過前面介紹的ControllerBuilder類的SetControllerFactory方法
注:第一種方式的優先級更高。
經過ControllerBuilder建立出ControllerFactory對象後,下面就要利用該對象完成具體Controller的建立,ControllerFactory都實現了IControllerFactory接口,經過實現CreateController
方法完成對Controller的實例化,CreateController的內部邏輯很是簡單,就兩步:獲取Controller類型,而後建立Controller對象。
根據控制器名稱獲取控制器Type的過程,有必要深刻了解一下,以便於咱們在往後遇到相關問題的時候可以更好的進行錯誤定位。獲取類型的邏輯都封裝在GetControllerType方法中,該過程根據路由數據中是否含有命名空間信息,分爲三個階段進行類型搜索:
UseNamespaceFallback==true
,此時會獲取ControllerBuilder中設置的命名空間信息,利用該信息和控制器名稱在緩存中進行類型搜索,若是找到惟一一個類型,則返回該類型,找到多個直接拋異常所以,命名空間的優先級是:RouteData > ControllerBuilder
在緩存中搜索類型的時候,若是是第一次查找,會調用ControllerTypeCache.EnsureInitialized方法將保存在硬盤中的Xml緩存文件加載到一個字典類型的內存緩存中。若是該緩存文件不存在,則會遍歷當前應用引用的全部程序集,找出全部public權限的Controller類型(判斷條件:實現IController接口、非抽象類、類名以Controller結尾),而後將這些類型信息進行xml序列化,生成緩存文件保存在硬盤中,以便於下次直接從緩存文件中加載,同時將類型信息分組以字典的形式緩存在內存中,提升搜索效率,字典的key爲ControllerName(不帶命名空間)。
Controller類型搜索流程以下圖所示:
獲取Controller類型之後,接下來就要進行Controller對象的建立。在DefaultControllerFactory類的源碼中能夠看到,同ControllerBuilder相似,該類的構造函數中也實例化了一個SingleServiceResolver對象,按照以前介紹的方法,咱們一眼就能夠看出,該對象是利用默認值的方式提供了一個DefaultControllerActivator對象。
_activatorResolver = activatorResolver ?? new SingleServiceResolver<IControllerActivator>( //一、泛型爲接口,IDependencyResolver方式失效 () => null, //二、返回了null,委託方式失效 new DefaultControllerActivator(dependencyResolver), //三、以上兩種方式均失效,則使用該提供方式 "DefaultControllerFactory constructor");
實際上DefaultControllerFactory類僅實現了類型的搜索,對象的真正建立過程須要由DefaultControllerActivator類來完成,默認狀況下,DefaultControllerActivator建立Controller的過程是很簡單的,由於它實際上使用的是一個叫作DefaultDependencyResolver的類來進行Controller建立的,在該類內部直接調用Activator.CreateInstance(serviceType)
方法完成對象的實例化。
從DefaultControllerFactory和DefaultControllerActivator這兩個類的建立過程能夠發現,MVC提供了多種方式(IDependencyResolver方式、委託方式 、默認值方式)來提供對象,所以在對MVC相關模塊進行擴展的時候,也有多種方式能夠採用。
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的實現原理。
首先看下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
一、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對應的數據了,即:數據只能在一次請求中使用。
二、Add
public void Add(string key, object value) { _data.Add(key, value); _initialKeys.Add(key); }
添加數據的時候在_initialKeys中打上標記,代表該key對應的數據能夠被訪問。
三、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後,該數據就不會被刪除了,即:能夠在多個請求中被訪問了。
四、Peek
public object Peek(string key) { object value; _data.TryGetValue(key, out value); return value; }
從代碼中能夠看出,該方法在讀取數據的時候,僅僅是從_data中進行了獲取,並無移除_initialKeys集合中對應的key,所以經過該方法讀取數據不影響數據的狀態,該條數據依然能夠在下一次請求中被使用。
五、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(); }
這兩個方法沒什麼多說的,只是在刪除數據的時候同時刪除其對應的狀態。