问题
ASP.NET Core 2.0的路由引擎是如何工作的?
答案
创建一个空项目,为Startup类添加MVC服务和请求中间件:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(routes => { routes.MapRoute( name: "goto_one", template: "one", defaults: new { controller = "Home", action = "PageOne" }); routes.MapRoute( name: "goto_two", template: "two/{id?}", defaults: new { controller = "Home", action = "PageTwo" }); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
创建一个控制器HomeController,来演示常规路由:
public class HomeController : Controller { public IActionResult Index() { return Content("Home/Index"); } public IActionResult PageOne() { return Content("Home/One"); } [HttpGet] public IActionResult PageTwo() { return Content("(GET) Home/Two"); } [HttpPost] public IActionResult PageTwo(int id) { return Content($"(POST) Home/Two: {id}"); } }
创建一个控制器WorkController,来演示特性路由:
[Route("work")] public class WorkController : Controller { public IActionResult Index() { return Content("Work/Index"); } [Route("one")] public IActionResult PageOne() { return Content("Work/One"); } [HttpGet("two")] public IActionResult PageTwo() { return Content("(GET) Work/Two"); } [HttpPost("two/{id?}")] public IActionResult PageTwo(int id) { return Content($"(POST) Work/Two: {id}"); } }
讨论
ASP.NET Core的路由引擎可以将传入的请求映射到控制器和它们的方法中。这是通过向请求管道中添加路由中间件实现的,具体来说是使用IRouteBuilder将URL规则(模板)映射到一个控制器的方法。
路由模板
路由模板可以使用字面值和标记(标识路由参数)。在匹配一个路由时,字面值会严格匹配URL中的文本,而标记会被替换掉。
为了匹配一个模板,模板中必须包含控制器和方法标记以便定位控制器方法(这是MVC的核心信息)。模板中的其它标记被映射为方法的参数(通过模型绑定实现)。
当添加一个路由映射时,可以为标记提供缺省值。当模板中不包含控制器和方法标记时会很有用。模板也可以包含对应于方法参数的可选标记。
让我们来看一个示例模板:
contact/{controller=Home}/{action=Index}/{id?}
注意如下几点:
- 标记包含中大括号中。这里有三个标记,分别是controller,action和id。
- 模板中包含一个字面值contact,它会匹配URL中的文本。
- 已经为controller(Home)和action(Index)提供了默认值。
- 可选标记通过问号来声明。
下面的URL会匹配这个模板:
- /contact/Home/Index/1: 所有标记都有值。
- /contact/Home/Index: 忽略了可选标记。
- /contact/Home: 忽略了action标记,将使用默认值Index。
- /contact: 忽略了controller和action标记,将分别使用其默认值Home和Index。
常规路由
常规路由为URL路径建立一个约定, 例如给定一个模板:
- 第一个标记映射到控制器
- 第二个标记映射到方法
- 第三个标记映射到可选的方法参数id
你也可以从模板中省略控制器和方法,只要你为它们提供缺省值就行了。比如下面的路由会映射到地址/one,因为通过defaults提供了所需的控制器和方法标记:
routes.MapRoute( name: "goto_one", template: "one", defaults: new { controller = "Home", action = "PageOne" });
注:请将此特定路由添加到通用路由之前,因为路由是按照定义的顺序执行的,一旦某个路由匹配成功,则整个匹配流程就会终结。
由于路由中间件只使用了控制器和方法标记来映射到一个控制器方法,因此同一个控制器中放置多个同名的的方法将会抛出异常。为了解决这个问题,可以使用方法上的IActionConstraint特性(比如HttpGet,HttpPost等特性):
[HttpGet("two")] public IActionResult PageTwo() { return Content("(GET) Work/Two"); } [HttpPost("two/{id?}")] public IActionResult PageTwo(int id) { return Content($"(POST) Work/Two: {id}"); }
====start by sanshi=========================
为了观察控制器中同名方法出现的异常,我们首先需要修改Configure()方法,添加开发时异常处理中间件:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(routes => ....); }
修改HomeController:
public IActionResult PageTwo() { return Content("(GET) Home/Two"); } public IActionResult PageTwo(int id) { return Content($"(POST) Home/Two: {id}"); }
看似很正常的重载函数,但是放到控制器中会抛出异常。
在浏览器地址栏敲入:http://localhost:65415/Home/PageTwo,观看到异常页面:
====end by sanshi=========================
特性路由
特性路由通过直接为控制器和方法提供路由模板来实现。
我们可以使用[Route]或者[HttpGet](或者其他动词)特性来指定模板。这些模板可以包含字面值和标记(不能包含控制器和方法标记)。
运行时,控制器的特性模板和方法的特性模板会被合并到一起,比如,在WorkController中,PageOne方法可以通过/work/one访问:
[Route("work")] public class WorkController : Controller { [Route("one")] public IActionResult PageOne() { return Content("Work/One"); } }
源代码下载
原文:https://tahirnaushad.com/2017/08/20/asp-net-core-mvc-routing/