回顾上一篇文章:dotnet core开发体验之开始MVC 里面体验了一把mvc,而后咱们知道了aspnet mvc是靠Routing来驱动起来的,因此感受须要研究一下Routing是什么鬼。html
首先咱们用命令yo aspnet
建立一个新的空web项目。(Yeoman的使用本身研究,参考:https://docs.asp.net/en/latest/client-side/yeoman.html?#building-projects-with-yeoman)git
建立完项目后,在project.json里面添加Routing依赖。github
"dependencies": { ... "Microsoft.AspNetCore.Routing": "1.0.0-*" },
添加完依赖后,修改Startup里面的Configure,和ConfigureServices里面添加Routing的使用依赖
修改前:web
public void Configure(IApplicationBuilder app) { app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }
修改后:json
public void ConfigureServices(IServiceCollection services) { services.AddRouting(); } public void Configure(IApplicationBuilder app) { var endpoint = new RouteHandler((c) => c.Response.WriteAsync("Hello, I am Routing!")); app.UseRouter(endpoint); }
dotnet run 而后浏览器访问http://localhost:5000/ 显示为Hello, I am Routing! 接下来咱们在http://localhost:5000/ 后面加入一些其余的东西来访问,发现其实仍是同样打印Hello, I am Routing! 这让咱们感受好像并无什么卵用的样子。不着急咱们先来看看Routing是怎么运行起来的。在开始这话题以前须要先了解到什么是中间件,参考:https://docs.asp.net/en/latest/fundamentals/middleware.html浏览器
Routing的驱动入口就是基于middleware的。能够先看看 app.UseRouter(endpoint)
的内部实现,参考一个扩展方法类RoutingBuilderExtensions,能够看到最后有一句代码return builder.UseMiddleware<RouterMiddleware>(router)
。这里能够很明显看到,入口在RouterMiddleware的Invoke方法。mvc
public async Task Invoke(HttpContext httpContext) { var context = new RouteContext(httpContext); context.RouteData.Routers.Add(_router); await _router.RouteAsync(context); if (context.Handler == null) { _logger.RequestDidNotMatchRoutes(); await _next.Invoke(httpContext); } else { httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature() { RouteData = context.RouteData, }; await context.Handler(context.HttpContext); } }
这个入口的实现是这样的:app
Invoke入口 ===>>> 实例化一个 RouteContext ===>>> 把咱们传进来的 IRouter 存到 RouteContext里面的 RouteData ===>>> 再执行IRouter的RouteAsync方法把RouteContext对象传进去提供给具体实现使用。===>>> 若是context.Handler没有东西,就执行下一个RequestDelegate,若是有的话就把RouteData保存起来而后执行这个RequestDelegate。asp.net
看到了这里咱们已经能够明白下面这个代码运行起来的原理。async
public void Configure(IApplicationBuilder app) { var endpoint = new RouteHandler((c) => c.Response.WriteAsync("Hello, I am Routing!")); app.UseRouter(endpoint); }
可能还会有人不明白RouteHandler是个什么鬼,既然咱们知道了代码的实现运行原理,那么确定能够猜到RouteHandler是有实现接口IRouter的。咱们能够看RouteHandler代码
public Task RouteAsync(RouteContext context) { context.Handler = _requestDelegate; return TaskCache.CompletedTask; }
这里能够看到把咱们的(c) => c.Response.WriteAsync("Hello, I am Routing!")
赋值给context.Handler,而后由RouterMiddleware来执行咱们这个事件方法。因而咱们就能够在浏览器上面看到输出 Hello, I am Routing!这么一句话了。
文章到如今,咱们虽然知道了Routing运行起来的一个大概原理,可是咱们一直打印出相同内容,确实也没有什么卵用呀。咱们要改一下让打印内容能有点改变。这个时候可使用到Routing提供的Route类来使用。代码修改以下:
public void Configure(IApplicationBuilder app) { var endpoint = new RouteHandler((c) => c.Response.WriteAsync($"Hello, I am Routing! your item is {c.GetRouteValue("item")}")); var resolver = app.ApplicationServices.GetRequiredService<IInlineConstraintResolver>(); var runRoute = new Route(endpoint,"{item}",resolver); app.UseRouter(runRoute); }
修改完代码后,咱们再次编译运行,而后输入http://localhost:5000/ 咱们发现一片空白,而后再输入http://localhost:5000/abc 发现打印出来的是Hello, I am Routing! your item is abc。而后再输入其余的 http://localhost:5000/abc/cc 发现也是一片空白。这是由于咱们给路由添加的匹配是主机地址/+{item}
那其余的路径都是匹配不到,那么确定就是不会显示任何东西啦。假设咱们要给一个默认值,那么能够改为这样
var runRoute = new Route(endpoint,"{item=home}",resolver);
OK,这个时候咱们再输入http://localhost:5000/ 看到的就是Hello, I am Routing! your item is home。
匹配原理相对比较复杂点,想要了解的话能够参考 RouteBase的源码,而后看相关的类,看看咱们设置的模板是如何解析的,而后如何和url进行匹配的。若是要要来解释完整个过程的话,这个文章确定是不够的,因此各位能够本身了解一下。
假如要配置多个路由支持的话,可使用RouteCollection
public void Configure(IApplicationBuilder app) { var endpoint = new RouteHandler((c) => c.Response.WriteAsync($"Hello, I am Routing! your item is {c.GetRouteValue("item")}")); var resolver = app.ApplicationServices.GetRequiredService<IInlineConstraintResolver>(); var runRoute = new Route(endpoint,"{item=home}",resolver); var otherRoute = new Route(endpoint,"other/{item=other_home}",resolver); var routeCollection = new RouteCollection(); routeCollection.Add(runRoute); routeCollection.Add(otherRoute); app.UseRouter(routeCollection); }
修改为上面的代码后就支持两个路由,假如输入的url是 http://localhost:5000/other 那么就是使用runRoute,若是输入的是http://localhost:5000/other/myother 那么使用的就是otherRoute。
这样书写暴露了不少细节东西,咱们能够用 Routing提供的RouteBuilder类来编写相同的东西。代码修改一下以下:
public void Configure(IApplicationBuilder app) { var endpoint = new RouteHandler((c) => c.Response.WriteAsync($"Hello, I am Routing! your item is {c.GetRouteValue("item")}")); var routeBuilder = new RouteBuilder(app) { DefaultHandler = endpoint, }; routeBuilder.MapRoute("default","{item=home}"); routeBuilder.MapRoute("other","other/{item=other_home}"); app.UseRouter(routeBuilder.Build()); }
若是有一些特殊的的路由配置,咱们也可使用routeBuilder.Routes.Add(route);
这代码来添加。至于能配置的模板都有些什么,能够看 Routing 的 Template 的测试类:https://github.com/aspnet/Routing/tree/dev/test/Microsoft.AspNetCore.Routing.Tests/Template 看完基本就知道都有些什么样的模板格式可使用了。
到如今,咱们已经知道了Routing大概是怎么运行起来,知道了如何简单的使用。那么接下来能够来建立一个本身的RouteHandler,来加深一下对Routing的使用体验。
建立一个类MyRouteHandler,实现接口IRoute:
public class MyRouteHandler : IRouter { public VirtualPathData GetVirtualPath(VirtualPathContext context) { return null; } public Task RouteAsync(RouteContext context) { context.Handler = (c) => { var printStr = $"controller:{c.GetRouteValue("controller")}," + $"action:{c.GetRouteValue("action")},id:{c.GetRouteValue("id")}"; return c.Response.WriteAsync(printStr); }; return TaskCache.CompletedTask; } }
而后咱们的路由配置改为这样:
public void Configure(IApplicationBuilder app) { var endpoint = new MyRouteHandler(); var routeBuilder = new RouteBuilder(app) { DefaultHandler = endpoint, }; routeBuilder.MapRoute("default","{controller=Home}/{action=Index}/{id?}"); app.UseRouter(routeBuilder.Build()); }
而后打开浏览器http://localhost:5000/ 打印出来的内容是 controller:Home,action:Index,id:
。这样是否是很像咱们去调用mvc的控制器和控制器的行为呢?Routing的体验文章到这来就结束了,谢谢观看。
因为本人水平有限,知识有限,文章不免会有错误,欢迎你们指正。若是有什么问题也欢迎你们回复交流。要是你以为本文还能够,那么点击一下推荐。