Map
經過對 WebApplication
的初步剖析,我們已經大致對Web應用的骨架有了一定了解,現在我們來看一下Hello World案例中僅剩的一條程式碼:
app.MapGet("/", () => "Hello World!"); // 3 新增路由處理
老規矩,看簽名:
public static RouteHandlerBuilder MapGet(this IEndpointRouteBuilder endpoints,
[StringSyntax("Route")] string pattern,Delegate handler){
return endpoints.MapMethods(pattern, (IEnumerable<string>) EndpointRouteBuilderExtensions.GetVerb, handler);
}
我們已經解釋過 IEndpointRouteBuilder
的定義了,即為程式定義路由構建的約定。這次看到的是他的擴充方法 Map
,該方法是諸如 MapGet
、MapPost
、MapPut
、MapDelete
、MapPatch
、MapMethods
的底層方法:
他的實現就是為了給IEndpointRouteBuilder
的 DataSources
新增一個 RouteEndpointDataSource
private static RouteHandlerBuilder Map(this IEndpointRouteBuilder endpoints, RoutePattern pattern, Delegate handler, IEnumerable<string> httpMethods, bool isFallback)
{
return endpoints.GetOrAddRouteEndpointDataSource().AddRouteHandler(pattern, handler, httpMethods, isFallback, RequestDelegateFactory.InferMetadata, RequestDelegateFactory.Create);
}
RouteEndpointDataSource
繼承自 EndpointDataSource
,提供一組RouteEndpoint
public override IReadOnlyList<RouteEndpoint> Endpoints
{
get
{
RouteEndpoint[] array = new RouteEndpoint[_routeEntries.Count];
for (int i = 0; i < _routeEntries.Count; i++)
{
array[i] = (RouteEndpoint)CreateRouteEndpointBuilder(_routeEntries[i]).Build();
}
return array;
}
}
Endpoint
RouteEndpoint
繼承自 Endpoint
。多了一個 RoutePattern
屬性,用於路由匹配,支援模式路由。還有一個 Order
屬性,用於處理多匹配源下的優先順序問題。
public sealed class RouteEndpoint : Endpoint
{
public int Order { get; }
public RoutePattern RoutePattern { get; }
}
Endpoint
是一個應用程式中路的一個邏輯終結點。
public string? DisplayName { get; } // 終結點名稱
public EndpointMetadataCollection Metadata { get; } // 後設資料
public RequestDelegate? RequestDelegate { get; } // 請求委託
其中 Metadata
就是一個object集合,包含描述、標籤、許可權等資料,這一般是框架用到的,後面會再次見到它。重點是 RequestDelegate
:HttpContext
首次登場,相信有過Web開發經驗的同學熟悉的不能再熟悉。該委託其實就是一個Func<HttpContext, Task>
,用於處理HTTP請求,由於TAP的普及,所以返回的Task
。
public delegate Task RequestDelegate(HttpContext context);
Endpoint
一般由 EndpointBuilder
構建,他能夠額外組裝Filter
public IList<Func<EndpointFilterFactoryContext, EndpointFilterDelegate, EndpointFilterDelegate>> FilterFactories
EndpointDataSource
經過對 MapGet
的剖析我們最終發現,所有的終結點都被掛載在了 EndpointDataSource
public abstract IReadOnlyList<Endpoint> Endpoints { get; }
public virtual IReadOnlyList<Endpoint> GetGroupedEndpoints(RouteGroupContext context)
除了被大家熟悉的 Endpoints
還提供了一個方法 GetGroupedEndpoints
:在給定指定字首和約定的情況下,獲取此EndpointDataSource
的所有 Endpoint
的。
public virtual IReadOnlyList<Endpoint> GetGroupedEndpoints(RouteGroupContext context)
{
IReadOnlyList<Endpoint> endpoints = Endpoints;
RouteEndpoint[] array = new RouteEndpoint[endpoints.Count];
for (int i = 0; i < endpoints.Count; i++)
{
Endpoint endpoint = endpoints[i];
if (!(endpoint is RouteEndpoint routeEndpoint))
{
throw new NotSupportedException(Resources.FormatMapGroup_CustomEndpointUnsupported(endpoint.GetType()));
}
RoutePattern routePattern = RoutePatternFactory.Combine(context.Prefix, routeEndpoint.RoutePattern);
RouteEndpointBuilder routeEndpointBuilder = new RouteEndpointBuilder(routeEndpoint.RequestDelegate, routePattern, routeEndpoint.Order)
{
DisplayName = routeEndpoint.DisplayName,
ApplicationServices = context.ApplicationServices
};
foreach (Action<EndpointBuilder> convention in context.Conventions)
{
convention(routeEndpointBuilder);
}
foreach (object metadatum in routeEndpoint.Metadata)
{
routeEndpointBuilder.Metadata.Add(metadatum);
}
foreach (Action<EndpointBuilder> finallyConvention in context.FinallyConventions)
{
finallyConvention(routeEndpointBuilder);
}
array[i] = (RouteEndpoint)routeEndpointBuilder.Build();
}
return array;
}
透過剖析 RouteGroupContext
,很容易發覺,Prefix
是一個路由字首,Conventions
和 FinallyConventions
是兩個約定hook。它專為 RouteEndpoint
獨有,透過 GetGroupedEndpoints
方法,組的字首和約定,會作用到每一個路由終結點。
public sealed class RouteGroupContext
{
public required RoutePattern Prefix { get; init; }
public IReadOnlyList<Action<EndpointBuilder>> Conventions { get; init; } = Array.Empty<Action<EndpointBuilder>>();
public IReadOnlyList<Action<EndpointBuilder>> FinallyConventions { get; init; } = Array.Empty<Action<EndpointBuilder>>();
}