當用戶經過URL訪問網站時,要把用戶請求的URL映射到正確的應用程序的操做上。那麼如何實現這個映射--Routing(路由)。html
路由並不專屬於Asp.Net MVC
,而是創建在Asp.Net Framework
之上的一個組件,因此全部依賴Asp.Net Framework
的均可以使用路由。如WebForms,API等,可是Asp.Net MVC 和路由密切相關。正則表達式
圖:路由關係圖mvc
Asp.Net是一個管道模型,一個Http請求先通過
HttpModule
,再經過HttpHandlerFactory
,建立一個對應的HttpHandler
處理對應的請求。因此對Asp.Net的全部的擴展也是經過註冊這些管道事件來實現的。由於路由是創建在Asp.Net Framework
之上的,因此路由也是註冊實現了管道事件。可是是經過註冊HttpModule
的PostResolveRequestCache
事件來實現的。app
由於:asp.net
若是把請求的管道模型比做一個運行的火車的話,
HttpHandler
是請求火車的目的地。HttpModule
是一個沿途的站點,要在終點前分析好這個請求是到哪一個目的地。網站
HttpHandler
多用來處理響應處理。HttpModule
多用來處理通用性和響應內容無關的功能。
小結:ui
路由就是一個實現了IHttpModule
接口的UrlRoutingModule
的HttpModule
,在管道事件中攔截請求,分析Url,匹配路由,再交給HttpHandler
處理的過程。this
上述認識到路由是經過實現了接口IHttpModule
的類--UrlRoutingModule
來註冊管道事件,在該類中實現了請求攔截,路由匹配,建立指定HttpHandler。url
因此路由組件中UrlRoutingModule
就是是關鍵。.net
經過該類的源代碼能夠發現。UrlRoutingModule
註冊了PostResolveRequestCache
事件。註冊該事件,純粹是由於要在HttpHandler
目的地建立以前執行路由。由於在管道事件中PostMapRequestHandler
事件是把請求交給HttpHandler
來處理。而PostResolveRequestCache
在該事件以前。(Asp.Net管道事件)
//UrlRoutingModule源碼 ... //註冊事件PostResolveRequestCache application.PostResolveRequestCache += OnApplicationPostResolveRequestCache; ...
查看UrlRoutingModule
代碼,發現該類的一個PostResolveRequestCache
方法,實現了路由的工做。
// UrlRoutingModule的本地方法 public virtual void PostResolveRequestCache(HttpContextBase context) { // 根據HttpContext的Url匹配路由對象,該對象包含了Controller,Action和參數 // Match the incoming URL against the route table RouteData routeData = RouteCollection.GetRouteData(context); // Do nothing if no route found if (routeData == null) { return; } //由匹配的路由對象建立一個MVCRouteHandler // If a route was found, get an IHttpHandler from the route's RouteHandler IRouteHandler routeHandler = routeData.RouteHandler; if (routeHandler == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, SR.GetString(SR.UrlRoutingModule_NoRouteHandler))); } // This is a special IRouteHandler that tells the routing module to stop processing // routes and to let the fallback handler handle the request. if (routeHandler is StopRoutingHandler) { return; } //封裝匹配的路由對象和HttpContext,建立新的RequestContext RequestContext requestContext = new RequestContext(context, routeData); // Dev10 766875 Adding RouteData to HttpContext context.Request.RequestContext = requestContext; //獲取MVCHandler IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); if (httpHandler == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentUICulture, SR.GetString(SR.UrlRoutingModule_NoHttpHandler), routeHandler.GetType())); } if (httpHandler is UrlAuthFailureHandler) { if (FormsAuthenticationModule.FormsAuthRequired) { UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); return; } else { throw new HttpException(401, SR.GetString(SR.Assess_Denied_Description3)); } } // Remap IIS7 to our handler context.RemapHandler(httpHandler); }
即:
HttpContext
,路由匹配規則,匹配一個RouteData
對象。RouteData
對象的RouteHandler
獲取IRouteHandler
的MVCRouteHandler
。RouteData
和HttpContext
建立RequestContext
MVCRouteHandler
和3的RequestContext
建立IHttpHandler
-MVCHandler
.HttpHandler
管道事件執行。流程以下圖所示:
以後HttpHandler
的運行能夠參考以下整個生命週期:
Asp.Net MVC 生命週期圖:
Global.asax
的MVCApplication
是管理Asp.Net應用程序生命週期的管道事件的類。在類中實現管道事件或方法會在對應的管道事件中調用。
在App_Start
文件下,新建RouteConfig.cs
文件裏配置路由信息。經過靜態方法RouteCollection.MapRoute()
配置路由信息。
如:
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}");//忽略該模式的URL routes.MapRoute( name: "Default",//路由名稱 url: "{controller}/{action}/{id}",//路由模板 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }//路由默認值,參數id能夠爲空 ); } }
name:爲該路由名稱
url:爲路由模板,{}
是佔位符。
defaults:爲路由默認值
Global.asax
的MVCApplication
繼承HttpApplication
。而HttpApplication
則是管理整個管道週期的實例。在該類中經過註冊事件,或方法能夠在管道事件中被調用。註冊路由到應用程序就是在Application_Start()
方法中實現。
如:
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes);//路由註冊到應用程序 BundleConfig.RegisterBundles(BundleTable.Bundles); } }
在配置路由裏建立了一個路由名爲Default
的路由。該Default
路由由controller
,action
,id
三部分組成,其中id
爲可選參數。
該路由能夠匹配以下url:
這些URL都會映射到以下Action:
public class HomeController :Controller { public ActionResult Index() { return View(); } }
或
//在路由中id參數是可爲空的,因此對於值類型的參數必須是可空的值類型。 public class HomeController :Controller { public ActionResult Index(int? id) { return View(); } }
而且該Action
的參數名稱須要和Route
中的參數(id)一致。即也是id。才能夠匹配xxx.com/home/index/1
不然只能經過url傳參匹配xxx.com/home/index?myparam=1
如:若是定義的Action以下
public class HomeController :Controller { public ActionResult Index(string str) { return View(); } }
輸入xxx.com/home/index/1
時,會認爲參數爲空,即str
並無被賦值,可是依然會調用index
方法,只不過是認爲str
爲空。可是當你經過url傳參請求時xxx.com/home/index?str=hello
,是能夠匹配到這個Action
,也能夠給str
賦值。
在同一個Controller下是不容許有Action重載的
如:
public class HomeController :Controller { public ActionResult Index(int? id) { return View(); } public ActionResult Index() { return View(); } }
在請求時提示錯誤:在對控制器類型「HomeController」的操做Index的請求方法不明確。
路由引擎在定位路由時,會遍歷路由集合中的全部路由。只要發現了一個匹配的路由,會當即中止搜索。因此定義路由必定要注意路由的前後循序。通常是越是精確的放在前面。
如:有一個以下的路由配置
routes.MapRoute{ name: "one", url:"{site}", defaults:new{controller="MyControllerOne",action="Index"} } routes.MapRoute{ name:"two", url:"Admin", defaults:new {controller="Admin",action="Index"} }
第一個路由有一個{site}佔位符。默認的控制器爲MyControllerOne
。第二個路由是一個常量Admin,
默認的控制器爲Admin
。這兩個都是正確的路由配置。可是當咱們輸入urlxxx.com/admin
時,咱們預想的是請求AdminController
下的Index
操做方法。可是根據上面的路由映射,該url會匹配第一個路由,而後就中止了路由查找。此時觸發的Controller
爲MyControllerOne
。
以前的路由配置,都沒有url的參數的類型信息。若是咱們的Action是一個Int類型,可是url中的參數是個字符串,這樣就會致使錯誤。因此若是有url的類型約束能夠規避這個錯誤的發生。
在Asp.Net MVC中咱們能夠經過正則表達式來約束路由。
如:
routes.MapRoute{ "Default", "{controller}/{action}/{id}", new{controller="Home",action="Index",id=UrlParameter.Optional}, new{id="\d+"}//該id爲整數 }
除了使用正則表達式來約束路由,咱們還能夠經過繼承IRouteConstraint接口自定義約束規則
如:
public class MyRouteConstraint : IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { //獲取id的值 var id = values[parameterName]; //id驗證方法 return true; } }
更新路由配置
routes.MapRoute{ "Default", "{controller}/{action}/{id}", new{controller="Home",action="Index",id=UrlParameter.Optional}, new{id=new MyRouteConstraint()} }
That's it
參考資料:
若有不對,請多多指教。