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取出,並調用RequestDelegate。RequestDelegate是預處理過的Url對應的Action方法。
在UseMvc方法里,UseEndpointRouting和UseEndpoint是連續的兩個中間件,而UseEndpoint是請求的結束,這意味着我們自定義的中間件無法取得Endpoint信息。
但是通過手動調用UseEndpointRouting,我們還是可以拿到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的實現原理,並給出了一個使用的示例。
參考鏈接:
