Asp.Net MVC 路由

Asp.Net MVC 路由

當用戶經過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之上的,因此路由也是註冊實現了管道事件。可是是經過註冊HttpModulePostResolveRequestCache事件來實現的。app

爲何不註冊HttpHandler來實現呢?

由於:asp.net

若是把請求的管道模型比做一個運行的火車的話,HttpHandler是請求火車的目的地。HttpModule是一個沿途的站點,要在終點前分析好這個請求是到哪一個目的地。網站

  • HttpHandler多用來處理響應處理。
  • HttpModule多用來處理通用性和響應內容無關的功能。

小結:ui

路由就是一個實現了IHttpModule接口的UrlRoutingModuleHttpModule,在管道事件中攔截請求,分析Url,匹配路由,再交給HttpHandler處理的過程。this


路由如何攔截請求

上述認識到路由是經過實現了接口IHttpModule的類--UrlRoutingModule來註冊管道事件,在該類中實現了請求攔截,路由匹配,建立指定HttpHandler。url

因此路由組件中UrlRoutingModule就是是關鍵。.net

UrlRoutingModule源碼在線查看

經過該類的源代碼能夠發現。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);
        }

即:

  1. 根據HttpContext,路由匹配規則,匹配一個RouteData對象。
  2. 調用RouteData對象的RouteHandler獲取IRouteHandlerMVCRouteHandler
  3. 由匹配的RouteDataHttpContext建立RequestContext
  4. 由2的MVCRouteHandler和3的RequestContext建立IHttpHandler-MVCHandler.
  5. HttpHandler管道事件執行。

流程以下圖所示:

以後HttpHandler的運行能夠參考以下整個生命週期:

Asp.Net MVC 生命週期圖:


路由的使用

Global.asaxMVCApplication是管理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.asaxMVCApplication繼承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);
    }
}

URL匹配

在配置路由裏建立了一個路由名爲Default的路由。該Default路由由controlleractionid三部分組成,其中id爲可選參數。

該路由能夠匹配以下url:

  • xxx.com/home/index/1
  • xxx.com/home/index
  • xxx.com/home
  • xxx.com/

這些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會匹配第一個路由,而後就中止了路由查找。此時觸發的ControllerMyControllerOne


路由約束

以前的路由配置,都沒有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

參考資料:


若有不對,請多多指教。

相關文章
相關標籤/搜索