九、路由

在本章中,我们将介绍:

  • 使用约定路由创建路由
  • 使用属性路由创建路由
  • 使用 IRouter 创建自定义路由
  • 创建路径约束
  • 创建域路由
  • 创建 seo 友好的路线

在 ASP.NET Core

在 MVC 之前,URL 代表一个物理的.ASPX文件,但是 MVC 添加了一个抽象层来映射 URL 与控制器和动作。 其目标是以 REST 的方式调用调用资源的方法,而不是调用页面。

在 MVC 框架中,无论使用何种技术,路由系统都将传入的 URL 映射为对应于动作控制器的 URL 路由模式。 路由系统不能处理物理文件。

所有的 URL 模式都存储在一个名为routecollection的对象中。

如果没有匹配,将返回一个 HTTP 404 错误。

路由系统与 IIS 连接。

HTTP.sys是 IIS 中的监听进程,它被重定向到UrlRouting模块,而UrlRouting模块又被重定向到 asp.net.NET 和 asp.net.NET MVC 管道。

因为 ASP.NET Core

现在,它是不同的。 缺省情况下不创建缺省路由,当路由不匹配时,缺省情况下不返回 404 错误。 路由系统作为中间件添加到 HTTP 管道中。

在 Windows 上,IIS 只会使用 IIS 映射处理程序上的aspnetcore模块重定向到kestrel或任何其他 web 服务器。

如果客户端请求的 URL 匹配路由集合中的现有路由,则调用其处理程序的RouteAsync方法,如果有异常处理请求,则尝试下一个路由模式,而不是直接返回 404 错误。

MVC 和 Web API 路由

最初,MVC 和 Web API 并不共享相同的路由框架。 默认情况下,Web API 路由是 RESTful。

因为 ASP.NET Core、MVC 和 Web API 已经统一为一个框架。 MVC 和 Web API 都继承自相同的控制器基类,它们的路由系统也被统一用于许多情况:

  • 自托管
  • 内存中的本地请求(对单元测试的)
  • WebServer 主机(IIS, NGinx, Azure 等)

路线顺序

路由添加到路由表的顺序很重要。

最佳实践是按照以下顺序添加路由:

  1. 忽略路线
  2. 具体的路线
  3. 一般的路线

路由与 ASP.NET Core

为了在应用中使用路由中间件,我们将Microsoft.AspNetCore.Routing添加到project.json中,并将services.AddRouting()添加到Startup.csConfigureServices方法中。

使用约定路由创建路由

在本食谱中,我们将学习如何使用约定路由创建路由。

怎么做……

我们有两种方法。 使用约定路由创建路由的第一种方法如下:

  1. services.AddMvc()加到ConfigureServices:
app.AddMvc(); 
  1. 我们添加app.UseMvc()来配置路由定义:
app.UseMvc(routes => 
                { 
                routes.MapRoute( 
                    name: "default", 
                    template: "{controller=Home}/{action=Index}/{id?}"); 
});

使用约定路由创建路由的第二种方法如下:

  1. services.AddMvc()加到ConfigureServices:
app.AddMvc(); 
  1. 现在,是时候在Configure()方法中添加app.UseMvc()方法调用了:
app.UseMvc(); 
  1. 我们将属性路由添加到控制器和/或动作:
[Route("api/[controller]")]
 public class ValuesController : Controller

它是如何工作的…

我们可以通过在configure方法中向routescollection添加路由,并在动作级别用属性路由完成或覆盖路由定义,从而将第一种方法与第二种方法混合在一起。

使用属性路由创建路由

在本食谱中,我们将学习如何使用属性路由创建路由。

准备

我们创建一个带有action方法的控制器,用路由属性装饰它。

怎么做……

属性路由是通过在控制器的action方法之上添加定义路由的属性来定义路由的能力。

  1. 首先,让我们为一个指定id参数的action方法添加一个路由属性:
//Route: /Laptop/10 
[Route("Products/{id}")] 
public ActionResult Details(int id) 
  1. 接下来,让我们添加一个可选参数:
//Route: /Products/10/Computers or /Products/10
 [Route("Products/{id}/{category?}")] 
public ActionResult Details(int id, string category) 
We can see how to do that for a RESTfull Web API method :
 // GET api/values/5 
       [HttpGet("{id?}")] 
public string Get(int? id) 
  1. 现在,让我们加上RoutePrefix。 这个属性将应用于这个控制器的每个动作:
[Route("Products")] 
         public class HomeController : Controller 
         { 
         //Route: /Products/10 
         [Route("{id?}")] 
         public ActionResult Details(int? id) 
         { 
                return View(); 
         } 
} 
  1. 最后,我们添加一个Order属性。 属性路由中的Order参数与按照惯例添加路由时routecollection参数插入的顺序一致。 Name参数允许我们覆盖ActionMethod名称:
[Route("Products")] 
    public class HomeController : Controller 
    { 
        [Route("Index", Name = "ProductList", Order = 2)] 
        public ActionResult Index() { return View(); } 

        [Route("{id?}", Name = "ProductDetail", Order = 1)] 
        public ActionResult Details(int? id) { return View(); } 
} 

使用 IRouter 创建自定义路由

在本教程中,我们将学习如何创建自定义路由。

准备

在 MVC 6 中,我们将使用 IRouter 而不是 IRouteHandler 和 IHttpHandler 来定义路由。 ASP.NET Core 使用 IRouter 的递归实现。 在较高的级别上,它可以编写 HTTP 响应,在较低的级别上,它将我的路由表添加到路由中间件。

怎么做……

  1. 首先,我们创建的类必须实现 IRouter 和RouteAsync方法:
public class ProductsRouter : IRouter 
{ 
        private readonly Route _innerRoute; 
        public VirtualPathData GetVirtualPath 
(VirtualPathContext context) 
        { return null; } 

        public Task RouteAsync(RouteContext context) 
        { 
            // Test QueryStrings 
            var qs = context.HttpContext.Request.Query; 
            var price = qs["price"]; 

            if(string.IsNullOrEmpty(price)) 
            { return Task.FromResult(0); } 

            var routeData = new RouteData(); 
            routeData.Values["controller"] = "Products"; 
            routeData.Values["action"] = "Details"; 
            routeData.DataTokens["price"] = price; 
            context.RouteData = routeData; 

            return _innerRoute.RouteAsync(context); 
        } 
} 
  1. 接下来,让我们创建一个extension方法,将该路由添加到routeBuilder:
public static class Extensions { 
public static IRouteBuilder AddProductsRoute( 
this IRouteBuilder routeBuilder, IApplicationBuilder app) 
       { 
          routeBuilder.Routes.Add( 
             new Route(new ProductsRouter(), 
"products/{lang:regex(^([a-z]{{2}})-([A-Z]{{2}})$)}/ 
{category:alpha}/{subcategory:alpha}/{id:guid}",                app.ApplicationServices.GetService( 
typeof(IInlineConstraintResolver))  
as IInlineConstraintResolver 
                 )); 
            return routeBuilder; 
        } 
} 

Regex makes it relatively simple to check whether a string value matches certain rules. Also, we can get certain parts of a string value after some rules have been applied to it. With Regex, we can create a rule to be applied to a route URL. You can learn more at https://regexr.com/.

  1. 最后,让我们在Startup.csConfigure方法中将该路由添加到路由集合中:
public void Configure(IApplicationBuilder app) 
{ 
  var routeBuilder = new RouteBuilder(app); 
  routeBuilder.AddProductsRoute(app); 
  app.UseRouter(routeBuilder.Build());
} 
  1. 我们必须匹配这个动作方法签名:
public IActionResult Details
 (string lang, string category, string subcategory, Guid id)

创建路径约束

在本教程中,我们将学习如何使用约定路由创建路由约束。

准备

一个路由约束被插入一个内联语法,称为内联约束,形式如下:{parameter:constraint}

有几种方法可以创建路径约束:

  • 以常规方式创建路由时插入约束
  • 在创建属性路由时插入约束
  • 创建实现IRouteConstraint的类

怎么做……

  1. 这是第一种方法,在常规创建路由时插入约束:
app.UseMvc(routes => 
            { 
                routes.MapRoute( 
                    name: "default", 
                    template: "{controller=Home}/{action=Index}/{id?}" 
                ); 

                routes.MapRoute( 
                    name: "products", 
                    template: "Products/{id=1}", 
                    defaults: new { controller = "Product", action = "Details" } 
                ); 
            }); 
  1. 这是第二种方式,通过属性路由插入约束:
// for an api controller
 [Route("api/[controller]")] 
    public class ProductValuesController : Controller 
    { 
        // GET api/productvalues/5  
        [HttpGet("{id}")] 
        public string Get(int id) 
        { 
            return "value"; 
        } 

        // PUT api/productvalues/laptop 
        [HttpPut("{name:alpha:length(3)}")] 
        public void UpdateProductName(string name) { } 
} 
// for a MVC controller
 [Route("[controller]")] 
    public class HomeController : Controller 
    { 
        // GET home/index/5  
        [Route("{id?}")] 
        public IActionResult Index(int? id) { return View(); } 

        [Route("{name:alpha:length(3)}")] 
        public IActionResult UpdateProductName(string name) 
 { return View(); } 
    } 
  1. 这是第三种方法,创建一个实现IRouteConstrains的类:
public class ValuesConstraint : IRouteConstraint 
{ 
        private readonly string[] validOptions; 
        public ValuesConstraint(string options) 
        { 
            validOptions = options.Split('|'); 
        } 

        public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) 
        { 
            object value; 
            if (values.TryGetValue(routeKey, out value) && value != null) 
            { 
                return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase); 
            } 
            return false; 
        } 
} 

下面是一个基于正则表达式创建路由约束的例子:

public class RegexLangConstraint : RegexRouteConstraint 
{ 
        public RegexLangConstraint() : base(@"^([a-z]{2})-([A-Z]{2})$") 
        { 
        } 
} 

该约束将使用以下模式检查语言参数:[fr-FR][en-US]

让我们看看如何将这个约束应用到路由定义中:

routes.MapRoute( 
name: "fr_products", 
template: "products/{lang}/{category:alpha}/{id:guid}", 
defaults: new { controller = "Products", action = "Details" }, 
constraints: new { lang = new RegexLangConstrain() }); 

它是如何工作的…

下面是一个用于约定路由或属性路由的约束的非详尽列表:

  • int :{id:int}
  • bool :{isActive:bool}
  • datetime :{dateBirth:datetime}
  • decimal :{price:decimal}
  • guid :{id:guid}
  • required :{userName:required}
  • 【t】【t】
  • minlength(value) : {userName:minlength(8)}
  • maxlength(value) : {userName:maxlength(20)}
  • length(min,max) :{userName:length(8,20)}
  • min(value) :{age:min(18)}
  • max(value) :{age:max(100)}
  • range(min,max) :{age:range(18,100)}
  • regex(expression) :{ssn:regex(^d{3}-d{3}-d{4}$)}

创建域路由

在这个食谱中,我们将学习如何创建一个域路由。

准备

在一些场景中,域路由可能非常有用和有趣:

  • 在使用多种语言的 web 应用时,url 可能如下所示:
route like "www.{language}-{culture}.domain.com" or "www.{language}.domain.com"
  • 当使用多租户 web 应用时,url 可以如下所示:
route like "www.{controller}.domain.com", "www.{area}.domain.com", "www.{subentity}.domain.com"

怎么做……

让我们添加新的路线到Startup.cs:

public void Configure(IApplicationBuilder app) 
{ 
app.UseMvc(routes => 
{ 
  routes.MapRoute( 
 name: "DomainRoute", 
 template: "home.domain.com/{action=Index}/{id?}"); 

  routes.MapRoute( 
 name: "DomainRoute2", 
 template: "{controller=Home}.domain.com/{id?}"); 

  routes.MapRoute( 
 name: "DomainRoute3", 
 template: "{controller=Home}-{action=Index}.domain.com/{id?}");
 } 
} 

创建 seo 友好的路线

在这个食谱中,我们将学习如何创建搜索引擎友好的路线。 为此,我们可以使用 MVC 路由系统、IISUrlRewriting模块或两者都使用。

准备

在某些情况下,SEO 是必须考虑的。

我们正在创建一个新的版本的网站与 MVC。 但是我们有很多遗留 url 已经被使用,或者是在 MVC 版本的应用之前被记录的。 我们也花了很多钱购买关键字到摩托搜索谷歌抛出所有这些 url。

为了解决这个问题,我们将使用 IISUrlRewriting模块将遗留 url 作为一个操作重定向到相应的控制器。

怎么做……

  1. 首先,我们必须使用 Web 平台安装程序安装UrlRewriting模块:

  1. 接下来,我们添加一条规则,将遗留 URL(如http://localhost:1962/products.ASPX?lang=fr-FR&category=smartphone&subcategory=samsung&id=cadc5808-75a7-4428-b491-3cacfe37d9ce)重定向到现有的 MUV 路由:http://localhost:1962/fr-FR/products/smartphone/samsung/cadc5808-75a7-4428-b491-3cacfe37d9ce

MVC 路由有以下模板模式: http://localhost:1962/products/{lang}/{category}/{subcategory}/{id}