zoukankan      html  css  js  c++  java
  • 重新整理 .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 ,里面有一丢丢小复杂,有兴趣可以自己去看下,就是扫描那一套了。

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

  • 相关阅读:
    Flex弹性盒模型
    响应式布局rem的使用
    京东首页如何实现pc端和移动端加载不同的html的?
    canvas绘制表盘时钟
    javascript实现ul中列表项随机排列
    使用 HTML5 视频事件
    Javascript获取当前鼠标在元素内的坐标定位
    移动 web 开发问题和优化小结
    关于fisher判别的一点理解
    机器学习第三课(EM算法和高斯混合模型)
  • 原文地址:https://www.cnblogs.com/aoximin/p/15649807.html
Copyright © 2011-2022 走看看