前言
前文已經提及到了endponint 是怎麼匹配到的,也就是說在UseRouting 之後的中介軟體都能獲取到endpoint了,如果能夠匹配到的話,那麼UseEndpoints又做了什麼呢?它是如何執行我們的action的呢。
正文
直接按順序看程式碼好了:
public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
VerifyRoutingServicesAreRegistered(builder);
VerifyEndpointRoutingMiddlewareIsRegistered(builder, out var endpointRouteBuilder);
configure(endpointRouteBuilder);
// Yes, this mutates an IOptions. We're registering data sources in a global collection which
// can be used for discovery of endpoints or URL generation.
//
// Each middleware gets its own collection of data sources, and all of those data sources also
// get added to a global collection.
var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
foreach (var dataSource in endpointRouteBuilder.DataSources)
{
routeOptions.Value.EndpointDataSources.Add(dataSource);
}
return builder.UseMiddleware<EndpointMiddleware>();
}
這裡面首先做了兩個驗證,一個是VerifyRoutingServicesAreRegistered 驗證路由服務是否註冊了,第二個VerifyEndpointRoutingMiddlewareIsRegistered是驗證煙油中介軟體是否注入了。
驗證手法也挺簡單的。
VerifyRoutingServicesAreRegistered 直接驗證是否serviceCollection 是否可以獲取該服務。
private static void VerifyRoutingServicesAreRegistered(IApplicationBuilder app)
{
// Verify if AddRouting was done before calling UseEndpointRouting/UseEndpoint
// We use the RoutingMarkerService to make sure if all the services were added.
if (app.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
{
throw new InvalidOperationException(Resources.FormatUnableToFindServices(
nameof(IServiceCollection),
nameof(RoutingServiceCollectionExtensions.AddRouting),
"ConfigureServices(...)"));
}
}
VerifyEndpointRoutingMiddlewareIsRegistered 這個驗證Properties 是否有EndpointRouteBuilder
private static void VerifyEndpointRoutingMiddlewareIsRegistered(IApplicationBuilder app, out DefaultEndpointRouteBuilder endpointRouteBuilder)
{
if (!app.Properties.TryGetValue(EndpointRouteBuilder, out var obj))
{
var message =
$"{nameof(EndpointRoutingMiddleware)} matches endpoints setup by {nameof(EndpointMiddleware)} and so must be added to the request " +
$"execution pipeline before {nameof(EndpointMiddleware)}. " +
$"Please add {nameof(EndpointRoutingMiddleware)} by calling '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' inside the call " +
$"to 'Configure(...)' in the application startup code.";
throw new InvalidOperationException(message);
}
// If someone messes with this, just let it crash.
endpointRouteBuilder = (DefaultEndpointRouteBuilder)obj!;
// This check handles the case where Map or something else that forks the pipeline is called between the two
// routing middleware.
if (!object.ReferenceEquals(app, endpointRouteBuilder.ApplicationBuilder))
{
var message =
$"The {nameof(EndpointRoutingMiddleware)} and {nameof(EndpointMiddleware)} must be added to the same {nameof(IApplicationBuilder)} instance. " +
$"To use Endpoint Routing with 'Map(...)', make sure to call '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' before " +
$"'{nameof(IApplicationBuilder)}.{nameof(UseEndpoints)}' for each branch of the middleware pipeline.";
throw new InvalidOperationException(message);
}
}
然後判斷是否endpointRouteBuilder.ApplicationBuilder 和 app 是否相等,這裡使用的是object.ReferenceEquals,其實是判斷其中的引用是否相等,指標概念。
上面的驗證只是做了一個簡單的驗證了,但是從中可以看到,肯定是該中介軟體要使用endpointRouteBuilder的了。
中介軟體就是大一點的方法,也逃不出驗證引數、執行核心程式碼、返回結果的三步走。
var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
foreach (var dataSource in endpointRouteBuilder.DataSources)
{
routeOptions.Value.EndpointDataSources.Add(dataSource);
}
這裡就是填充RouteOptions的EndpointDataSources了。
那麼具體看EndpointMiddleware吧。
public EndpointMiddleware(
ILogger<EndpointMiddleware> logger,
RequestDelegate next,
IOptions<RouteOptions> routeOptions)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_next = next ?? throw new ArgumentNullException(nameof(next));
_routeOptions = routeOptions?.Value ?? throw new ArgumentNullException(nameof(routeOptions));
}
public Task Invoke(HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
if (endpoint?.RequestDelegate != null)
{
if (!_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
{
if (endpoint.Metadata.GetMetadata<IAuthorizeData>() != null &&
!httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
{
ThrowMissingAuthMiddlewareException(endpoint);
}
if (endpoint.Metadata.GetMetadata<ICorsMetadata>() != null &&
!httpContext.Items.ContainsKey(CorsMiddlewareInvokedKey))
{
ThrowMissingCorsMiddlewareException(endpoint);
}
}
Log.ExecutingEndpoint(_logger, endpoint);
try
{
var requestTask = endpoint.RequestDelegate(httpContext);
if (!requestTask.IsCompletedSuccessfully)
{
return AwaitRequestTask(endpoint, requestTask, _logger);
}
}
catch (Exception exception)
{
Log.ExecutedEndpoint(_logger, endpoint);
return Task.FromException(exception);
}
Log.ExecutedEndpoint(_logger, endpoint);
return Task.CompletedTask;
}
return _next(httpContext);
static async Task AwaitRequestTask(Endpoint endpoint, Task requestTask, ILogger logger)
{
try
{
await requestTask;
}
finally
{
Log.ExecutedEndpoint(logger, endpoint);
}
}
}
EndpointMiddleware 初始化的時候注入了routeOptions。
然後直接看invoke了。
if (!_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
{
if (endpoint.Metadata.GetMetadata<IAuthorizeData>() != null &&
!httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
{
ThrowMissingAuthMiddlewareException(endpoint);
}
if (endpoint.Metadata.GetMetadata<ICorsMetadata>() != null &&
!httpContext.Items.ContainsKey(CorsMiddlewareInvokedKey))
{
ThrowMissingCorsMiddlewareException(endpoint);
}
}
這裡面判斷了如果有IAuthorizeData 後設資料,如果沒有許可權中介軟體的統一丟擲異常。
然後如果有ICorsMetadata後設資料的,這個是某個action指定了跨域規則的,統一丟擲異常。
try
{
var requestTask = endpoint.RequestDelegate(httpContext);
if (!requestTask.IsCompletedSuccessfully)
{
return AwaitRequestTask(endpoint, requestTask, _logger);
}
}
catch (Exception exception)
{
Log.ExecutedEndpoint(_logger, endpoint);
return Task.FromException(exception);
}
Log.ExecutedEndpoint(_logger, endpoint);
return Task.CompletedTask;
這一段就是執行我們的action了,RequestDelegate 這一個就是在執行我們的action,同樣注入了httpContext。
裡面的邏輯非常簡單哈。
那麼這裡就有人問了,前面你不是說要用到IEndpointRouteBuilder,怎麼沒有用到呢?
看這個,前面我們一直談及到IEndpointRouteBuilder 管理著datasource,我們從來就沒有看到datasource 是怎麼生成的。
在UseRouting中,我們看到:
這裡new 了一個DefaultEndpointRouteBuilder,DefaultEndpointRouteBuilder 繼承IEndpointRouteBuilder,但是我們看到這裡沒有datasource注入。
那麼我們的action 是如何轉換為endponit的呢?可以參考endpoints.MapControllers();。
這個地方值得注意的是:
這些地方不是在執行中介軟體哈,而是在組合中介軟體,中介軟體是在這裡組合完畢的。
那麼簡單看一下MapControllers 是如何生成datasource的吧,當然有很多生成datasource的,這裡只介紹一下這個哈。
ControllerActionEndpointConventionBuilder MapControllers(
this IEndpointRouteBuilder endpoints)
{
if (endpoints == null)
throw new ArgumentNullException(nameof (endpoints));
ControllerEndpointRouteBuilderExtensions.EnsureControllerServices(endpoints);
return ControllerEndpointRouteBuilderExtensions.GetOrCreateDataSource(endpoints).DefaultBuilder;
}
看下EnsureControllerServices:
private static void EnsureControllerServices(IEndpointRouteBuilder endpoints)
{
if (endpoints.ServiceProvider.GetService<MvcMarkerService>() == null)
throw new InvalidOperationException(Microsoft.AspNetCore.Mvc.Core.Resources.FormatUnableToFindServices((object) "IServiceCollection", (object) "AddControllers", (object) "ConfigureServices(...)"));
}
這裡檢查我們是否注入mvc服務。這裡我們是值得借鑑的地方了,每次在服務注入的時候專門有一個服務注入的標誌,這樣就可以檢測出服務是否注入了,這樣我們就可以更加準確的丟擲異常,而不是通過依賴注入服務來丟擲。
private static ControllerActionEndpointDataSource GetOrCreateDataSource(
IEndpointRouteBuilder endpoints)
{
ControllerActionEndpointDataSource endpointDataSource = endpoints.DataSources.OfType<ControllerActionEndpointDataSource>().FirstOrDefault<ControllerActionEndpointDataSource>();
if (endpointDataSource == null)
{
OrderedEndpointsSequenceProviderCache requiredService = endpoints.ServiceProvider.GetRequiredService<OrderedEndpointsSequenceProviderCache>();
endpointDataSource = endpoints.ServiceProvider.GetRequiredService<ControllerActionEndpointDataSourceFactory>().Create(requiredService.GetOrCreateOrderedEndpointsSequenceProvider(endpoints));
endpoints.DataSources.Add((EndpointDataSource) endpointDataSource);
}
return endpointDataSource;
}
這裡的匹配方式暫時就不看了,總之就是生成endpointDataSource ,裡面有一丟丟小複雜,有興趣可以自己去看下,就是掃描那一套了。
結
下一節,介紹一下檔案上傳。