1. 程式人生 > >解讀ASP.NET 5 & MVC6系列(11):Routing路由

解讀ASP.NET 5 & MVC6系列(11):Routing路由

新版Routing功能介紹

在ASP.NET 5和MVC6中,Routing功能被全部重寫了,雖然用法有些類似,但和之前的Routing原理完全不太一樣了,該Routing框架不僅可以支援MVC和Web API,還支援一般的ASP.NET5程式。新版的改變有如下幾個部分。

首先,Routing系統是基於ASP.NET 5的,是一個獨立於MVC的路由框架,而不是基於MVC的。MVC只是在上面擴充套件了一個快捷方式而已。

其次,在ASP.NET 5中,MVC和Web API控制器沒有區別了,即合二為一了。兩者派生於同一個Controller基類。也就是說該Routing框架是適用於兩者的,適用於MVC則意味著也適用於Web API。

最後,不管在基於約定的Route宣告還是基於Attribute的Route宣告,都可以使用內聯約束和引數選項。例如,你可以約定路由中某個引數的資料型別,也可以讓一個引數標記為可選型別,再或者給其提供一個預設值。

Routing框架的主要流程

基本的Routing框架是基於Middleware來實現的,這樣就可以將其新增到HTTP的請求Pipeline中了,它可以喝其它任意Middleware一起進行組合使用,如靜態檔案處理程式、錯誤頁、或者SignalR伺服器。

在使用Routing框架之前,首要要了解Routing的作用,作用很簡單:

  1. 對於HTTP請求,Routing系統負責找出與之匹配的route,建立route資料,並將該請求派送到該route對於的處理程式(Handler)上。
  2. Controller和Action的選擇,只是MVC的Handler的一個具體實現,該實現使用route資料和HTTP請求中的其它資訊來選擇要執行的Controller和Action。在新版的MVC6中,該處理程式的名稱為MvcRouteHandler。

路由系統的執行流程如下:

  1. ASP.NET 5監聽到一個HTTP請求。然後Routing Middleware就會嘗試將route集合中的route匹配該請求。
  2. 一旦成功匹配一個請求,就找出該route對應的handler。
  3. 呼叫該handler上的RouteAsync方法(因為所有的handler都要實現該介面方法)。
  4. RoutingContext有一個IsHandled標記,如果該標記設定為true,則意味著該請求已經被這個handler成功處理了;如果設定為false,則意味著該handler無法處理該請求,系統會再為此匹配一個route。

和之前的Routing系統有點不同的是,老版的Routing系統一旦成功匹配一個路由,就將其交由其對應的Handler,不管對應的Handler能不能處理該請求,所以就會出現route匹配成功了,但是找不到對應的action,此時就會出現404錯誤,而新版對此作出了上述第4步驟的改進(重新將控制權交回給Routing系統,進行重新匹配),看起來還是非常不錯的。

Route引數和約束條件的改進

在之前的route設定中,要約束一個引數的資料型別的話,我們需要使用型別如下程式碼:

routes.MapRoute(
    "Product", 
    "Product/{productId}", 
    defaults: new { controller = "Product", action = "Details" },
    constraints: new { productId = @"\d+" });

而在新版route中,就可以直接設定Product/{productId:int}了,約束條件遵守如下約定:

{parameter:constraint}

目前支援的約束如下:

約束 示例 說明
required "Product/{ProductName:required}" 引數必選
alpha "Product/{ProductName:alpha}" 匹配字母,大小寫不限
int "Product/{ProductId:int}" 匹配int型別
long "Product/{ProductId:long}" 匹配long型別
bool "Product/{ProductId:bool}" 匹配bool型別
double "Product/{ProductId:double}" 匹配double型別
float "Product/{ProductId:float}" 匹配float型別
guid "Product/{ProductId:guid}" 匹配guid型別
decimal "Product/{ProductId:decimal}" 匹配decimal型別
datetime "Search/{datetime:datetime}" 匹配datetime型別
composite "Product/{ProductId:composite}" 匹配composite型別
length "Product/{ProductName:length(5)}" 長度必須是5個字元
length "Product/{ProductName:length(5, 10)}" 長度在5-10個之間
maxlength "Product/{productId:maxlength(10)}" 最大長度為10
minlength "Product/{productId:minlength(3)}" 最小長度為3
min "Product/{ProductID:min(3)}" 大於等於3
max "Product/{ProductID:max(10)}" 小於等於10
range "Product/{ProductID:range(5, 10)}" 對應的陣列在5-10之間
Regex "Product/{productId:regex(^\d{4}$)}" 符合指定的正則表示式

而對於可選引數,則值需要在約束型別後面加一個問號即可,示例如下:

routes.MapRoute(
    "Product", 
    "Product/{productId:long?}", 
    new { controller = "Product", action = "Details" });

如果引數是必填的,需要保留一個預設值的話,則可以按照如下示例進行設定:

routes.MapRoute(
    "Product", 
    "Product/{productId:long=1000}",
    new { controller = "Product", action = "Details" });

通用Routing

關於示例使用,我們先不從MVC開始,而是先從普通的Routing使用方式開始,新版route新增的時候預設新增的是TemplateRoute例項,並且在該例項例項化的時候要設定一個Handler

舉例來說,我們先建立一個空的ASP.NET 5專案,並在project.json檔案的dependencies節點中新增程式集"Microsoft.AspNet.Routing": "1.0.0-beta3",,在Startup.csConfigure方法裡新增如下程式碼:

public void Configure(IApplicationBuilder app)
{
    RouteCollection routes = new RouteCollection();
    routes.Add(new TemplateRoute(new DebuggerRouteHandler("RouteHandlerA"), "", null));
    routes.Add(new TemplateRoute(new DebuggerRouteHandler("RouteHandlerB"), "test/{a}/{b:int}", null));
    routes.Add(new TemplateRoute(new DebuggerRouteHandler("RouteHandlerC"), "test2", null));

    app.UseRouter(routes); // 開啟Routing功能
}

在這裡,我們設定HTTP請求處理的的Handler為DebuggerRouteHandler,該類繼承於IRouter,例項程式碼如下:

public class DebuggerRouteHandler : IRouter
{
    private string _name;

    public DebuggerRouteHandler(string name)
    {
        _name = name;
    }

    public string GetVirtualPath(VirtualPathContext context)
    {
        throw new NotImplementedException();
    }

    public async Task RouteAsync(RouteContext context)
    {
        var routeValues = string.Join("", context.RouteData.Values);
        var message = String.Format("{0} Values={1} ", _name, routeValues);
        await context.HttpContext.Response.WriteAsync(message);
        context.IsHandled = true;
    }
}

上述類,繼承IRouter以後,必須實現一個RouteAsync的方法,並且如果處理成功,則將IsHandled設定為true

訪問如下網址即可檢視相應的結果:

正常:`http://localhost:5000/`
正常:`http://localhost:5000/test/yyy/12`
404 :`http://localhost:5000/test/yyy/s`
正常:`http://localhost:5000/test2`
404 :`http://localhost:5000/test3`

注意:TemplateRouteDebuggerRouteHandler都繼承於IRouter,是實現前面所述的不出現404錯誤(繼續匹配下一個路由)的核心。

MVC中的Routing

在MVC示例程式中,我們只需要配置在呼叫app.UseMVC方法的時候,使用委託中的MapRoute方法來定義各種route就可以了。在這裡我們以空白專案為例,來看看MVC的route如何使用。

第一步:在project.json檔案的dependencies節點中引用程式集"Microsoft.AspNet.Mvc": "6.0.0-beta3"
第二部:新增MVC的Middleware,並使用MVC,然後新增一條預設的路由,程式碼如下:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
}

public void Configure(IApplicationBuilder app)
{
    app.UseMvc(routeBuilder =>
    {
        routeBuilder.MapRoute(
            name: "default",
            template: "{controller}/{action}/{id?}",
            defaults: new { controller = "Home", action = "Index" });
    });
}

第三步:分別建立如下如下三種Controller,其中ProductsController繼承於Microsoft.AspNet.Mvc下的Controller

public class ProductsController : Controller
{
    public IActionResult Index()
    {
        return Content("It Works with Controller Base Class!");
    }
}

public class DemoController
{
    public IActionResult Index()
    {
        return new ObjectResult("It Works without Controller Base Class!");
    }
}

public class APIController
{
    public object Index()
    {
        return new { Code = 100000, Data = "OK" };
    }
}

訪問http://localhost:5000/productshttp://localhost:5000/demo,均能顯示正常的輸出結果;而訪問http://localhost:5000/api的時候返回的則是json資料。

這就是我們在前面ASP.NET5新特性中所講的MVC和API合二為一了,並且也可以不繼承於Controller基類(但類名要以Controller結尾)。這種技術的核心是Controller的查詢機制,關於如何在一個專案中查詢合適的程式集,請參考《Controller與Action》章節。

新版MVC在判定Controller的時候,有2個條件:要麼繼承於Controller,要麼是引用MVC程式集並且類名以Controller結尾。

所以,在建立MVC Controller和Web API Controller的時候,如果你不需要相關的上下文(如HTTPContext、ActionContext等)的話,則可以不必繼承於Controller基類;但推薦都繼承於Controller,因為可以多多利用基類的方法和屬性,因為不管繼承不繼承,你定義的所有Controller類都要走MVC的各個生命週期,我們通過ActionFilter來驗證一下:

第一步:在project.json檔案的dependencies節點中引用程式集"Microsoft.AspNet.Server.WebListener": "1.0.0-beta3"
第二步:建立一個Aciton Filter,分別在Action執行前和執行後輸出一行文字,程式碼如下:

public class ActionFilterTest : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        var typeName = context.Controller.GetType().FullName;
        Console.WriteLine(typeName + "." + context.ActionDescriptor.Name + ":Start");
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        var typeName = context.Controller.GetType().FullName;
        Console.WriteLine(typeName + "." + context.ActionDescriptor.Name + ":END");
    }
}

第三步:在ConfigureServices方法裡註冊該Action Filter。

services.Configure<MvcOptions>(options =>
{
    options.Filters.Add(typeof(ActionFilterTest));
});

執行程式,並訪問響應的路徑,三種類型的程式碼均會按計劃輸出內容,輸出內容如下:

RouterTest.ProductsController.Index:Start
RouterTest.ProductsController.Index:End
RouterTest.DemoController.Index:Start
RouterTest.DemoController.Index:End
RouterTest.APIController.Index:Start
RouterTest.APIController.Index:End

普通的ASP.NET5程式和MVC程式是可以在一起混合使用Routing功能的。

自定義Route

ASP.NET 5和MVC6都提供了豐富的Route自定義功能,關於普通Route的自定義,可以參考前面小節的DebuggerRouteHandler,這種方式需要實現自己的HTTP輸出,相當於原來輕量級的IHttpHandler一樣。本節,我們將這種在基於MVC的Route自定義功能,即定義的Route的Handler處理程式都是MvcRouteHandler。

在之前版本的MVC中,要自定義Route,一般都是繼承於RouteBase基類或Route類;而在新版的MVC6中,要實現自定義Route,有三種方式,分別如下:

  1. 繼承於TemplateRoute
  2. 實現IRouter
  3. 實現INamedRouter(注:INamedRouter和IRouter的唯一區別是多了一個名稱)

本例中,我們以繼承繼承於TemplateRoute為例,首先建立一個繼承於該類的子類PromoTemplateRoute,該類只匹配/promo目錄下的路徑。

public class PromoTemplateRoute : TemplateRoute
{
    public PromoTemplateRoute(IRouter target, string routeTemplate, IInlineConstraintResolver inlineConstraintResolver)
        : base(target, routeTemplate, inlineConstraintResolver: inlineConstraintResolver)
    {
    }

    public PromoTemplateRoute(IRouter target,
                            string routeTemplate,
                            IDictionary<string, object> defaults,
                            IDictionary<string, object> constraints,
                            IDictionary<string, object> dataTokens,
                            IInlineConstraintResolver inlineConstraintResolver)
        : base(target, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
    {
    }

    public PromoTemplateRoute(IRouter target,
                            string routeName,
                            string routeTemplate,
                            IDictionary<string, object> defaults,
                            IDictionary<string, object> constraints,
                            IDictionary<string, object> dataTokens,
                            IInlineConstraintResolver inlineConstraintResolver)
        : base(target, routeName, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
    { }

    public async override Task RouteAsync(RouteContext context)
    {
        var requestPath = context.HttpContext.Request.Path.Value ?? string.Empty;
        if (!requestPath.StartsWith("/promo", StringComparison.OrdinalIgnoreCase))
        {
            return;
        }
        await base.RouteAsync(context);
    }
}

為了方便使用,我們也比葫蘆畫瓢,建立一些擴充套件方法,示例如下:

public static class RouteBuilderExtensions
{
    public static IRouteBuilder MapPromoRoute(this IRouteBuilder routeCollectionBuilder, string name, string template)
    {
        MapPromoRoute(routeCollectionBuilder, name, template, defaults: null);
        return routeCollectionBuilder;
    }

    public static IRouteBuilder MapPromoRoute(this IRouteBuilder routeCollectionBuilder, string name, string template, object defaults)
    {
        return MapPromoRoute(routeCollectionBuilder, name, template, defaults, constraints: null, dataTokens: null);
    }

    public static IRouteBuilder MapPromoRoute(this IRouteBuilder routeCollectionBuilder, string name, string template, object defaults, object constraints, object dataTokens)
    {
        var inlineConstraintResolver = routeCollectionBuilder.ServiceProvider.GetService<IInlineConstraintResolver>();
        routeCollectionBuilder.Routes.Add(
            new PromoTemplateRoute(
                routeCollectionBuilder.DefaultHandler,
                name,
                template,
                ObjectToDictionary(defaults),
                ObjectToDictionary(constraints),
                ObjectToDictionary(dataTokens),
                inlineConstraintResolver));

        return routeCollectionBuilder;
    }

    private static IDictionary<string, object> ObjectToDictionary(object value)
    {
        var dictionary = value as IDictionary<string, object>;
        if (dictionary != null)
        {
            return dictionary;
        }

        return new RouteValueDictionary(value);
    }
}

使用的時候,則很簡單,和之前的方式非常類似,示例如下:

routes.MapPromoRoute(
    name: "default2",
    template: "promo/{controller}/{action}/{id?}",
    defaults: new { controller = "Home", action = "Index" });

通過這種方式,我們可以在符合路由匹配條件的時候,使用PromoTemplateRoute類來處理一些自定義邏輯,比如新增一些額外的檔案頭資訊等等。

基於Attribute的Routing

基於Attribute的Routing功能一直是MVC所期待的功能,在Web API已經通過RoutePrefix(Controller上使用)和Route(Action上使用)來實現了。該特性在MVC 6中進行了重寫和增強,並且由於MVC和Web API合二而一了,所以在這兩種Controller上都可以使用該特性。

舉例來說:

[Route("bookhome")]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    [Route("about")]
    public IActionResult About()
    {
        ViewBag.Message = "Your application description page.";
        return View();
    }

    [Route("contactus")]
    public IActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";
        return View();
    }
}

在上述Controller上定義一個bookhome字首,並且在About和Contact上又分別定義了action名稱,所以上述3個Action的訪問地址則是如下這種形式:

/bookhome
/bookhome/about
/bookhome/contactus

在這裡,我們需要注意,Controller和Action使用的Attribute都是Route,同時,在這些路由模板字串中,依然可以使用內聯引數,比如,我們可以定義類似這樣的路由:

[Route("products/{productId:int}")]

Controller和Action標記位

另外,針對Route的模板字串,不僅支援內聯引數,還支援Controller和Action的標記位,即不用寫死該Controller或Action的名稱,使用一個[controller][action]的字元即可表示該Controller或Action的名稱。比如,我們可以在Controller上定義這樣的一個路由(Action上什麼都不定義):

[Route("book/[controller]/[action]")]

這樣訪問首頁的地址就變成了:/book/Home/Index

Web API的等價Route定義

在Web API中,我們一般還要定義GET、POST這樣的請求方式,為了方便,新版的HTTPGET等一系列方法都集成了Route功能,直接在建構函式傳入Route模板即可,示例如下:

[HttpGet("products/{productId:int}")]

上述Route的定義,即表明,既要符合products/{productId:int}的路由規則,又要是GET請求。

  1. 其實HTTPGET這一系列Attribute也可以在普通的MVC Controller上使用,因為在MVC6中,MVC Controller和Web API Controller本身就是同一個東西,只不過MVC的返回型別都是IActionResult而已。
  2. Route定義,不僅僅支援GET請求,還支援POST等其它型別的請求,即不限制請求方式。
  3. 在HttpXXX系列特性中,也是支援內聯引數和[controller]、[action]標記位的,大可放心使用。
  4. 目前可用的特性類有:HttpGet、HttpPost、HttpPut、HttpDelete、HttpPatch。

非要重要Route定義規則

基於Attribute的Route定義很方便,但也很危險,具體規則和危險性如下。

規則1:Controller上定義了Route特性很危險

一旦在Controller上定義了Route特性,該Controller下的所有路由規則都不受其它規則控制了,比如,如果你定義了類似這樣的

[Route("book")]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public IActionResult About()
    {
        ViewBag.Message = "Your application description page.";
        return View();
    }
}

那麼,上述2個Action你都再也沒辦法訪問了,因為預設的action的名稱根本就不會起作用,即/book/index/book/about這兩個路徑無法路由到對應的Action方法上。而且/book也訪問不了,因為有兩個以上的Action,系統無法定位到其中一個Action上。

所以要讓上述Action能訪問,必須要在其中一個Action上定義再Route,例如:

[Route("book")]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    [Route("about")]
    public IActionResult About()
    {
        ViewBag.Message = "Your application description page.";
        return View();
    }
}

這樣,就可以通過/book/about來訪問About方法了,而訪問/book則可以訪問預設的index方法了,因為該index方法是預設唯一一個沒有定義路由的方法,所以他就是/book路由規則的預設Action。如果,有3個Action的話,則必須要至少給兩個Action定義Route,示例如下:

[Route("book")]
public class HomeController : Controller
{
    [Route("index")]
    public IActionResult Index()
    {
        return View();
    }

    [Route("about")]
    public IActionResult About()
    {
        ViewBag.Message = "Your application description page.";
        return View();
    }
    public IActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";
        return View();
    }
}

此時,Contact方法就是預設/book路由的Action了,訪問/book路徑的話,就會顯示Contact對應的頁面。

規則2:Route和HttpGet可以一起使用,但也很危險

我們前面提到,在Action上即可以使用Route特性,也可以使用HttpGet特性,兩者之間的不同,就是多了一個Http Method。很多同學可以要問兩個特性在一起使用的時候會有問題麼?

其實,這兩個特性是可以在一起使用的,示例如下:

[Route("book")]
public class HomeController : Controller
{
    [Route("Contact")]
    [HttpGet("home/Contact2")]
    public IActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";

        return View();
    }
}

這樣/book/contact/book/home/contact2這兩個網址,都可以訪問了。但如果這裡定義HttpGet,情況就不一樣了,示例如下:

[Route("Contact")]
[HttpPost("home/Contact2")]

此時,訪問該Action的方式,要麼是以GET的方式訪問/book/contact地址,要麼是以POST的方式訪問/book/home/contact2。所以為了避免出錯,建議使用的時候不要講兩者混用,即便是要同時支援GET和POST,那也是建議用同類型的HttpXXX來定義這些路由,例如:

[HttpGet("Contact")]
[HttpPost("home/Contact2")]

這樣,看起來就清晰多了。

規則3:多個Route和多個HttpXXX也可以一起使用,但也很危險

在如下示例中,我們為HomeController定義了2個Route特性,而Contact定義了2個Route特性和1個HttpPost特性。

[Route("book")]
[Route("tom")]
public class HomeController : Controller
{
    [Route("Contact")]
    [Route("ContactUS")]
    [HttpPost("home/Contact2")]
    public IActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";
        return View();
    }
}

那麼,在上述程式碼生效後,我們將有六種訪問來訪問該Action,這六種方式分佈如下:

GET:/book/contact
GET:/book/contactus
GET:/tom/contact
GET:/tom/contactus
POST:/book/home/contact2
POST:/tom/home/contact2

但是,在檢視檔案中,通過@Html.ActionLink("Contact", "Contact", "Home")生成連結地址的話,則預設會使用第一個定義的Route,如果要強制指定順序,則可以使用Order屬性來定義排序值,預設會優先使用最小的值。示例如下:

[Route("book", Order = 1)]
[Route("tom", Order = 0)]
public class HomeController : Controller
{
    [Route("Contact", Order = 1)]
    [Route("ContactUS", Order = 0)]
    [HttpPost("home/Contact2", Order = 2)]
    public IActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";
        return View();
    }
}

自定義內聯引數約束

在前面的介紹中,我們知道任意型別的路由在定義的時候都支援不同的內聯引數約束,因為這些約束是基於ASP.NET 5的,而不是基於MVC6的,並且這些約束還是可以擴充套件的,本節我們就來看看如何自定義一些擴充套件。

無引數約束

首先,我們來看一個比較簡單的約束,即無引數約束,類似於{productId:int}這樣的型別約束,假設我們要實現一個AABBCC字串限定的約束,示例如下:

[Route("index/{productId:aabbcc}")]

為了確保/index/112233和/index/aabbcc是符合約束的,而/index/aabbccdd是不符合約束的,我們首先要自定義一個約束類AABBCCRouteConstraint,並實現IRouteConstraint介面,示例如下:

public class AABBCCRouteConstraint : IRouteConstraint
{
    public bool Match(HttpContext httpContext, IRouter route, string routeKey, IDictionary<string, object> values, RouteDirection routeDirection)
    {
        bool b = false;

        object value;
        if (values.TryGetValue(routeKey, out value) && value != null)
        {
            if (value is string)  // 獲取傳入的值,比如aabbcc或112233
            {
                string aabbcc = value.ToString();
                b = !string.IsNullOrWhiteSpace(aabbcc) && aabbcc.Length == 6 && aabbcc[0] == aabbcc[1] && aabbcc[2] == aabbcc[3] && aabbcc[4] == aabbcc[5];

            }
        }

        return b;
    }
}

在該實現類中,要實現Match方法,根據傳入的各種引數,判斷是否符合定義的約束,並返回true或false,Match方法的引數中,其中routeKey是約束{productId:aabbcc}對應的引數名稱(本例中是productId),values集合中會有該productId所對應的數字(如112233),在該方法通過響應的判斷返回true和false。

下一步,就是要將該約束類註冊到Routing系統的約束集合中,在Startup.csConfigureServices方法中,執行如下語句:

services.Configure<RouteOptions>(opt =>
{
    opt.ConstraintMap.Add("aabbcc", typeof(AABBCCRouteConstraint));
});

注意,這裡註冊的aabbcc就是前面我們所指定約束名稱,完成上述步驟以後,即可實現類似{productId:int}的功能了。

有引數約束

一般情況下,有些時候可能需要定義一些約束的值,比如Length(1,10)來表示1-10之間的字串長度,舉例來說,加入我們要定義一個4個引數的約束規則,如abcd(1,10,20,30)來表示一個特殊的驗證項,則需要宣告有4個引數的建構函式,示例如下:

public class ABCDRouteConstraint : IRouteConstraint
{
    public int A { get; private set; }
    public int B { get; private set; }
    public int C { get; private set; }
    public int D { get; private set; }
    public ABCDRouteConstraint(int a, int b, int c, int d)
    {
        A = a;B = b;C = c;D = d;
    }

    public bool Match(HttpContext httpContext, IRouter route, string routeKey, IDictionary<string, object> values, RouteDirection routeDirection)
    {
        bool b = false;

        object value;
        if (values.TryGetValue(routeKey, out value) && value != null)
        {
            var valueString = value.ToString();//這裡需要進行進一步的驗證工作
            return true;
        }

        return b;
    }
}

假如你在Action上了定義瞭如下約束:

[Route("index/{productId:abcd(1,20,30,40)}")]

那麼,在註冊該約束型別以後,系統啟動厚掃描所有的Route進行註冊的時候,會分析你定義的這4個值,然後會將這4個值賦值給該路由對應的約束例項上的A、B、C、D四個屬性上,以便在HTTP請求過來的時候,分析URL上的值,看是否符合Match裡定義的規則(在驗證的時候就可以使用這4個屬性值)。

預設約束的所有程式碼可以參考: https://github.com/aspnet/Routing/tree/dev/src/Microsoft.AspNet.Routing/Constraints

另外,如果定義了4個引數的約束,那麼在action上定義路由的時候則必須符合引數的資料型別,如果不符合,系統啟動的時候就會出錯,示例錯誤如下:

[Route("index/{productId:abcd}")] //沒有為該物件定義無引數的建構函式

[Route("index/{productId:abcd(a)}")]
[Route("index/{productId:abcd('a')}")] //輸入字串的格式不正確

[Route("index/{productId:abcd(1,2,3)}")] //建構函式的引數個數和定義的引數個數不一致。

如果你定義的引數型別是字串型別,則下面2種形式的定義都是合法的:

[Route("index/{productId:abcd(a,b,c,d)}")]
[Route("index/{productId:abcd('a','b','c','d')}")]

雖然ASP.NET 5 和MVC6的路由使用方式很簡單,但是相關的使用規則卻很複雜,大家使用的時候需要多加註意。

同步與推薦