ASP.NET MVC Controller的激活


最近抽空看了一下ASP.NET MVC的部分源碼,順帶寫篇文章做個筆記以便日后查看。

在UrlRoutingModule模塊中,將請求處理程序映射到了MvcHandler中,因此,說起Controller的激活,首先要從MvcHandler入手,MvcHandler實現了三個接口:IHttpAsyncHandler, IHttpHandler, IRequiresSessionState。 其處理邏輯主要實現在同步和異步的ProcessRequest方法中,總的來說,該方法在執行的時候,大致經歷以下幾個步驟:

  1. 預處理(在響應頭中添加版本信息並去除未賦值的可選路由參數)
  2. 通過ControllerBuilder獲取ControlerFactory,並使用Controller工廠創建Controller
  3. 根據是否是異步處理,調用Controller中相應的方法(ExecuteCore或BeginExecute)
  4. 釋放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");

通過以上創建對象的過程可以得知,有兩種方式可以替換默認的對象提供器:

  1. 替換默認的DependencyResolver,可以通過DependencyResolver類的靜態方法SetResolver方法來實現:

     CustomDependencyResolver customResolver = new  CustomDependencyResolver();
     DependencyResolver.SetResolver(customResolver);
    

    將以上語句放在程序啟動的地方,例如:Application_Start

  2. 通過前面介紹的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類型以后,接下來就要進行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();
}

這兩個方法沒什么多說的,只是在刪除數據的時候同時刪除其對應的狀態。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM