回顧:傳統路由是如何提供的?
我們知道最終匹配的路由數據是保存在RouteData中的,而RouteData通常又是封裝在RequestContext中的,他們是在哪里被創建的呢?沒錯,回到了UrlRoutingModule,我們知道UrlRoutingModule通過注冊HttpApplication的PostResolveRequestCache方法來分發IHttpHandler決定ASP.NET請求最終交給哪個IHttpHandler去處理的。其實在這之前,首先會通過當前請求的HttpContextBase解析虛擬路徑,匹配路由。然而這個工作是RouteCollection完成的。
public virtual void PostResolveRequestCache(HttpContextBase context) { RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData == null) { return; } //省略了注冊IHttpHandler的代碼 }
反編譯查看Route的GetRouteData方法
public RouteData GetRouteData(HttpContextBase httpContext) { if (httpContext == null) { throw new ArgumentNullException("httpContext"); } if (httpContext.Request == null) { throw new ArgumentException(SR.GetString("RouteTable_ContextMissingRequest"), "httpContext"); } if (base.Count == 0) { return null; } bool flag = false; bool flag2 = false; if (!this.RouteExistingFiles) { flag = this.IsRouteToExistingFile(httpContext); flag2 = true; if (flag) { return null; } } using (this.GetReadLock()) { foreach (RouteBase current in this) { RouteData routeData = current.GetRouteData(httpContext); if (routeData != null) { RouteData result; if (!current.RouteExistingFiles) { if (!flag2) { flag = this.IsRouteToExistingFile(httpContext); } if (flag) { result = null; return result; } } result = routeData; return result; } } } return null; }
這個方法乍一看也太復雜了吧,再看一下還是復雜,花了點時間理清了,就是這樣子的
一句話解釋:是文件都(RouteCollection和RouteBase)路由文件才成功匹配,不是文件只要路由規則匹配就成功匹配
正題:特性路由的初始化
特性路由是如何添加到路由系統中的呢?主要由AttributeRoutingMapper這個靜態類實現的,先來跟隨着源碼一一道來。
在程序啟動的時候,MVC會通過DefaultControllerFactory找到所有的控制器類型,為每一個控制器內注冊的特性路由創建一個或多個Route。
public static void MapAttributeRoutes(RouteCollection routes, IInlineConstraintResolver constraintResolver) { DefaultControllerFactory typesLocator = DependencyResolver.Current.GetService<IControllerFactory>() as DefaultControllerFactory ?? ControllerBuilder.Current.GetControllerFactory() as DefaultControllerFactory ?? new DefaultControllerFactory(); //獲得所有的控制器類型 IReadOnlyList<Type> controllerTypes = typesLocator.GetControllerTypes(); //開始注冊 MapAttributeRoutes(routes, controllerTypes, constraintResolver); }
尋找特新路由的過程中會把所有創建的Route都放在SubRouteCollection這個RouteEntry只讀集合中(RouteEntry封裝一個Route和一個string類型的Name),最后MVC會將SubRouteCollection封裝成一個Route對象(實際上是RouteBase的子類RouteCollectionRoute),添加到RouteCollection中。
public static void MapAttributeRoutes(RouteCollection routes, IEnumerable<Type> controllerTypes, IInlineConstraintResolver constraintResolver) { SubRouteCollection subRoutes = new SubRouteCollection(); AddRouteEntries(subRoutes, controllerTypes, constraintResolver); IReadOnlyCollection<RouteEntry> entries = subRoutes.Entries; if (entries.Count > 0) { RouteCollectionRoute aggregrateRoute = new RouteCollectionRoute(subRoutes); routes.Add(aggregrateRoute); } }
那她是如何根據控制器來創建Route的呢?先要認識幾個類型,ControllerDescriptor(控制器描述器),可以根據它獲取應用在控制器上的各種特性,類似的還有ActionDescriptor(方法描述器,注意區分MethodInfo),還有ActionMethodSelector(方法選擇器),方法選擇器有兩個屬性,分別是DirectRouteMethods表示應用了特性路由的方法,StandardRouteMethods表示普通的方法。
具體處理每一個控制器類型的時候。會獲取該控制器的方法選擇器,對它的DirectRouteMethods和StandardRouteMethods分別處理。但兩者有一個共同第一步,就是獲取控制器前綴和Area前綴。控制器前綴直接從RoutePrefix特性中獲取,Area前綴的話,會從RouteAreaAttribute獲取,如果沒有應用RouteAreaAttribute,那么就是控制器類型所在命名空間的最后一段。
internal static void AddRouteEntries(SubRouteCollection collector, ReflectedAsyncControllerDescriptor controller, IInlineConstraintResolver constraintResolver) { string prefix = GetRoutePrefix(controller); RouteAreaAttribute area = controller.GetAreaFrom(); string areaName = controller.GetAreaName(area); string areaPrefix = area != null ? area.AreaPrefix ?? area.AreaName : null; AsyncActionMethodSelector actionSelector = controller.Selector; //處理應用了特性路由的Action foreach (var method in actionSelector.DirectRouteMethods) { //... } //處理沒有應用特性路由的Action foreach (var method in actionSelector.StandardRouteMethods) { //... } }
處理DirectRouteMethods
首先會根據該Action的方法描述器獲取應用的所有特性路由,針對每一個特性路由創建一個RouteEntry,這個核心任務是由DirectRouteBuilder的Builder方法實現的。DirectRouteBuilder包含了所有和Route相關的屬性,同時還包含一個ActionDescriptor數組,大多數屬性我們都是熟悉的,有必要介紹一下_actions字段,這個字段保存的是這條特性路由是應用在哪些方法上的(對於應用了特性路由的Action來說只有一個),Precedence是模板根據約束生成的一個字段,用來排序。
internal class DirectRouteBuilder : IDirectRouteBuilder { private readonly ActionDescriptor[] _actions; private readonly bool _targetIsAction; private string _template; public DirectRouteBuilder(IReadOnlyCollection<ActionDescriptor> actions, bool targetIsAction) { if (actions == null) { throw new ArgumentNullException("actions"); } _actions = actions.ToArray(); _targetIsAction = targetIsAction; } public string Name { get; set; } public string Template { get { return _template; } set { ParsedRoute = null; _template = value; } } public RouteValueDictionary Defaults { get; set; } public RouteValueDictionary Constraints { get; set; } public RouteValueDictionary DataTokens { get; set; } public int Order { get; set; } public decimal Precedence { get; set; } public IReadOnlyCollection<ActionDescriptor> Actions { get { return _actions; } } public bool TargetIsAction { get { return _targetIsAction; } } public virtual RouteEntry Build() { //省略 } }
對於應用了特性路由的Action來說。會找到所有應用在該Action上的特性路由,針對每一個特新路由創建相應的RouteEntry,而這一過程就是有RouteAttribute本身完成的。在這之前,會將特新路由的一些列屬性封裝到DirectRouteFactoryContext中。這里IDirectRouteFactory的唯一實現就是RouteAttribute。(不知道微軟為什么不在這里直接寫一個靜態的Build方法,而要弄出DirectRouteBuilder的Builder和DirectRouteFactoryContext這兩個類,個人覺得會簡單很多)
internal static RouteEntry CreateRouteEntry(string areaPrefix, string prefix, IDirectRouteFactory factory, IReadOnlyCollection<ActionDescriptor> actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) { DirectRouteFactoryContext context = new DirectRouteFactoryContext(areaPrefix, prefix, actions, constraintResolver, targetIsAction); RouteEntry entry = factory.CreateRoute(context); return entry; }
RouteAttribute的CreateRoute包含兩本分,創建一個IDirectRouteBuilder,然后在調用他的Build方法。這里也表明了,我們可以通過RouteAttribute顯示指定路由的優先級。
RouteEntry IDirectRouteFactory.CreateRoute(DirectRouteFactoryContext context) { IDirectRouteBuilder builder = context.CreateBuilder(Template); builder.Name = Name; builder.Order = Order; return builder.Build(); }
CreateBuilder方法主要做了兩件事,給路由添加控制器前綴,清除路由模板的內聯約束。接下來看一下最核心的Build方法。
(1)將actions(類型為ActionDescriptor)存入DataTokens的MS_DirectRouteActions鍵
(2)如果定義路由的時候給定了Order,則將Order存入DataTokens的MS_DirectRouteOrder鍵
(3)如果特性路由的模板和約束條件改變了precedence值,也將它寫入DataTokens的MS_DirectRoutePrecedence鍵
(4)如果actions對應的控制器描述器為同一個控制器類型,那就表示這條特性路由的控制器是有默認值的
(5)如果目標是應用了特新路由的Action,並且actions只有一個,name這條特性路由的Action也是有默認值的,同時將true寫入DataTokens的MS_DirectRouteTargetIsAction鍵
(6)如果控制器使用的使用了RouteAreaAttribute特性,那么也將命名空間相關的值寫入DataTokens中,UseNamespaceFallback用於以后控制器的查詢。
public virtual RouteEntry Build() { if (ParsedRoute == null) { ParsedRoute = RouteParser.Parse(Template); } RouteValueDictionary defaults; defaults = Copy(Defaults) ?? new RouteValueDictionary(); RouteValueDictionary constraints = Copy(Constraints); RouteValueDictionary dataTokens = Copy(DataTokens) ?? new RouteValueDictionary(); dataTokens[RouteDataTokenKeys.Actions] = _actions; ControllerDescriptor controllerDescriptor = GetControllerDescriptor(); RouteAreaAttribute area = controllerDescriptor.GetAreaFrom(); string areaName = controllerDescriptor.GetAreaName(area); if (areaName != null) { dataTokens[RouteDataTokenKeys.Area] = areaName; dataTokens[RouteDataTokenKeys.UseNamespaceFallback] = false; Type controllerType = controllerDescriptor.ControllerType; if (controllerType != null) { dataTokens[RouteDataTokenKeys.Namespaces] = new[] { controllerType.Namespace }; } } Route route = new Route(Template, defaults, constraints, dataTokens, routeHandler: null); ConstraintValidation.Validate(route); return new RouteEntry(Name, route); }
處理StandardRouteMethods
對於沒有應用特性路由的Action來說,和應用了特新路由的Action只有兩點不一樣,一個是RouteAttribute來說,他們的全都是Controller上的RouteAttribute提供的,還有就是方法描述器,是所有Action,為什么這樣子呢?首先應用了特性路由的Action生成的Route會默認指定他的Action名稱,由於現在的Action沒有指定特性路由,全都是依靠Controller類型提供了,所以就不難理解了。
特性路由的提供機制
由於特性路由最終是以一個RouteCollectionRoute添加到RouteTable中的,所以他的重寫了GetRouteData。這個類比較有意思,它本身是一個路由,但它卻包括了一系列(特性)路由。
(1)在集合中找到所有和當前虛擬路徑匹配的路由
(2)給RouteData的Values設置鍵MS_DirectRouteMatches,值為所有匹配的路由
(3)取出匹配的路由的第一個,如果有controller默認值,就給RouteData的Values設置一個controller的值
internal class RouteCollectionRoute : RouteBase, IReadOnlyCollection<RouteBase> { private readonly IReadOnlyCollection<RouteBase> _subRoutes; public override RouteData GetRouteData(HttpContextBase httpContext) { List<RouteData> matches = new List<RouteData>(); foreach (RouteBase route in _subRoutes) { var match = route.GetRouteData(httpContext); if (match != null) { matches.Add(match); } } return CreateDirectRouteMatch(this, matches); } public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { return null; } public static RouteData CreateDirectRouteMatch(RouteBase route, List<RouteData> matches) { if (matches.Count == 0) { return null; } else { var routeData = new RouteData(); routeData.Route = route; routeData.RouteHandler = new MvcRouteHandler(); ControllerDescriptor controllerDescriptor = matches[0].GetTargetControllerDescriptor(); if (controllerDescriptor != null) { routeData.Values[RouteDataTokenKeys.Controller] = controllerDescriptor.ControllerName; } return routeData; } } }
可能也許有人會有疑問,特性路由最后也沒有匹配一個具體的Route,事實上就是這樣子的,RouteCollectionRoute就是一個具體的路由,在控制器的匹配工作上還要用到這里的知識,所以任重而道遠哈,不過說回來這篇博客是我有史以來耗時最長的一個了。。鼓勵一下自己!!!