ASP.NET Core 3.0中使用動態控制器路由


原文:Dynamic controller routing in ASP.NET Core 3.0
作者:Filip W
譯文:https://www.cnblogs.com/lwqlun/p/11461657.html
譯者:Lamond Lu

譯者注

今天在網上看到了這篇關於ASP.NET Core動態路由的文章,感覺蠻有意思的,給大家翻譯一下,雖然文中的例子不一定會在日常編碼中出現,但是也給我們提供了一定的思路。

前言

相對於ASP.NET MVC以及ASP.NET Core MVC中的舊版本路由特性, 在ASP.NET Core 3.0中新增了一個不錯的擴展點,即程序獲取到路由后,可以將其動態指向一個給定的controller/action.

這個功能有非常多的使用場景。如果你正在使用從ASP.NET Core 3.0 Preview 7及更高版本,你就可以在ASP.NET Core 3.0中使用它了。

PS: 官方沒有在Release Notes中提到這一點。

下面就讓我們一起來看一看ASP.NET Core 3.0中的動態路由。

背景

當我們使用MVC路由的時候,最典型的用法是,我們使用路由特性(Route Attributes)來定義路由信息。使用這種方法,我們需要要為每個路由進行顯式的聲明。

public class HomeController : Controller
{
   [Route("")]
   [Route("Home")]
   [Route("Home/Index")]
   public IActionResult Index()
   {
      return View();
   }
}

相對的,你可以使用中心化的路由模型,使用這種方式,你就不需要顯式的聲明每一個路由 - 這些路由會自動被所有發現的控制器的自動識別。 然而,這樣做的前提是,所有的控制器首先必須存在。

以下是ASP.NET Core 3.0中使用新語法Endpoint Routing的實現方式。

app.UseEndpoints(
    endpoints =>
    {
        endpoints.MapControllerRoute("default", 
                  "{controller=Home}/{action=Index}/{id?}");
    }
);

以上兩種方式的共同點是,所有的路由信息都必須在應用程序啟動時加載。

但是,如果你希望能夠動態定義路由, 並在應用程序運行時添加/刪除它們,該怎么辦?

下面我給大家列舉幾個動態定義路由的使用場景。

  • 多語言路由,以及使用新語言時,針對那些新語言路由的修改。
  • 在CMS類型的系統中,我們可能會動態添加一些新頁面,這些新頁面不需要創建的控制器或者在源碼中硬編碼路由信息。
  • 多租戶應用中,租戶路由可以在運行時動態激活或者取消激活。

這個問題的處理過程應該相當的好理解。我們希望盡早的攔截路由處理,檢查已為其解析的當前路由值,並使用例如數據庫中的數據將它們“轉換”為一組新的路由值,這些新的路由值指向了一個實際存在的控制器。

實例問題 - 多語言翻譯路由問題

在舊版本的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是這樣的,例如

  • 英語 - /en/orders/list
  • 德語 - /de/bestellungen/liste
  • 波蘭語 - /pl/zamowienia/lista

使用動態路由處理多語言翻譯路由問題

那么我們現在該如何解決這個問題呢?我們可以使用新特性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個路由發送請求了。

  • 英語 - /en/orders/list
  • 德語 - /de/bestellungen/liste
  • 波蘭語 - /pl/zamowienia/lista

每個請求都會命中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上找到。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM