最近抽空看了一下ASP.NET MVC的部分源碼,順帶寫篇文章做個筆記以便日後查看。 在UrlRoutingModule模塊中,將請求處理程式映射到了MvcHandler中,因此,說起Controller的激活,首先要從MvcHandler入手,MvcHandler實現了三個介面:IHttpAsyn ...
最近抽空看了一下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
下麵看一下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();
}
這兩個方法沒什麼多說的,只是在刪除數據的時候同時刪除其對應的狀態。