Endpoint路由
在ASP.NET Core 2.2中,新增了一种路由,叫做Endpoint
(终结点)路由。本文将以往的路由系统称为传统路由
。
本文通过源码的方式介绍传统路由和Endpoint
路由部分核心功能和实现方法,具体功能上的差异见官方文档。
在升级到ASP.NET Core 2.2后,会自动启用Endpoint
路由。如果要恢复以往的实现逻辑,需要加入以下代码:
services.AddMvc(options => options.EnableEndpointRouting = false)
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
本文分析的源代码基于ASP.NET Core 2.2.3版本的源代码。
Endpoint作用
Endpoint
路由与传统路由的区别在于,传统路由Url
与Action
对应关系的处理是在UseMvc
中做的。我们无法根据Url
获取对应的Action
然后进行处理。
Endpoint
就是将Url
与Action
的映射关系从Mvc
中拆离,作为独立使用的中间件。
由此带来的好处是我们可以在其他的中间件中使用Controller
和Action
上的一些信息,例如Attruibute
。
框架也提供了LinkGenerator
类来直接根据Endpoint
生成链接,不再需要HttpContext
的信息。
另外也提升了一些RPS(Requests per Second)。
不过目前Endpoint
依然是在UseMvc
中调用,更多开放的使用方式会在ASP.NET Core 3.0中实现。
启用Endpoint路由
源代码见Github。也可以获取源代码到本地看。
在MvcApplicationBuilderExtensions.cs
文件72行的UseMvc
方法中我们可以看到以下代码:
var options = app.ApplicationServices.GetRequiredService<IOptions<MvcOptions>>();
if (options.Value.EnableEndpointRouting)
{
...
}
else
{
...
}
if
之中是Endpoint
路由的逻辑,else
是传统路由的逻辑。
而MvcOptions
的构造方法如下所示,EnableEndpointRouting
是通过CompatibilitySwitch
来控制默认值的,这就是CompatibilityVersion.Version_2_2
启用Endpoint
路由的原因。
public MvcOptions()
{
// ...
_enableEndpointRouting = new CompatibilitySwitch<bool>(nameof(EnableEndpointRouting));
// ...
}
Endpoint路由实现原理
在MvcApplicationBuilderExtensions.cs
文件的92-123行的代码是将所有的Controller
中的Action
转换成Endpoint
。
在129行的UseEndpointRouting
中,添加了一个EndpointRoutingMiddleware
的中间件,这个中间件就是从所有的Endpoint
中找到当前路由对应的Endpoint
,然后放到Feature
集合中。
在132行的UseEndpoint
中,添加了一个EndpointMiddleware
中间件,这个中间件是将EndpointRoutingMiddleware
中找到的Endpoint
取出,根据其中的MetaData
信息,找到对应的Controller
和Action
,并调用。
在UseMvc
方法里,UseEndpointRouting
和UseEndpoint
是连续的两个中间件,而UseEndpoint
是请求的结束,这意味着我们自定义的中间件无法取得Endpoint
信息。
但是通过手动调用UseEndpoint
,我们还是可以拿到Endpoint
路由信息的。
使用示例
下面展示一个使用示例。
定义一个LogAttribute
类,并包含一个Message
属性,在Action
上声明使用。
定义一个EndpointTestMiddleware
中间件,输出LogAttribute
的Message
属性。
手动调用UseEndpointRouting
,然后调用我们定义的EndpointTestMiddleware
中间件。
// Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseEndpointRouting();
app.UseMiddleware<EndpointTestMiddleware>();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
// EndpointTestMiddleware.cs
public class EndpointTestMiddleware
{
private RequestDelegate _next;
public EndpointTestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var endpoint = httpContext.Features.Get<IEndpointFeature>()?.Endpoint;
if (endpoint == null)
{
await _next(httpContext);
return;
}
var attruibutes = endpoint.Metadata.OfType<LogAttribute>();
foreach (var attribute in attruibutes)
{
Debug.WriteLine("------------------------------------------------------------------------");
Debug.WriteLine(attribute.Message);
Debug.WriteLine("------------------------------------------------------------------------");
}
await _next(httpContext);
}
}
// LogAttribute.cs
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
public sealed class LogAttribute : Attribute
{
public LogAttribute(string message)
{
Message = message;
}
public string Message { get; set; }
}
// HomeController.cs
public class HomeController : Controller
{
[Log("Index")]
public IActionResult Index()
{
return View();
}
[Log("Privacy")]
public IActionResult Privacy()
{
return View();
}
}
这样的话,我们可以在我们自己的中间件中拿到Endpoint
信息,然后找到Controller
上的LogAttribute
,然后输出Message
。
总结
Endpoint
是ASP.NET Core 2.2中一种新的路由机制,它解决了传统路由难以扩展的问题,解决了传统路由与MVC过于耦合的问题,并提升了一定的RPS。
本文介绍了Endpoint路由,简单分析了Endpoint的实现原理,并给出了一个使用的示例。
参考链接:
- [https://devblogs.microsoft.com/aspnet/asp-net-core-2-2-0-preview1-endpoint-routing/]
- [https://www.stevejgordon.co.uk/asp-net-core-first-look-at-global-routing-dispatcher]
- [https://rolandguijt.com/endpoint-routing-in-asp-net-core-2-2-explained/]