重新整理 .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 ,里面有一丢丢小复杂,有兴趣可以自己去看下,就是扫描那一套了。
结
下一节,介绍一下文件上传。
重新整理 .net core 实践篇——— UseEndpoints中间件[四十八]的更多相关文章
- 重新整理 .net core 实践篇—————异常中间件[二十]
前言 简单介绍一下异常中间件的使用. 正文 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } 这样写入中间件哈,那么在env环 ...
- 重新整理 .net core 实践篇————缓存相关[四十二]
前言 简单整理一下缓存. 正文 缓存是什么? 缓存是计算结果的"临时"存储和重复使用 缓存本质是用空间换取时间 缓存的场景: 计算结果,如:反射对象缓存 请求结果,如:DNS 缓存 ...
- 重新整理 .net core 实践篇—————Mediator实践[二十八]
前言 简单整理一下Mediator. 正文 Mediator 名字是中介者的意思. 那么它和中介者模式有什么关系呢?前面整理设计模式的时候,并没有去介绍具体的中介者模式的代码实现. 如下: https ...
- 重新整理 .net core 实践篇————cookie 安全问题[三十八]
前言 简单整理一下cookie的跨站攻击,这个其实现在不常见,因为很多公司都明确声明不再用cookie存储重要信息,不过对于老站点还是有的. 正文 攻击原理: 这种攻击要达到3个条件: 用户访问了我们 ...
- 重新整理 .net core 实践篇——— 权限中间件源码阅读[四十六]
前言 前面介绍了认证中间件,下面看一下授权中间件. 正文 app.UseAuthorization(); 授权中间件是这个,前面我们提及到认证中间件并不会让整个中间件停止. 认证中间件就两个作用,我们 ...
- 重新整理 .net core 实践篇—————静态中间件[二十一]
前言 简单整理一下静态中间件. 正文 我们使用静态文件调用: app.UseStaticFiles(); 那么这个默认会将我们根目录下的wwwroot作为静态目录. 这个就比较值得注意的,可能刚开始学 ...
- 重新整理 .net core 实践篇————配置中心[四十三]
前言 简单整理一下配置中心. 正文 什么时候需要配置中心? 多项目组并行协作 运维开发分工职责明确 对风险控制有更高诉求 对线上配置热更新有诉求 其实上面都是套话,如果觉得项目不方便的时候就需要用配置 ...
- 重新整理 .net core 实践篇—————配置系统之间谍[八](文件监控)
前言 前文提及到了当我们的配置文件修改了,那么从 configurationRoot 在此读取会读取到新的数据,本文进行扩展,并从源码方面简单介绍一下,下面内容和前面几节息息相关. 正文 先看一下,如 ...
- 重新整理 .net core 实践篇—————领域事件[二十九]
前文 前面整理了仓储层,工作单元模式,同时简单介绍了一下mediator. 那么就mediator在看下领域事件启到了什么作用吧. 正文 这里先注册一下MediatR服务: // 注册中间者:Medi ...
随机推荐
- SVD专题1 算子的奇异值分解——矩阵形式的推导
目录 SVD专题1 算子的奇异值分解--矩阵形式的推导 前言 Preface 几点说明 预备知识 Prerequisite 1.1 极分解 Polar Decomposition 1.2 等距同构 U ...
- [bzoj1190]梦幻岛宝珠
根据$2^b$分组,组内处理出g[i][j]表示当容量为$j\cdot 2^{i}$且只能选b=i时最大价值,再组间dp用f[i][j]表示当容量为$j\cdot 2^{i}+(w\&(2^{ ...
- 多线程03.实现Runnable接口
package chapter2; public class TicketWindowRun implements Runnable { private static final int MAX =5 ...
- 架构师必备:巧用Canal实现异步、解耦的架构
本文介绍如何应用Canal实现异步.解耦的架构,后续有空再写文章分析Canal原理和源代码. Canal简介 Canal是用来获取数据库变更的中间件. 伪装自己为MySQL从库,拉取主库binlog并 ...
- 主动扫描之Nmap
主动扫描之Nmap 本文参考于李华峰等人的图书<Kali Linux2 网络渗透测试实践指南>2018年第1版 目录 主动扫描之Nmap 基本用法 主机发现 端口发现 扫描目标操作系统 扫 ...
- git新手配置(windows环境)
windows环境,初步了解git是个什么东西,使用过svn相关软件最佳,否则可以先补一下git的相关概念和用处,相关教程:https://www.liaoxuefeng.com/wiki/89604 ...
- 快来使用Portainer让测试环境搭建飞起来吧
Portainer是Docker的图形化管理工具,提供状态显示面板.应用模板快速部署.容器镜像网络数据卷的基本操作(包括上传下载镜像,创建容器等操作).事件日志显示.容器控制台操作.Swarm集群和服 ...
- SpringSecurity+Jwt遇到的bug
最近在使用springsecurity整合Jwt的时候,遇到了一Bug,卡住了很久,记录一下. 编写Jwt工具类JwtUtil,编写Jwt认证的核心过滤器JwtAuthenticationFilter ...
- R语言与医学统计图形-【25】ggplot图形分面
ggplot2绘图系统--图形分面 ggplot2的分面faceting,主要有三个函数: facet_grid facet_wrap facet_null (不分面) 1. facet_grid函数 ...
- Linux服务器I/O性能分析-3
一.通过脚本分析IO的读/写数量.最大延迟.延迟的分布情况.块大小及数量 #!/bin/sh # # File Name : count_io.sh # Time : 2020-07-29-11:24 ...