原文:Dynamic controller routing in ASP.NET Core 3.0
<https://www.strathweb.com/2019/08/dynamic-controller-routing-in-asp-net-core-3-0/>
作者:Filip W <http://twitter.com/filip_woj>
譯文:https://www.cnblogs.com/lwqlun/p/11461657.html
<https://www.cnblogs.com/lwqlun/p/11461657.html>
譯者:Lamond Lu
譯者注
今天在網(wǎng)上看到了這篇關(guān)于ASP.NET
Core動態(tài)路由的文章,感覺蠻有意思的,給大家翻譯一下,雖然文中的例子不一定會在日常編碼中出現(xiàn),但是也給我們提供了一定的思路。
前言
相對于ASP.NET MVC以及ASP.NET Core MVC中的舊版本路由特性, 在ASP.NET Core
3.0中新增了一個(gè)不錯(cuò)的擴(kuò)展點(diǎn),即程序獲取到路由后,可以將其動態(tài)指向一個(gè)給定的controller/action.
這個(gè)功能有非常多的使用場景。如果你正在使用從ASP.NET Core 3.0 Preview 7及更高版本,你就可以在ASP.NET Core
3.0中使用它了。
PS: 官方?jīng)]有在Release Notes中提到這一點(diǎn)。
下面就讓我們一起來看一看ASP.NET Core 3.0中的動態(tài)路由。
背景
當(dāng)我們使用MVC路由的時(shí)候,最典型的用法是,我們使用路由特性(Route
Attributes)來定義路由信息。使用這種方法,我們需要要為每個(gè)路由進(jìn)行顯式的聲明。
public class HomeController : Controller { [Route("")] [Route("Home")]
[Route("Home/Index")] public IActionResult Index() { return View(); } }
相對的,你可以使用中心化的路由模型,使用這種方式,你就不需要顯式的聲明每一個(gè)路由 - 這些路由會自動被所有發(fā)現(xiàn)的控制器的自動識別。
然而,這樣做的前提是,所有的控制器首先必須存在。
以下是ASP.NET Core 3.0中使用新語法Endpoint Routing的實(shí)現(xiàn)方式。
app.UseEndpoints( endpoints => { endpoints.MapControllerRoute("default",
"{controller=Home}/{action=Index}/{id?}"); } );
以上兩種方式的共同點(diǎn)是,所有的路由信息都必須在應(yīng)用程序啟動時(shí)加載。
但是,如果你希望能夠動態(tài)定義路由, 并在應(yīng)用程序運(yùn)行時(shí)添加/刪除它們,該怎么辦?
下面我給大家列舉幾個(gè)動態(tài)定義路由的使用場景。
* 多語言路由,以及使用新語言時(shí),針對那些新語言路由的修改。
* 在CMS類型的系統(tǒng)中,我們可能會動態(tài)添加一些新頁面,這些新頁面不需要?jiǎng)?chuàng)建的控制器或者在源碼中硬編碼路由信息。
* 多租戶應(yīng)用中,租戶路由可以在運(yùn)行時(shí)動態(tài)激活或者取消激活。
這個(gè)問題的處理過程應(yīng)該相當(dāng)?shù)暮美斫狻N覀兿MM早的攔截路由處理,檢查已為其解析的當(dāng)前路由值,并使用例如數(shù)據(jù)庫中的數(shù)據(jù)將它們“轉(zhuǎn)換”為一組新的路由值,這些新的路由值指向了一個(gè)實(shí)際存在的控制器。
實(shí)例問題 - 多語言翻譯路由問題
在舊版本的ASP.NET Core MVC中, 我們通常通過自定義IRouter接口,來解決這個(gè)問題。然而在ASP.NET Core
3.0中這種方式已經(jīng)行不通了,因?yàn)槁酚梢呀?jīng)改由上面提到的Endpoint Routing來處理。值得慶幸的是,ASP.NET Core 3.0 Preview
7以及后續(xù)版本中,我們可以通過一個(gè)新特性MapDynamicControllRoute以及一個(gè)擴(kuò)展點(diǎn)DynamicRouteValueTransformer,
來支持我們的需求。下面讓我們看一個(gè)具體的例子。
想象一下,在你的項(xiàng)目中,有一個(gè)OrderController控制器,然后你希望它支持多語言翻譯路由。
public class OrdersController : Controller { public IActionResult List() {
return View(); } }
我們可能希望的請求的URL是這樣的,例如
* 英語 - /en/orders/list
* 德語 - /de/bestellungen/liste
* 波蘭語 - /pl/zamowienia/lista
使用動態(tài)路由處理多語言翻譯路由問題
那么我們現(xiàn)在該如何解決這個(gè)問題呢?我們可以使用新特性MapDynamicControllerRoute來替代默認(rèn)的MVC路由, 并將其指向我們自定義的
DynamicRouteValueTransformer類, 該類實(shí)現(xiàn)了我們之前提到的路由值轉(zhuǎn)換 。
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}");
}); } }
這里我們定義了一個(gè)TranslationTransformer類,它繼承了DynamicRouteValueTransformer
類。這個(gè)新類將負(fù)責(zé)將特定語言路由值,轉(zhuǎn)換為可以在我們應(yīng)用可以匹配到controller/action的路由值字典,而這些值通常不能直接和我們應(yīng)用中的任何controller/action匹配。所以這里簡單點(diǎn)說,就是在德語場景下,controller名會從“Bestellungen”轉(zhuǎn)換成"Orders",
action名"Liste"轉(zhuǎn)換成"List"。
TranslationTransformer類被作為泛型類型參數(shù),傳入MapDynamicControllerRoute
方法中,它必須在依賴注入容器中注冊。這里,我們還需要注冊一個(gè)TranslationDatabase類,但是這個(gè)類僅僅為了幫助演示,后面我們會需要它。
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; } }
在這個(gè)轉(zhuǎn)換器中,我們需要嘗試提取3個(gè)路由參數(shù), language, controller,action
,然后我們需要在模擬用的數(shù)據(jù)庫類中,找到其對應(yīng)的翻譯。正如我們之前提到的,你通常會希望從數(shù)據(jù)庫中查找對應(yīng)的內(nèi)容,因?yàn)槭褂眠@種方式,我們可以在應(yīng)用程序生命周期的任何時(shí)刻,動態(tài)的影響路由。為了說明這一點(diǎn),我們將使用
TranslationDatabase類來模擬數(shù)據(jù)庫操作,這里你可以把它想象成一個(gè)真正的數(shù)據(jù)庫倉儲服務(wù)。
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; } }
到目前為止,我們已經(jīng)很好的解決了這個(gè)問題。這里通過在MVC應(yīng)用中啟用這個(gè)設(shè)置,我們就可以向我們之前定義的3個(gè)路由發(fā)送請求了。
* 英語 - /en/orders/list
* 德語 - /de/bestellungen/liste
* 波蘭語 - /pl/zamowienia/lista
每個(gè)請求都會命中OrderController控制器和List
方法。當(dāng)前你可以將這個(gè)方法進(jìn)一步擴(kuò)展到其他的控制器。但最重要的是,如果新增一種新語言或者新的路由別名映射到現(xiàn)有語言中的controller/actions,你是不需要做任何代碼更改,甚至重啟項(xiàng)目的。
請注意,在本文中,我們只關(guān)注路由轉(zhuǎn)換,這里僅僅是為了演示ASP.NET Core
3.0中的動態(tài)路由特性。如果你希望在應(yīng)用程序中實(shí)現(xiàn)本地化,你可能還需要閱讀ASP.NET Core 3.0的本地化指南
<https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-3.0>
, 因?yàn)槟憧梢孕枰鶕?jù)語言的路由值設(shè)置正確的CurrentCulture。
最后, 我還想再補(bǔ)充一點(diǎn),在我們之前的例子中,我們在路由模板中顯式的使用了{(lán)controller}和{action}
占位符。這并不是必須的,在其他場景中,你還可以使用"catch-all"路由通配符,并將其轉(zhuǎn)換為controller/action路由值。
"catch-all"路由通配符是CMS系統(tǒng)中的典型解決方案,你可以使用它來處理不同的動態(tài)“頁面”路由。
它看起來可能類似:
endpoints.MapDynamicControllerRoute<PageTransformer>("pages/{**slug}");
然后,你需要將pages之后的整個(gè)URL參數(shù)轉(zhuǎn)換為現(xiàn)有可執(zhí)行控制器的內(nèi)容 - 通常URL/路由的映射是保存在數(shù)據(jù)庫中的。
希望你會發(fā)現(xiàn)這篇文章很有用 - 所有的演示源代碼都可以在Github
<https://github.com/filipw/Strathweb.Samples.DynamicControllerRouting>上找到。
熱門工具 換一換