前言

前文已经提及到了endponint 是怎么匹配到的,也就是说在UseRouting 之后的中间件都能获取到endpoint了,如果能够匹配到的话,那么UseEndpoints又做了什么呢?它是如何执行我们的action的呢。

正文

直接按顺序看代码好了:

  1. public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure)
  2. {
  3. if (builder == null)
  4. {
  5. throw new ArgumentNullException(nameof(builder));
  6. }
  7. if (configure == null)
  8. {
  9. throw new ArgumentNullException(nameof(configure));
  10. }
  11. VerifyRoutingServicesAreRegistered(builder);
  12. VerifyEndpointRoutingMiddlewareIsRegistered(builder, out var endpointRouteBuilder);
  13. configure(endpointRouteBuilder);
  14. // Yes, this mutates an IOptions. We're registering data sources in a global collection which
  15. // can be used for discovery of endpoints or URL generation.
  16. //
  17. // Each middleware gets its own collection of data sources, and all of those data sources also
  18. // get added to a global collection.
  19. var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
  20. foreach (var dataSource in endpointRouteBuilder.DataSources)
  21. {
  22. routeOptions.Value.EndpointDataSources.Add(dataSource);
  23. }
  24. return builder.UseMiddleware<EndpointMiddleware>();
  25. }

这里面首先做了两个验证,一个是VerifyRoutingServicesAreRegistered 验证路由服务是否注册了,第二个VerifyEndpointRoutingMiddlewareIsRegistered是验证烟油中间件是否注入了。

验证手法也挺简单的。

VerifyRoutingServicesAreRegistered 直接验证是否serviceCollection 是否可以获取该服务。

  1. private static void VerifyRoutingServicesAreRegistered(IApplicationBuilder app)
  2. {
  3. // Verify if AddRouting was done before calling UseEndpointRouting/UseEndpoint
  4. // We use the RoutingMarkerService to make sure if all the services were added.
  5. if (app.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
  6. {
  7. throw new InvalidOperationException(Resources.FormatUnableToFindServices(
  8. nameof(IServiceCollection),
  9. nameof(RoutingServiceCollectionExtensions.AddRouting),
  10. "ConfigureServices(...)"));
  11. }
  12. }

VerifyEndpointRoutingMiddlewareIsRegistered 这个验证Properties 是否有EndpointRouteBuilder

  1. private static void VerifyEndpointRoutingMiddlewareIsRegistered(IApplicationBuilder app, out DefaultEndpointRouteBuilder endpointRouteBuilder)
  2. {
  3. if (!app.Properties.TryGetValue(EndpointRouteBuilder, out var obj))
  4. {
  5. var message =
  6. $"{nameof(EndpointRoutingMiddleware)} matches endpoints setup by {nameof(EndpointMiddleware)} and so must be added to the request " +
  7. $"execution pipeline before {nameof(EndpointMiddleware)}. " +
  8. $"Please add {nameof(EndpointRoutingMiddleware)} by calling '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' inside the call " +
  9. $"to 'Configure(...)' in the application startup code.";
  10. throw new InvalidOperationException(message);
  11. }
  12. // If someone messes with this, just let it crash.
  13. endpointRouteBuilder = (DefaultEndpointRouteBuilder)obj!;
  14. // This check handles the case where Map or something else that forks the pipeline is called between the two
  15. // routing middleware.
  16. if (!object.ReferenceEquals(app, endpointRouteBuilder.ApplicationBuilder))
  17. {
  18. var message =
  19. $"The {nameof(EndpointRoutingMiddleware)} and {nameof(EndpointMiddleware)} must be added to the same {nameof(IApplicationBuilder)} instance. " +
  20. $"To use Endpoint Routing with 'Map(...)', make sure to call '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' before " +
  21. $"'{nameof(IApplicationBuilder)}.{nameof(UseEndpoints)}' for each branch of the middleware pipeline.";
  22. throw new InvalidOperationException(message);
  23. }
  24. }

然后判断是否endpointRouteBuilder.ApplicationBuilder 和 app 是否相等,这里使用的是object.ReferenceEquals,其实是判断其中的引用是否相等,指针概念。

上面的验证只是做了一个简单的验证了,但是从中可以看到,肯定是该中间件要使用endpointRouteBuilder的了。

中间件就是大一点的方法,也逃不出验证参数、执行核心代码、返回结果的三步走。

  1. var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
  2. foreach (var dataSource in endpointRouteBuilder.DataSources)
  3. {
  4. routeOptions.Value.EndpointDataSources.Add(dataSource);
  5. }

这里就是填充RouteOptions的EndpointDataSources了。

那么具体看EndpointMiddleware吧。

  1. public EndpointMiddleware(
  2. ILogger<EndpointMiddleware> logger,
  3. RequestDelegate next,
  4. IOptions<RouteOptions> routeOptions)
  5. {
  6. _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  7. _next = next ?? throw new ArgumentNullException(nameof(next));
  8. _routeOptions = routeOptions?.Value ?? throw new ArgumentNullException(nameof(routeOptions));
  9. }
  10. public Task Invoke(HttpContext httpContext)
  11. {
  12. var endpoint = httpContext.GetEndpoint();
  13. if (endpoint?.RequestDelegate != null)
  14. {
  15. if (!_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
  16. {
  17. if (endpoint.Metadata.GetMetadata<IAuthorizeData>() != null &&
  18. !httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
  19. {
  20. ThrowMissingAuthMiddlewareException(endpoint);
  21. }
  22. if (endpoint.Metadata.GetMetadata<ICorsMetadata>() != null &&
  23. !httpContext.Items.ContainsKey(CorsMiddlewareInvokedKey))
  24. {
  25. ThrowMissingCorsMiddlewareException(endpoint);
  26. }
  27. }
  28. Log.ExecutingEndpoint(_logger, endpoint);
  29. try
  30. {
  31. var requestTask = endpoint.RequestDelegate(httpContext);
  32. if (!requestTask.IsCompletedSuccessfully)
  33. {
  34. return AwaitRequestTask(endpoint, requestTask, _logger);
  35. }
  36. }
  37. catch (Exception exception)
  38. {
  39. Log.ExecutedEndpoint(_logger, endpoint);
  40. return Task.FromException(exception);
  41. }
  42. Log.ExecutedEndpoint(_logger, endpoint);
  43. return Task.CompletedTask;
  44. }
  45. return _next(httpContext);
  46. static async Task AwaitRequestTask(Endpoint endpoint, Task requestTask, ILogger logger)
  47. {
  48. try
  49. {
  50. await requestTask;
  51. }
  52. finally
  53. {
  54. Log.ExecutedEndpoint(logger, endpoint);
  55. }
  56. }
  57. }

EndpointMiddleware 初始化的时候注入了routeOptions。

然后直接看invoke了。

  1. if (!_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
  2. {
  3. if (endpoint.Metadata.GetMetadata<IAuthorizeData>() != null &&
  4. !httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
  5. {
  6. ThrowMissingAuthMiddlewareException(endpoint);
  7. }
  8. if (endpoint.Metadata.GetMetadata<ICorsMetadata>() != null &&
  9. !httpContext.Items.ContainsKey(CorsMiddlewareInvokedKey))
  10. {
  11. ThrowMissingCorsMiddlewareException(endpoint);
  12. }
  13. }

这里面判断了如果有IAuthorizeData 元数据,如果没有权限中间件的统一抛出异常。

然后如果有ICorsMetadata元数据的,这个是某个action指定了跨域规则的,统一抛出异常。

  1. try
  2. {
  3. var requestTask = endpoint.RequestDelegate(httpContext);
  4. if (!requestTask.IsCompletedSuccessfully)
  5. {
  6. return AwaitRequestTask(endpoint, requestTask, _logger);
  7. }
  8. }
  9. catch (Exception exception)
  10. {
  11. Log.ExecutedEndpoint(_logger, endpoint);
  12. return Task.FromException(exception);
  13. }
  14. Log.ExecutedEndpoint(_logger, endpoint);
  15. return Task.CompletedTask;

这一段就是执行我们的action了,RequestDelegate 这一个就是在执行我们的action,同样注入了httpContext。

里面的逻辑非常简单哈。

那么这里就有人问了,前面你不是说要用到IEndpointRouteBuilder,怎么没有用到呢?

看这个,前面我们一直谈及到IEndpointRouteBuilder 管理着datasource,我们从来就没有看到datasource 是怎么生成的。

在UseRouting中,我们看到:

这里new 了一个DefaultEndpointRouteBuilder,DefaultEndpointRouteBuilder 继承IEndpointRouteBuilder,但是我们看到这里没有datasource注入。

那么我们的action 是如何转换为endponit的呢?可以参考endpoints.MapControllers();。

这个地方值得注意的是:

这些地方不是在执行中间件哈,而是在组合中间件,中间件是在这里组合完毕的。

那么简单看一下MapControllers 是如何生成datasource的吧,当然有很多生成datasource的,这里只介绍一下这个哈。

  1. ControllerActionEndpointConventionBuilder MapControllers(
  2. this IEndpointRouteBuilder endpoints)
  3. {
  4. if (endpoints == null)
  5. throw new ArgumentNullException(nameof (endpoints));
  6. ControllerEndpointRouteBuilderExtensions.EnsureControllerServices(endpoints);
  7. return ControllerEndpointRouteBuilderExtensions.GetOrCreateDataSource(endpoints).DefaultBuilder;
  8. }

看下EnsureControllerServices:

  1. private static void EnsureControllerServices(IEndpointRouteBuilder endpoints)
  2. {
  3. if (endpoints.ServiceProvider.GetService<MvcMarkerService>() == null)
  4. throw new InvalidOperationException(Microsoft.AspNetCore.Mvc.Core.Resources.FormatUnableToFindServices((object) "IServiceCollection", (object) "AddControllers", (object) "ConfigureServices(...)"));
  5. }

这里检查我们是否注入mvc服务。这里我们是值得借鉴的地方了,每次在服务注入的时候专门有一个服务注入的标志,这样就可以检测出服务是否注入了,这样我们就可以更加准确的抛出异常,而不是通过依赖注入服务来抛出。

  1. private static ControllerActionEndpointDataSource GetOrCreateDataSource(
  2. IEndpointRouteBuilder endpoints)
  3. {
  4. ControllerActionEndpointDataSource endpointDataSource = endpoints.DataSources.OfType<ControllerActionEndpointDataSource>().FirstOrDefault<ControllerActionEndpointDataSource>();
  5. if (endpointDataSource == null)
  6. {
  7. OrderedEndpointsSequenceProviderCache requiredService = endpoints.ServiceProvider.GetRequiredService<OrderedEndpointsSequenceProviderCache>();
  8. endpointDataSource = endpoints.ServiceProvider.GetRequiredService<ControllerActionEndpointDataSourceFactory>().Create(requiredService.GetOrCreateOrderedEndpointsSequenceProvider(endpoints));
  9. endpoints.DataSources.Add((EndpointDataSource) endpointDataSource);
  10. }
  11. return endpointDataSource;
  12. }

这里的匹配方式暂时就不看了,总之就是生成endpointDataSource ,里面有一丢丢小复杂,有兴趣可以自己去看下,就是扫描那一套了。

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

重新整理 .net core 实践篇——— UseEndpoints中间件[四十八]的更多相关文章

  1. 重新整理 .net core 实践篇—————异常中间件[二十]

    前言 简单介绍一下异常中间件的使用. 正文 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } 这样写入中间件哈,那么在env环 ...

  2. 重新整理 .net core 实践篇————缓存相关[四十二]

    前言 简单整理一下缓存. 正文 缓存是什么? 缓存是计算结果的"临时"存储和重复使用 缓存本质是用空间换取时间 缓存的场景: 计算结果,如:反射对象缓存 请求结果,如:DNS 缓存 ...

  3. 重新整理 .net core 实践篇—————Mediator实践[二十八]

    前言 简单整理一下Mediator. 正文 Mediator 名字是中介者的意思. 那么它和中介者模式有什么关系呢?前面整理设计模式的时候,并没有去介绍具体的中介者模式的代码实现. 如下: https ...

  4. 重新整理 .net core 实践篇————cookie 安全问题[三十八]

    前言 简单整理一下cookie的跨站攻击,这个其实现在不常见,因为很多公司都明确声明不再用cookie存储重要信息,不过对于老站点还是有的. 正文 攻击原理: 这种攻击要达到3个条件: 用户访问了我们 ...

  5. 重新整理 .net core 实践篇——— 权限中间件源码阅读[四十六]

    前言 前面介绍了认证中间件,下面看一下授权中间件. 正文 app.UseAuthorization(); 授权中间件是这个,前面我们提及到认证中间件并不会让整个中间件停止. 认证中间件就两个作用,我们 ...

  6. 重新整理 .net core 实践篇—————静态中间件[二十一]

    前言 简单整理一下静态中间件. 正文 我们使用静态文件调用: app.UseStaticFiles(); 那么这个默认会将我们根目录下的wwwroot作为静态目录. 这个就比较值得注意的,可能刚开始学 ...

  7. 重新整理 .net core 实践篇————配置中心[四十三]

    前言 简单整理一下配置中心. 正文 什么时候需要配置中心? 多项目组并行协作 运维开发分工职责明确 对风险控制有更高诉求 对线上配置热更新有诉求 其实上面都是套话,如果觉得项目不方便的时候就需要用配置 ...

  8. 重新整理 .net core 实践篇—————配置系统之间谍[八](文件监控)

    前言 前文提及到了当我们的配置文件修改了,那么从 configurationRoot 在此读取会读取到新的数据,本文进行扩展,并从源码方面简单介绍一下,下面内容和前面几节息息相关. 正文 先看一下,如 ...

  9. 重新整理 .net core 实践篇—————领域事件[二十九]

    前文 前面整理了仓储层,工作单元模式,同时简单介绍了一下mediator. 那么就mediator在看下领域事件启到了什么作用吧. 正文 这里先注册一下MediatR服务: // 注册中间者:Medi ...

随机推荐

  1. c++学习笔记(六)

    windows批处理 什么是批处理? 批处理(Batch),也称为批处理脚本. 顾名思义,批处理就是对某对象进行批量的处理.批处理文件的扩展名为bat. 批处理文件(batch file)包含一系列 ...

  2. Linux下搭建FFmpeg开发调试环境

    背景 如果你是一个FFmpeg的使用者,那么绝大部分情况下只需要在你的程序中引用FFmpeg的libav*相关的头文件,然后在编译阶段链接相关的库即可.但是如果你想调试FFmpeg内部相关的逻辑,或者 ...

  3. React-Router学习(基础路由与嵌套路由)

    示例:基本路由 在这个例子中,我们有3个'Page'组件处理<Router>. 注意:而不是<a href="/">我们使用<Link to=&quo ...

  4. PowerDotNet平台化软件架构设计与实现系列(04):服务治理平台

    系统和系统之间,少不了数据的互联互通.随着微服务的流行,一个系统内的不同应用进行互联互通也是常态. PowerDotNet的服务治理平台发源于早期的个人项目Power.Apix.这个项目借鉴了工作过的 ...

  5. HCNP Routing&Switching之组播技术-组播地址

    前文我们聊到了组播技术背景,单播.广播在点到多点应用中的问题,以及组播对比单播.广播在点到多点的网络环境中的优势.劣势,相关回顾请参考https://www.cnblogs.com/qiuhom-18 ...

  6. Codeforces 1406E - Deleting Numbers(根分+数论)

    Codeforces 题面传送门 & 洛谷题面传送门 一道个人感觉挺有意思的交互题,本人一开始想了个奇奇怪怪的做法,还以为卡不进去,结果发现竟然过了,而且还是正解( 首先看到这类题目可以考虑每 ...

  7. [Linux] 非root安装GCC9.1.0

    说明 一般Linux系统自带或公共的GCC版本都很低,如目前我们的服务器版本的GCC还停留在gcc-4.9.3,而官网已到达9.2版本(下载http://ftp.gnu.org/gnu/gcc/) , ...

  8. Vue.js知识点总结

    1. Vue.js简介 1.1 Vue.js简介 1.2 创建一个vue实例 2. Vue.js基础 2.1 模板语法 2.2 环境搭建 2.3 生命周期钩子

  9. 容器中的容器——利用Dind实现开箱即用的K3s

    我在学习 Rancher 和 Minikube 的时候,发现它们都可以在自己的容器环境中提供一个 K3s 或 K8s 集群.尤其是 Minikube ,用户可以在它的容器环境中执行 docker ps ...

  10. Go知识盲区--闭包

    1. 引言 关于闭包的说明,曾在很多篇幅中都有过一些说明,包括Go基础--函数2, go 函数进阶,异常与错误 都有所提到, 但是会发现,好像原理(理论)都懂,但是就是不知道如何使用,或者在看到一些源 ...