重新整理 .net core 实践篇——— UseEndpoints中间件[四十八]

重新整理 .net core 实践篇——— UseEndpoints中间件[四十八]

前言

前文已经提及到了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 ,里面有一丢丢小复杂,有兴趣可以自己去看下,就是扫描那一套了。

下一节,介绍一下文件上传

免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部