重新整理 .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 ,里面有一丟丟小復雜,有興趣可以自己去看下,就是掃描那一套了。

下一節,介紹一下文件上傳。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM