開始
回顧上一篇文章:dotnet core開發體驗之開始MVC 里面體驗了一把mvc,然后我們知道了aspnet mvc是靠Routing來驅動起來的,所以感覺需要研究一下Routing是什么鬼。
Routing簡單使用體驗
首先我們用命令yo aspnet
創建一個新的空web項目。(Yeoman的使用自己研究,參考:https://docs.asp.net/en/latest/client-side/yeoman.html?#building-projects-with-yeoman)
創建完項目后,在project.json里面添加Routing依賴。
"dependencies": {
...
"Microsoft.AspNetCore.Routing": "1.0.0-*"
},
添加完依賴后,修改Startup里面的Configure,和ConfigureServices里面添加Routing的使用依賴
修改前:
public void Configure(IApplicationBuilder app)
{
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
修改后:
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的最基本運行原理
Routing的驅動入口就是基於middleware的。可以先看看 app.UseRouter(endpoint)
的內部實現,參考一個擴展方法類RoutingBuilderExtensions,可以看到最后有一句代碼return builder.UseMiddleware<RouterMiddleware>(router)
。這里可以很明顯看到,入口在RouterMiddleware的Invoke方法。
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);
}
}
這個入口的實現是這樣的:
Invoke入口 ===>>> 實例化一個 RouteContext ===>>> 把我們傳進來的 IRouter 存到 RouteContext里面的 RouteData =>>> 再執行IRouter的RouteAsync方法把RouteContext對象傳進去提供給具體實現使用。=>>> 如果context.Handler沒有東西,就執行下一個RequestDelegate,如果有的話就把RouteData保存起來然后執行這個RequestDelegate。
看到了這里我們已經可以明白下面這個代碼運行起來的原理。
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運行起來的一個大概原理,但是我們一直打印出相同內容,確實也沒有什么卵用呀。我們要改一下讓打印內容能有點改變。這個時候可以使用到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 看完基本就知道都有些什么樣的模板格式可以使用了。
實現自己的 RouteHandler
到現在,我們已經知道了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的體驗文章到這來就結束了,謝謝觀看。
由於本人水平有限,知識有限,文章難免會有錯誤,歡迎大家指正。如果有什么問題也歡迎大家回復交流。要是你覺得本文還可以,那么點擊一下推薦。