【轉】ASP.NET Core MVC 配置全局路由前綴

本文地址:http://www.cnblogs.com/savorboard/p/dontnet-IApplicationModelConvention.html
做者博客:Savorboard

前言

你們好,今天給你們介紹一個 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 接口

首先,咱們須要使用到 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

  • ApiExplorerModel:主要是配置默認MVC Api Explorer的一些東西,包括Api的描述信息,組信息,可見性等。
  • ControllerModel:主要是 Comtroller 默認約定相關的了,這個裏面東西就比較多了,就不一一介紹了,咱們等下就要配置裏面的一個東西。
  • IFilterMetadata :空接口,主要起到標記的做用。

還有一個地方須要告訴你們的是,能夠看到上面的 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

相關文章
相關標籤/搜索