在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
新版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中,就可以直接设置
目前支持的约束如下:
而对于可选参数,则值需要在约束类型后面加一个问号即可,示例如下: 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添加的时候默认添加的是 举例来说,我们先创建一个空的ASP.NET 5项目,并在project.json文件的dependencies节点中添加程序集 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为 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; } } 上述类,继承 访问如下网址即可查看相应的结果: 正常:`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` 注意: MVC中的Routing 在MVC示例程序中,我们只需要配置在调用 第一步:在project.json文件的dependencies节点中引用程序集 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,其中 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" }; } } 访问 这就是我们在前面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节点中引用程序集 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,有三种方式,分别如下: 继承于TemplateRoute实现IRouter实现INamedRouter(注:INamedRouter和IRouter的唯一区别是多了一个名称) 本例中,我们以继承继承于TemplateRoute为例,首先创建一个继承于该类的子类 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" }); 通过这种方式,我们可以在符合路由匹配条件的时候,使用 基于Attribute的Routing 基于Attribute的Routing功能一直是MVC所期待的功能,在Web API已经通过 举例来说: [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使用的 [Route("products/{productId:int}")] Controller和Action标记位 另外,针对Route的模板字符串,不仅支持内联参数,还支持Controller和Action的标记位,即不用写死该Controller或Action的名称,使用一个 [Route("book/[controller]/[action]")] 这样访问首页的地址就变成了: Web API的等价Route定义 在Web API中,我们一般还要定义GET、POST这样的请求方式,为了方便,新版的HTTPGET等一系列方法都集成了Route功能,直接在构造函数传入Route模板即可,示例如下: [HttpGet("products/{productId:int}")] 上述Route的定义,即表明,既要符合 其实HTTPGET这一系列Attribute也可以在普通的MVC Controller上使用,因为在MVC6中,MVC Controller和Web API Controller本身就是同一个东西,只不过MVC的返回类型都是IActionResult而已。Route定义,不仅仅支持GET请求,还支持POST等其它类型的请求,即不限制请求方式。在HttpXXX系列特性中,也是支持内联参数和[controller]、[action]标记位的,大可放心使用。目前可用的特性类有: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的名称根本就不会起作用,即 所以要让上述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(); } } 这样,就可以通过 [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(); } } 此时, 规则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(); } } 这样 [Route("Contact")] [HttpPost("home/Contact2")] 此时,访问该Action的方式,要么是以GET的方式访问 [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 但是,在视图文件中,通过 [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的,并且这些约束还是可以扩展的,本节我们就来看看如何自定义一些扩展。 无参数约束 首先,我们来看一个比较简单的约束,即无参数约束,类似于 [Route("index/{productId:aabbcc}")] 为了确保/index/112233和/index/aabbcc是符合约束的,而/index/aabbccdd是不符合约束的,我们首先要自定义一个约束类 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方法的参数中,其中 下一步,就是要将该约束类注册到Routing系统的约束集合中,在 services.Configure<RouteOptions>(opt => { opt.ConstraintMap.Add("aabbcc", typeof(AABBCCRouteConstraint)); }); 注意,这里注册的 有参数约束 一般情况下,有些时候可能需要定义一些约束的值,比如 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的路由使用方式很简单,但是相关的使用规则却很复杂,大家使用的时候需要多加注意。 |
请发表评论