原文:Dynamic controller routing in ASP.NET Core 3.0
做者:Filip W
譯文:http://www.javashuo.com/article/p-celdwqng-dn.html
譯者:Lamond Luhtml
今天在網上看到了這篇關於ASP.NET Core動態路由的文章,感受蠻有意思的,給你們翻譯一下,雖然文中的例子不必定會在平常編碼中出現,可是也給咱們提供了必定的思路。git
相對於ASP.NET MVC以及ASP.NET Core MVC中的舊版本路由特性, 在ASP.NET Core 3.0中新增了一個不錯的擴展點,即程序獲取到路由後,能夠將其動態指向一個給定的controller/action.github
這個功能有很是多的使用場景。若是你正在使用從ASP.NET Core 3.0 Preview 7及更高版本,你就能夠在ASP.NET Core 3.0中使用它了。web
PS: 官方沒有在Release Notes中提到這一點。數據庫
下面就讓咱們一塊兒來看一看ASP.NET Core 3.0中的動態路由。c#
當咱們使用MVC路由的時候,最典型的用法是,咱們使用路由特性(Route Attributes)來定義路由信息。使用這種方法,咱們須要要爲每一個路由進行顯式的聲明。app
public class HomeController : Controller { [Route("")] [Route("Home")] [Route("Home/Index")] public IActionResult Index() { return View(); } }
相對的,你可使用中心化的路由模型,使用這種方式,你就不須要顯式的聲明每個路由 - 這些路由會自動被全部發現的控制器的自動識別。 然而,這樣作的前提是,全部的控制器首先必須存在。async
如下是ASP.NET Core 3.0中使用新語法Endpoint Routing的實現方式。ide
app.UseEndpoints( endpoints => { endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); } );
以上兩種方式的共同點是,全部的路由信息都必須在應用程序啓動時加載。ui
可是,若是你但願可以動態定義路由, 並在應用程序運行時添加/刪除它們,該怎麼辦?
下面我給你們列舉幾個動態定義路由的使用場景。
這個問題的處理過程應該至關的好理解。咱們但願儘早的攔截路由處理,檢查已爲其解析的當前路由值,並使用例如數據庫中的數據將它們「轉換」爲一組新的路由值,這些新的路由值指向了一個實際存在的控制器。
在舊版本的ASP.NET Core MVC中, 咱們一般經過自定義IRouter
接口,來解決這個問題。然而在ASP.NET Core 3.0中這種方式已經行不通了,由於路由已經改由上面提到的Endpoint Routing來處理。值得慶幸的是,ASP.NET Core 3.0 Preview 7以及後續版本中,咱們能夠經過一個新特性MapDynamicControllRoute
以及一個擴展點DynamicRouteValueTransformer
, 來支持咱們的需求。下面讓咱們看一個具體的例子。
想象一下,在你的項目中,有一個OrderController
控制器,而後你但願它支持多語言翻譯路由。
public class OrdersController : Controller { public IActionResult List() { return View(); } }
咱們可能但願的請求的URL是這樣的,例如
那麼咱們如今該如何解決這個問題呢?咱們可使用新特性MapDynamicControllerRoute
來替代默認的MVC路由, 並將其指向咱們自定義的DynamicRouteValueTransformer
類, 該類實現了咱們以前提到的路由值轉換 。
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddSingleton<TranslationTransformer>(); services.AddSingleton<TranslationDatabase>(); } public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapDynamicControllerRoute<TranslationTransformer>("{language}/{controller}/{action}"); }); } }
這裏咱們定義了一個TranslationTransformer
類,它繼承了DynamicRouteValueTransformer
類。這個新類將負責將特定語言路由值,轉換爲能夠在咱們應用能夠匹配到controller/action的路由值字典,而這些值一般不能直接和咱們應用中的任何controller/action匹配。因此這裏簡單點說,就是在德語場景下,controller名會從「Bestellungen」轉換成"Orders", action名"Liste"轉換成"List"。
TranslationTransformer
類被做爲泛型類型參數,傳入MapDynamicControllerRoute
方法中,它必須在依賴注入容器中註冊。這裏,咱們還須要註冊一個TranslationDatabase
類,可是這個類僅僅爲了幫助演示,後面咱們會須要它。
public class TranslationTransformer : DynamicRouteValueTransformer { private readonly TranslationDatabase _translationDatabase; public TranslationTransformer(TranslationDatabase translationDatabase) { _translationDatabase = translationDatabase; } public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext , RouteValueDictionary values) { if (!values.ContainsKey("language") || !values.ContainsKey("controller") || !values.ContainsKey("action")) return values; var language = (string)values["language"]; var controller = await _translationDatabase.Resolve(language, (string)values["controller"]); if (controller == null) return values; values["controller"] = controller; var action = await _translationDatabase.Resolve(language, (string)values["action"]); if (action == null) return values; values["action"] = action; return values; } }
在這個轉換器中,咱們須要嘗試提取3個路由參數, language
, controller
,action
,而後咱們須要在模擬用的數據庫類中,找到其對應的翻譯。正如咱們以前提到的,你一般會但願從數據庫中查找對應的內容,由於使用這種方式,咱們能夠在應用程序生命週期的任什麼時候刻,動態的影響路由。爲了說明這一點,咱們將使用TranslationDatabase
類來模擬數據庫操做,這裏你能夠把它想象成一個真正的數據庫倉儲服務。
public class TranslationDatabase { private static Dictionary<string, Dictionary<string, string>> Translations = new Dictionary<string, Dictionary<string, string>> { { "en", new Dictionary<string, string> { { "orders", "orders" }, { "list", "list" } } }, { "de", new Dictionary<string, string> { { "bestellungen", "orders" }, { "liste", "list" } } }, { "pl", new Dictionary<string, string> { { "zamowienia", "order" }, { "lista", "list" } } }, }; public async Task<string> Resolve(string lang, string value) { var normalizedLang = lang.ToLowerInvariant(); var normalizedValue = value.ToLowerInvariant(); if (Translations.ContainsKey(normalizedLang) && Translations[normalizedLang] .ContainsKey(normalizedValue)) { return Translations[normalizedLang][normalizedValue]; } return null; } }
到目前爲止,咱們已經很好的解決了這個問題。這裏經過在MVC應用中啓用這個設置,咱們就能夠向咱們以前定義的3個路由發送請求了。
每一個請求都會命中OrderController
控制器和List
方法。當前你能夠將這個方法進一步擴展到其餘的控制器。但最重要的是,若是新增一種新語言或者新的路由別名映射到現有語言中的controller/actions,你是不須要作任何代碼更改,甚至重啓項目的。
請注意,在本文中,咱們只關注路由轉換,這裏僅僅是爲了演示ASP.NET Core 3.0中的動態路由特性。若是你但願在應用程序中實現本地化,你可能還須要閱讀ASP.NET Core 3.0的本地化指南, 由於你能夠須要根據語言的路由值設置正確的CurrentCulture
。
最後, 我還想再補充一點,在咱們以前的例子中,咱們在路由模板中顯式的使用了{controller}
和{action}
佔位符。這並非必須的,在其餘場景中,你還可使用"catch-all"
路由通配符,並將其轉換爲controller/action路由值。
"catch-all"
路由通配符是CMS系統中的典型解決方案,你可使用它來處理不一樣的動態「頁面」路由。
它看起來可能相似:
endpoints.MapDynamicControllerRoute<PageTransformer>("pages/{**slug}");
而後,你須要將pages
以後的整個URL參數轉換爲現有可執行控制器的內容 - 一般URL/路由的映射是保存在數據庫中的。
但願你會發現這篇文章頗有用 - 全部的演示源代碼均可以在Github上找到。