前言

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

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

重新整理 .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. SVD专题1 算子的奇异值分解——矩阵形式的推导

    目录 SVD专题1 算子的奇异值分解--矩阵形式的推导 前言 Preface 几点说明 预备知识 Prerequisite 1.1 极分解 Polar Decomposition 1.2 等距同构 U ...

  2. [bzoj1190]梦幻岛宝珠

    根据$2^b$分组,组内处理出g[i][j]表示当容量为$j\cdot 2^{i}$且只能选b=i时最大价值,再组间dp用f[i][j]表示当容量为$j\cdot 2^{i}+(w\&(2^{ ...

  3. 多线程03.实现Runnable接口

    package chapter2; public class TicketWindowRun implements Runnable { private static final int MAX =5 ...

  4. 架构师必备:巧用Canal实现异步、解耦的架构

    本文介绍如何应用Canal实现异步.解耦的架构,后续有空再写文章分析Canal原理和源代码. Canal简介 Canal是用来获取数据库变更的中间件. 伪装自己为MySQL从库,拉取主库binlog并 ...

  5. 主动扫描之Nmap

    主动扫描之Nmap 本文参考于李华峰等人的图书<Kali Linux2 网络渗透测试实践指南>2018年第1版 目录 主动扫描之Nmap 基本用法 主机发现 端口发现 扫描目标操作系统 扫 ...

  6. git新手配置(windows环境)

    windows环境,初步了解git是个什么东西,使用过svn相关软件最佳,否则可以先补一下git的相关概念和用处,相关教程:https://www.liaoxuefeng.com/wiki/89604 ...

  7. 快来使用Portainer让测试环境搭建飞起来吧

    Portainer是Docker的图形化管理工具,提供状态显示面板.应用模板快速部署.容器镜像网络数据卷的基本操作(包括上传下载镜像,创建容器等操作).事件日志显示.容器控制台操作.Swarm集群和服 ...

  8. SpringSecurity+Jwt遇到的bug

    最近在使用springsecurity整合Jwt的时候,遇到了一Bug,卡住了很久,记录一下. 编写Jwt工具类JwtUtil,编写Jwt认证的核心过滤器JwtAuthenticationFilter ...

  9. R语言与医学统计图形-【25】ggplot图形分面

    ggplot2绘图系统--图形分面 ggplot2的分面faceting,主要有三个函数: facet_grid facet_wrap facet_null (不分面) 1. facet_grid函数 ...

  10. Linux服务器I/O性能分析-3

    一.通过脚本分析IO的读/写数量.最大延迟.延迟的分布情况.块大小及数量 #!/bin/sh # # File Name : count_io.sh # Time : 2020-07-29-11:24 ...