原文: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上找到。

ASP.NET Core 3.0中使用动态控制器路由的更多相关文章

  1. ASP.NET Core 1.0 中的依赖项管理

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  2. 在ASP.NET Core 1.0中如何发送邮件

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:目前.NET Core 1.0中并没有提供SMTP相关的类库,那么要如何从ASP.NE ...

  3. ASP.NET Core 1.0 中使用 Swagger 生成文档

    github:https://github.com/domaindrivendev/Ahoy 之前文章有介绍在ASP.NET WebAPI 中使用Swagger生成文档,ASP.NET Core 1. ...

  4. 用ASP.NET Core 1.0中实现邮件发送功能

    准备将一些项目迁移到 asp.net core 先从封装类库入手,在遇到邮件发送类时发现在 asp.net core 1.0中并示提供SMTP相关类库,于是网上一搜发现了MailKit 好东西一定要试 ...

  5. 在ASP.NET Core 2.0中使用CookieAuthentication

    在ASP.NET Core中关于Security有两个容易混淆的概念一个是Authentication(认证),一个是Authorization(授权).而前者是确定用户是谁的过程,后者是围绕着他们允 ...

  6. 如何在ASP.NET Core 2.0中使用Razor页面

    如何在ASP.NET Core 2.0中使用Razor页面  DotNetCore2017-11-22 14:49 问题 如何在ASP.NET Core 2.0中使用Razor页面 解 创建一个空的项 ...

  7. asp.net core 3.0 中使用 swagger

    asp.net core 3.0 中使用 swagger Intro 上次更新了 asp.net core 3.0 简单的记录了一下 swagger 的使用,那个项目的 api 比较简单,都是匿名接口 ...

  8. 探索 ASP.Net Core 3.0系列三:ASP.Net Core 3.0中的Service provider validation

    前言:在本文中,我将描述ASP.NET Core 3.0中新的“validate on build”功能. 这可以用来检测您的DI service provider是否配置错误. 具体而言,该功能可检 ...

  9. 在Asp.Net Core 3.0中如何使用 Newtonsoft.Json 库序列化数据

    在.Net Core 3.0中 内置了一套Json序列化/反序列化方案,默认可以不再依赖,不再支持   Newtonsoft.Json. 但是.NET Core 3.0 System.Text.Jso ...

随机推荐

  1. flink入门实战总结

    随着大数据技术在各行各业的广泛应用,要求能对海量数据进行实时处理的需求越来越多,同时数据处理的业务逻辑也越来越复杂,传统的批处理方式和早期的流式处理框架也越来越难以在延迟性.吞吐量.容错能力以及使用便 ...

  2. KNN算法实现手写体区分

    KNN算法在python里面可以使用pip install指令安装,我在实现之前查看过安装的KNN算法,十分全面,包括了对于手写体数据集的处理.我这里只是实现了基础的识别方法,能力有限,没有数据处理方 ...

  3. 并发栅栏CyclicBarrier---简单问2

    并发栅栏CyclicBarrier---简单问 背景:前几天在网上看到关于Java并发包java.concurrent中一个连环炮的面试题,整理下以备不时之需. CyclicBarrier简介: 栅栏 ...

  4. 网站安装SSL证书成为影响SEO排名的重要因素之一

    百度谷歌先后发声明倡导站长们使用https链接,同样的网站,https站点要比http站点拥有更好的排名权重.https已经是网站SEO必须要考虑的环节之一了,而https的必要条件就是安装SSL证书 ...

  5. 【JDK】JDK源码分析-AbstractQueuedSynchronizer(3)

    概述 前文「JDK源码分析-AbstractQueuedSynchronizer(2)」分析了 AQS 在独占模式下获取资源的流程,本文分析共享模式下的相关操作. 其实二者的操作大部分是类似的,理解了 ...

  6. 使用Arthas 获取Spring ApplicationContext还原问题现场

    ## 背景 最近来了个实习僧小弟,安排他实现对目标网站 连通性检测的小功能,简单讲就是将下边的shell 脚本换成Java 代码来实现 ``` 1#!/bin/bash 2URL="http ...

  7. Spring Cloud下基于OAUTH2+ZUUL认证授权的实现

    Spring Cloud下基于OAUTH2认证授权的实现 在Spring Cloud需要使用OAUTH2来实现多个微服务的统一认证授权,通过向OAUTH服务发送某个类型的grant type进行集中认 ...

  8. DedeCMS 5.7 sp1远程文件包含漏洞(CVE-2015-4553)

    DedeCMS 5.7 sp1远程文件包含漏洞(CVE-2015-4553) 一.漏洞描述 该漏洞在/install/index.php(index.php.bak)文件中,漏洞起因是$$符号使用不当 ...

  9. K8S搭建-1 Master 2 Workers(dashboard+ingress)

    本文讲述k8s最新版的搭建(v1.15.2) 分如下几个topic步骤: 各个节点的基本配置 master节点的构建 worker节点的构建 安装dashboard 安装ingress 常见命令 do ...

  10. loging日志的使用

    2.日志: 记住怎么使用就好了 自己定义日志开始 import logging logger = logging.getLogger() # 创建一个logger fh = logging.FileH ...