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的實現原理,並給出了一個使用的示例。
參考鏈接: