這些天一直在學習MVC的源碼,深刻學習後,發現本身不懂的真的是愈來愈多,爲何會有上一篇博客呢?在學習DefaultControllerFactory提供控制器的過程當中,先是被路由中的MS_DirectRouteMatches這個Toeken值困惑,我知道他表示一個特性路由,但我想它是哪裏來的呢?因而就有了上一篇博文,可是這兩天就是和路由幹上了,我知道還有一個區域路由的註冊工做,仍是Application_Start的的第一行代碼,因而乎就有了這一篇博文。小程序
咱們新建一個名稱爲Admin的Area,VS生成下面的代碼。緩存
public class AdminAreaRegistration : AreaRegistration { public override string AreaName { get { return "Admin"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "Admin_default", "Admin/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional } ); } }
咱們先來看AreaRegistration這個抽象類,實際上,它只有一個核心功能,就是RegisterAllAreas,獲取全部繼承它的子類類型,而後建立它,在爲他建立一個AreaRegistrationContext,在調用它的RegisterArea方法。app
public abstract class AreaRegistration { private const string TypeCacheName = "MVC-AreaRegistrationTypeCache.xml"; public abstract string AreaName { get; } internal static void RegisterAllAreas(RouteCollection routes, IBuildManager buildManager, object state) { List<Type> areaRegistrationTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(TypeCacheName, IsAreaRegistrationType, buildManager); foreach (Type areaRegistrationType in areaRegistrationTypes) { AreaRegistration registration = (AreaRegistration)Activator.CreateInstance(areaRegistrationType); registration.CreateContextAndRegister(routes, state); } } internal void CreateContextAndRegister(RouteCollection routes, object state) { AreaRegistrationContext context = new AreaRegistrationContext(AreaName, routes, state); string thisNamespace = GetType().Namespace; if (thisNamespace != null) { context.Namespaces.Add(thisNamespace + ".*"); } RegisterArea(context); } public abstract void RegisterArea(AreaRegistrationContext context); }
爲何要有AreaRegistrationContext這個類型呢?假如沒有它,AreaRegistration子類建立完成時,就能夠直接註冊了,咱們的AdminAreaRegistration的RegisterArea方法徹底能夠經過RouteCollection再重載一個MapRoute方法用於Area路由的註冊。像下面這個樣子。ide
public override void RegisterArea(RouteCollection routes) { routes.MapRoute( "Admin_default", "Admin/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional } ); }
這樣不是很好麼?跟隨着源碼,詳細瞧一瞧這個AreaRegistrationContext性能
這個類本質上只有一個屬性,那就是命名空間。學習
public class AreaRegistrationContext { private readonly HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public AreaRegistrationContext(string areaName, RouteCollection routes, object state) { } public string AreaName { get; private set; } public ICollection<string> Namespaces { get { return _namespaces; } } public RouteCollection Routes { get; private set; } public object State { get; private set; } public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces) { } }
咱們回到核心的RegisterAllAreas方法中。測試
private static bool IsAreaRegistrationType(Type type) { return typeof(AreaRegistration).IsAssignableFrom(type) && type.GetConstructor(Type.EmptyTypes) != null; } internal static void RegisterAllAreas(RouteCollection routes, IBuildManager buildManager, object state) { List<Type> areaRegistrationTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(TypeCacheName, IsAreaRegistrationType, buildManager); foreach (Type areaRegistrationType in areaRegistrationTypes) { AreaRegistration registration = (AreaRegistration)Activator.CreateInstance(areaRegistrationType); registration.CreateContextAndRegister(routes, state); } }
經過TypeCacheUtil.GetFilteredTypesFromAssemblies獲取出來的類型必須符合IsAreaRegistrationType委託,(AreaRegistration).IsAssignableFrom(type)不難理解,必須是AreaRegistration的子類,那type.GetConstructor(Type.EmptyTypes)呢?其實一開始我也不明白它是什麼意思,後來經過Console寫了個小程序測試了下。ui
class TA { TA() {} } class TB { TB() {} TB(int i) {} } class TC {} class Program { static void Main(string[] args) { Type ta = typeof(TA); var tac = ta.GetConstructor(Type.EmptyTypes); Type tb = typeof(TB); var tbc = tb.GetConstructor(Type.EmptyTypes); Type tc = typeof(TC); var tcc = tc.GetConstructor(Type.EmptyTypes); Console.WriteLine("類TA :" + (tac != null)); Console.WriteLine("類TB :" + (tbc != null)); Console.WriteLine("類TC :" + (tcc != null)); } }
輸出:this
類TA :Falseurl
類TB :False
類TC :True
請按任意鍵繼續. . .
咱們能夠明白了,也就是咱們的AdminAreaRegistration不能有構造器(Visual Studio生成的確實沒有構造器)。可是這裏爲何要這樣約定呢?確實想不通,咱們先繼續回到剛剛的TypeCacheUtil.GetFilteredTypesFromAssemblies方法。首先,會嘗試從緩存中獲取類型,與往常不一樣的是,這裏緩存的格式是xml文件,緩存的緣由應該很容易理解,頻繁反射會形成性能的影響,改良反射的方式有多種,這裏咱們學到了一種,緩存。關於TypeCacheSerializer如何工做和ReadTypesFromCache具體是如何實現的這裏就不去看了,主要就是一些關於Stream和XmlDocument這兩個類的操做。可是有必要提一下IBuildManager這個接口。在MVC中的實現者是BuildManagerWrapper,內部實際使用的是BuildManager(位於System.Web.Compilation),關於它的詳細資料少之又少,只知道主要負責站點的動態編譯和程序集的管理。咱們知道能夠經過AppDomain來獲取應用程序相關的程序集,但這裏爲何用BuilderManager呢?想必必有什麼不一樣!
private static IEnumerable<Type> FilterTypesInAssemblies(IBuildManager buildManager, Predicate<Type> predicate) { // Go through all assemblies referenced by the application and search for types matching a predicate IEnumerable<Type> typesSoFar = Type.EmptyTypes; ICollection assemblies = buildManager.GetReferencedAssemblies(); foreach (Assembly assembly in assemblies) { Type[] typesInAsm; try { typesInAsm = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { typesInAsm = ex.Types; } typesSoFar = typesSoFar.Concat(typesInAsm); } return typesSoFar.Where(type => TypeIsPublicClass(type) && predicate(type)); }
咱們看到這裏用它獲取全部的應用程序集。在foreach前打一個斷點。藉助即時窗口咱們能夠和AppDomain獲取的程序集進行一個比較。
string[] Arr1 = assemblies.Cast().Select(a=>a.FullName).ToArray();
已計算表達式,表達式沒有值
string[] Arr2 = AppDomain.CurrentDomain.GetAssemblies().Select(a=>a.FullName).ToArray();
已計算表達式,表達式沒有值
Arr1.Length
36
Arr2.Length
42
string[] Arr3 = Arr2.Except(Arr1).ToArray();
已計算表達式,表達式沒有值
Arr3
{string[6]}
[0]: "System.Runtime.Caching, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
[1]: "Microsoft.Build.Utilities.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
[2]: "Microsoft.JScript, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
[3]: "Microsoft.VisualStudio.Web.PageInspector.Runtime, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
[4]: "Microsoft.VisualStudio.Web.PageInspector.Tracing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
[5]: "Microsoft.VisualStudio.Debugger.Runtime, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
這裏列出的幾個命名空間我也不熟悉,可是大體能夠了解,使用AppDomain返回的程序集是當前AppDomain下全部程序中顯示使用過的類型所在的程序集(若是你對AppDomain有了解,但願不要被我誤解),而BuildManager返回的是和程序運行環境甚至配置(調試)相關的程序集,咱們能夠這麼理解,BuildManager提供更強大的功能,能夠負責站點的動態編譯和程序集的管理。關於AreaRegistration類型的緩存咱們基本已經瞭解,拿到全部的AreaRegistration類型後,咱們針對每個進行一次路由配置工做。
internal static void RegisterAllAreas(RouteCollection routes, IBuildManager buildManager, object state) { List<Type> areaRegistrationTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(TypeCacheName, IsAreaRegistrationType, buildManager); foreach (Type areaRegistrationType in areaRegistrationTypes) { AreaRegistration registration = (AreaRegistration)Activator.CreateInstance(areaRegistrationType); registration.CreateContextAndRegister(routes, state); } }
具體的
internal void CreateContextAndRegister(RouteCollection routes, object state) { AreaRegistrationContext context = new AreaRegistrationContext(AreaName, routes, state); string thisNamespace = GetType().Namespace; if (thisNamespace != null) { context.Namespaces.Add(thisNamespace + ".*"); } RegisterArea(context); }
咱們來思考一下,這個thisNamespace會是什麼值呢?因爲這裏的GetType目標是AdminAreaRegistration,(在我這裏)因此是Mvc_Web.Areas.Admin,而後會被添加到這裏的AreaRegistrationContext的Namespace屬性中,而後調用子類重寫的RegisterArea方法,最終添加到RouteCollection中,咱們看最後調用的MapRoute方法。
public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces) { if (namespaces == null && Namespaces != null) { namespaces = Namespaces.ToArray(); } Route route = Routes.MapRoute(name, url, defaults, constraints, namespaces); route.DataTokens[RouteDataTokenKeys.Area] = AreaName; bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0); route.DataTokens[RouteDataTokenKeys.UseNamespaceFallback] = useNamespaceFallback; return route; }
最重要的是倒數第二行和倒數第三行,他和控制器的匹配有關,其實根據UseNamespaceFallback這個也很容易理解,若是咱們的AdminAreaRegistration沒有命名空間,那就容許它退回(到其餘地方找)。
路由這塊終於結束了,任重道遠啊,鼓勵一下本身,加油!!!