重新整理 .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 ...
随机推荐
- Effective C++ 总结笔记(五)
六.继承与面向对象设计 32.确定你的public继承塑模出is-a关系 public继承意味着is-a.适用于base class身上的每一件事情也一定适用于derived class身上.每一个d ...
- 关于postman的接口登录验证问题
1.shiro的接口登录问题 碰到需要接口登录验证的:访问项目接口地址login,找到cookie将Cookie数据放入postman的headers 中. 2.碰到 security的项目.首先把相 ...
- js--迭代器总结
前言 我们已经熟练使用set.map.array几种集合类型了,掌握了map(),for..of..,filter()等迭代集合的方法,你是否思考过,js引擎是怎么迭代的,怎么判断迭代是否结束,本文来 ...
- Codeforces 1455G - Forbidden Value(map 启发式合并+DP)
Codeforces 题面传送门 & 洛谷题面传送门 首先这个 if 与 end 配对的结构显然形成一个树形结构,考虑把这棵树建出来,于是这个程序的结构就变为,对树进行一遍 DFS,到达某个节 ...
- [Cnoi2020]线性生物
期望入门题.但是我不会做. 考虑设\(E_{x\to{x+1}}\)为\(x\)到\(x+1\)点的期望步数. 则\(ans = \sum_{i = 0}^{n} E_{x\to{x+1}}\) 知\ ...
- Codeforces 796E - Exam Cheating(dp)
Codeforces 题目传送门 & 洛谷题目传送门 当被数据结构搞自闭的 tzc 信心满满地点开一道 *2400 的 dp 题时-- 却发现自己不会做?! 这足以证明蒟蒻 dp 之菜/dk/ ...
- perl和python3 同时打开两个文件
perl : while(defined(my $f1=<FQ1>) && defined(my $f2=<FQ2>)){ condition } python ...
- PPT插件——iSlide全功能解析
做幻灯展示是我们日常工作中不可缺少的一部分,有的人喜欢用代码如Latex, markdown+pandoc+revealjs 或 bookdown.这些都是自动化做幻灯的好工具.我也都有过体会,确实简 ...
- 字符scanf 的输入注意
1.注意scanf 不能有空格,如果有空格会将空格给输入进去 scanf("d "):---有空格 和scanf("d");--没有空格 有很大的区别
- 修复UE4编辑器,ClearLog操作导致的崩溃
UE4 4.24.3版本,编辑器Output Log窗口中,右键--Clear Log操作很大概率会导致编辑器奔溃:解决办法: 相关文件: Engine\Source\Developer\Output ...