你們好,今天給你們介紹一個 ASP.NET Core MVC 的一個新特性,給全局路由添加統一前綴。嚴格說其實不算是新特性,不過是Core MVC特有的。html
不知道你們在作 Web Api 應用程序的時候,有沒有遇到過這種場景,就是全部的接口都是以 /api 開頭的,也就是咱們的api 接口請求地址是像這樣的:swift
http://www.example.com/api/order/333
或者是這樣的需求api
http://www.example.com/api/v2/order/333
在之前,咱們若是要實現這種需求,能夠在 Controller 中添加一個 [Route("/api/order")]
這樣的特性路由 Attribute,而後MVC 框架就會掃描你的路由表從而能夠匹配到 /api/order
這樣的請求。
可是第二個帶版本號的需求,本來 Controller 的 Route 定義是 [Route("/api/v1/order")]
,如今要升級到v2,又有上百個接口,這就須要一個一個修改,可能就會懵逼了。app
如今,有一種更加簡便優雅的方式來作這個事情了,你能夠統一的來添加一個全局的前綴路由標記,下面就一塊兒來看看吧。框架
首先,咱們須要使用到 IApplicationModelConvention
這個接口,位於 Microsoft.AspNetCore.Mvc.ApplicationModels
命名空間下,咱們來看一下接口的定義。ide
public interface IApplicationModelConvention { void Apply(ApplicationModel application); }
咱們知道,MVC 框架有一些約定俗成的東西,那麼這個接口就是主要是用來自定義一些 MVC 約定的一些東西的,咱們能夠經過指定 ApplicationModel
對象來添加或者修改一些約定。能夠看到接口提供了一個 Apply
的方法,這個方法有一個ApplicationModel
對象,咱們能夠利用這個對象來修改咱們須要的東西,MVC 框架自己在啓動的時候會注入這個接口到 Services 中,因此咱們只須要實現這個接口,而後稍加配置便可。學習
那再讓咱們看一下ApplicationModel
這個對象都有哪些東西:ui
public class ApplicationModel : IPropertyModel, IFilterModel, IApiExplorerModel { public ApiExplorerModel ApiExplorer { get; set; } public IList<ControllerModel> Controllers { get; } public IList<IFilterMetadata> Filters { get; } public IDictionary<object, object> Properties { get; } }
能夠看到有 ApiExplorer
,Controllers
,Filters
,Properties
等屬性。this
還有一個地方須要告訴你們的是,能夠看到上面的 Controllers 屬性它是一個IList<ControllerModel>
,也就是說這個列表中記錄了你程序中的全部 Controller 的信息,你能夠經過遍歷的方式針對某一部分或某個 Controller 進行設置,包括Controller中的Actions的信息均可以經過此種方式來設置,咱們能夠利用這個特性來很是靈活的對 MVC 框架進行改造,是否是很炫酷。spa
下面,咱們就利用這個特性來實現咱們今天的主題。謝謝你點的贊~ :)
沒有那麼多廢話了,直接上代碼,要說的話全在代碼裏:
//定義個類RouteConvention,來實現 IApplicationModelConvention 接口 public class RouteConvention : IApplicationModelConvention { private readonly AttributeRouteModel _centralPrefix; public RouteConvention(IRouteTemplateProvider routeTemplateProvider) { _centralPrefix = new AttributeRouteModel(routeTemplateProvider); } //接口的Apply方法 public void Apply(ApplicationModel application) { //遍歷全部的 Controller foreach (var controller in application.Controllers) { // 已經標記了 RouteAttribute 的 Controller var matchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList(); if (matchedSelectors.Any()) { foreach (var selectorModel in matchedSelectors) { // 在 當前路由上 再 添加一個 路由前綴 selectorModel.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_centralPrefix, selectorModel.AttributeRouteModel); } } // 沒有標記 RouteAttribute 的 Controller var unmatchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel == null).ToList(); if (unmatchedSelectors.Any()) { foreach (var selectorModel in unmatchedSelectors) { // 添加一個 路由前綴 selectorModel.AttributeRouteModel = _centralPrefix; } } } } }
而後,咱們就能夠開始使用咱們本身定義的這個類了。
public static class MvcOptionsExtensions { public static void UseCentralRoutePrefix(this MvcOptions opts, IRouteTemplateProvider routeAttribute) { // 添加咱們自定義 實現IApplicationModelConvention的RouteConvention opts.Conventions.Insert(0, new RouteConvention(routeAttribute)); } }
最後,在 Startup.cs 文件中,添加上面的擴展方法就能夠了。
public class Startup { public Startup(IHostingEnvironment env) { //... } public void ConfigureServices(IServiceCollection services) { //... services.AddMvc(opt => { // 路由參數在此處仍然是有效的,好比添加一個版本號 opt.UseCentralRoutePrefix(new RouteAttribute("api/v{version}")); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { //... app.UseMvc(); } }
其中,opt.UseCentralRoutePrefix
就是上面定義的那個擴展方法,此處路由參數仍然是可使用的,因此好比你能夠給你的接口指定一個版本號之類的東西。這樣以後,你的全部 Controller 的 RoteAttribute 都會添加上了這個前綴,這樣就完美解決了最開始的那個版本號的需求。他們看起來大概是這樣的:
[Route("order")] public class OrderController : Controller { // 路由地址 : /api/v{version}/order/details/{id} [Route("details/{id}")] public string GetById(int id, int version) { //上面是能夠接收到版本號的,返回 version 和 id return $"other resource: {id}, version: {version}"; } } public class ItemController : Controller { // 路由地址: /api/v{version}/item/{id} [Route("item/{id}")] public string GetById(int id, int version) { //上面是能夠接收到版本號的,返回 version 和 id return $"item: {id}, version: {version}"; } }
上面的黑體字,但願你們可以理解並運用,這個例子只是實際需求中的很小的一個場景,在具體的項目中會有各類各樣正常或者非正常的需求,咱們在作一個功能的時候要多多思考,其實 MVC 框架還有不少東西能夠去學習,包括它的設計思想,擴展性等東西,都是須要慢慢領悟的。若是你們對 ASP.NET Core 感興趣,能夠關注我一下,我會按期在博客中分享個人一些學習成果吧。
感謝支持,若是你以爲這篇文章對你有幫助,謝謝你的【推薦】,晚安~。
手動敲了一遍:項目http://files.cnblogs.com/files/SzeCheng/VersionMid.zip