前言
相对于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中使用它了。
背景
当我们使用MVC路由的时候,最典型的用法是,我们使用路由特性、EndPoint Route两种方式
以上两种方式的共同点是,所有的路由信息都必须在应用程序启动时加载。
但是,如果你希望能够动态定义路由, 并在应用程序运行时添加/删除它们,该怎么办?
下面我给大家列举几个动态定义路由的使用场景。
- 多语言路由,以及使用新语言时,针对那些新语言路由的修改。
- 在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.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
,然后我们需要在模拟用的数据库类中,找到其对应的翻译。正如我们之前提到的,你通常会希望从数据库中查找对应的内容,因为使用这种方式,我们可以在应用程序生命周期的任何时刻,动态的影响路由。