九、路由
在本章中,我们将介绍:
- 使用约定路由创建路由
- 使用属性路由创建路由
- 使用 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 等)
路线顺序
路由添加到路由表的顺序很重要。
最佳实践是按照以下顺序添加路由:
- 忽略路线
- 具体的路线
- 一般的路线
路由与 ASP.NET Core
为了在应用中使用路由中间件,我们将Microsoft.AspNetCore.Routing
添加到project.json
中,并将services.AddRouting()
添加到Startup.cs
的ConfigureServices
方法中。
使用约定路由创建路由
在本食谱中,我们将学习如何使用约定路由创建路由。
怎么做……
我们有两种方法。 使用约定路由创建路由的第一种方法如下:
- 将
services.AddMvc()
加到ConfigureServices
:
app.AddMvc();
- 我们添加
app.UseMvc()
来配置路由定义:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
使用约定路由创建路由的第二种方法如下:
- 将
services.AddMvc()
加到ConfigureServices
:
app.AddMvc();
- 现在,是时候在
Configure()
方法中添加app.UseMvc()
方法调用了:
app.UseMvc();
- 我们将属性路由添加到控制器和/或动作:
[Route("api/[controller]")]
public class ValuesController : Controller
它是如何工作的…
我们可以通过在configure
方法中向routescollection
添加路由,并在动作级别用属性路由完成或覆盖路由定义,从而将第一种方法与第二种方法混合在一起。
使用属性路由创建路由
在本食谱中,我们将学习如何使用属性路由创建路由。
准备
我们创建一个带有action
方法的控制器,用路由属性装饰它。
怎么做……
属性路由是通过在控制器的action
方法之上添加定义路由的属性来定义路由的能力。
- 首先,让我们为一个指定
id
参数的action
方法添加一个路由属性:
//Route: /Laptop/10
[Route("Products/{id}")]
public ActionResult Details(int id)
- 接下来,让我们添加一个可选参数:
//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)
- 现在,让我们加上
RoutePrefix
。 这个属性将应用于这个控制器的每个动作:
[Route("Products")]
public class HomeController : Controller
{
//Route: /Products/10
[Route("{id?}")]
public ActionResult Details(int? id)
{
return View();
}
}
- 最后,我们添加一个
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 响应,在较低的级别上,它将我的路由表添加到路由中间件。
怎么做……
- 首先,我们创建的类必须实现 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);
}
}
- 接下来,让我们创建一个
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/.
- 最后,让我们在
Startup.cs
的Configure
方法中将该路由添加到路由集合中:
public void Configure(IApplicationBuilder app)
{
var routeBuilder = new RouteBuilder(app);
routeBuilder.AddProductsRoute(app);
app.UseRouter(routeBuilder.Build());
}
- 我们必须匹配这个动作方法签名:
public IActionResult Details
(string lang, string category, string subcategory, Guid id)
创建路径约束
在本教程中,我们将学习如何使用约定路由创建路由约束。
准备
一个路由约束被插入一个内联语法,称为内联约束,形式如下:{parameter:constraint}
。
有几种方法可以创建路径约束:
- 以常规方式创建路由时插入约束
- 在创建属性路由时插入约束
- 创建实现
IRouteConstraint
的类
怎么做……
- 这是第一种方法,在常规创建路由时插入约束:
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" }
);
});
- 这是第二种方式,通过属性路由插入约束:
// 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(); }
}
- 这是第三种方法,创建一个实现
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 作为一个操作重定向到相应的控制器。
怎么做……
- 首先,我们必须使用 Web 平台安装程序安装
UrlRewriting
模块:
- 接下来,我们添加一条规则,将遗留 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}
。
版权属于:月萌API www.moonapi.com,转载请注明出处