您现在的位置是:网站首页> 编程资料编程资料

解读ASP.NET 5 & MVC6系列教程(11):Routing路由_自学过程_

2023-05-24 481人已围观

简介 解读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的作用,作用很简单:

对于HTTP请求,Routing系统负责找出与之匹配的route,创建route数据,并将该请求派送到该route对于的处理程序(Handler)上。Controller和Action的选择,只是MVC的Handler的一个具体实现,该实现使用route数据和HTTP请求中的其它信息来选择要执行的Controller和Action。在新版的MVC6中,该处理程序的名称为MvcRouteHandler。

路由系统的执行流程如下:

ASP.NET 5监听到一个HTTP请求。然后Routing Middleware就会尝试将route集合中的route匹配该请求。一旦成功匹配一个请求,就找出该route对应的handler。调用该handler上的RouteAsync方法(因为所有的handler都要实现该接口方法)。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(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,有三种方式,分别如下:

继承于TemplateRoute实现IRouter实现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 defaults, IDictionary constraints, IDictionary dataTokens, IInlineConstraintResolver inlineConstraintResolver) : base(target, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver) 
                
                

-六神源码网