I. Antecedentes
En va a escribir un artículo sobre este Identityserver4
artículo, se encontraron en EndPoint
- la expedición punto final no es muy comprensiva, por lo que ser el primero en abandonar la IdentityServer4
investigación y la escritura; por lo que tuvieron hoy sobre este EndPoint
artículo (punto final de enrutamiento) de.
O, como de costumbre, abrir la potencia de los ordenadores de Google y Baidu acceso a los motores de búsqueda a la información pertinente, así como de núcleo abierto código fuente Asp.net 3.1 leí, y, finalmente, en mi práctica y pruebas para EndPoint
tener una comprensión diferente, diciendo aquí es más que admirar en la tubería para el modelo de diseño de la estructura del núcleo Asp.net 3.x de Microsoft.
Me gustaría plantear las siguientes preguntas:
1. Cuando se accede a una dirección de aplicación Web, Asp.Net Core es cómo llevar a cabo Controller
en Action
él? 2. Endpoint
con la vía ordinaria y no es qué tipo de relación? 3 UseRouing()
, UseAuthorization()
, UserEndpoints()
la relación entre estos tres middleware ¿Qué es? 4. cómo el uso Endpoint
de escribir su propio middleware y escenarios de aplicación de punto final (por un tiempo limitado, consolidación de la acción la próxima vez)
En segundo lugar, lea las dudas de código fuente
Startup
código
En primer vistazo a Startup
la versión simplificada del código, el código es el siguiente:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Programa fase de puesta en marcha:
Paso 1: Ejecutar services.AddControllers () los Controller
servicios básicos registrados al recipiente a
El segundo paso: la implementación de app.UseRouting () se EndpointRoutingMiddleware
middleware registro para http oleoducto
El tercer paso: la implementación de app.UseAuthorization () se AuthorizationMiddleware
middleware registro para http oleoducto
El cuarto paso: realizar app.UseEndpoints (encpoints => endpoints.MapControllers () ) tiene dos funciones principales: llamada endpoints.MapControllers()
a un conjunto definido de todo el programa Controller
y Action
convertir el uno EndPoint
en el middleware objeto de configuración de enrutamiento RouteOptions
en el EndpointMiddleware
medio inscripciones para tubería http
app.UseRouting()
El código fuente es el siguiente:
public static IApplicationBuilder UseRouting(this IApplicationBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
VerifyRoutingServicesAreRegistered(builder);
var endpointRouteBuilder = new DefaultEndpointRouteBuilder(builder);
builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;
return builder.UseMiddleware<EndpointRoutingMiddleware>(endpointRouteBuilder);
}
EndpointRoutingMiddleware
middleware de código es el siguiente:
internal sealed class EndpointRoutingMiddleware
{
private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched";
private readonly MatcherFactory _matcherFactory;
private readonly ILogger _logger;
private readonly EndpointDataSource _endpointDataSource;
private readonly DiagnosticListener _diagnosticListener;
private readonly RequestDelegate _next;
private Task<Matcher> _initializationTask;
public EndpointRoutingMiddleware(
MatcherFactory matcherFactory,
ILogger<EndpointRoutingMiddleware> logger,
IEndpointRouteBuilder endpointRouteBuilder,
DiagnosticListener diagnosticListener,
RequestDelegate next)
{
if (endpointRouteBuilder == null)
{
throw new ArgumentNullException(nameof(endpointRouteBuilder));
}
_matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
_next = next ?? throw new ArgumentNullException(nameof(next));
_endpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources);
}
public Task Invoke(HttpContext httpContext)
{
// There's already an endpoint, skip maching completely
var endpoint = httpContext.GetEndpoint();
if (endpoint != null)
{
Log.MatchSkipped(_logger, endpoint);
return _next(httpContext);
}
// There's an inherent race condition between waiting for init and accessing the matcher
// this is OK because once `_matcher` is initialized, it will not be set to null again.
var matcherTask = InitializeAsync();
if (!matcherTask.IsCompletedSuccessfully)
{
return AwaitMatcher(this, httpContext, matcherTask);
}
var matchTask = matcherTask.Result.MatchAsync(httpContext);
if (!matchTask.IsCompletedSuccessfully)
{
return AwaitMatch(this, httpContext, matchTask);
}
return SetRoutingAndContinue(httpContext);
// Awaited fallbacks for when the Tasks do not synchronously complete
static async Task AwaitMatcher(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task<Matcher> matcherTask)
{
var matcher = await matcherTask;
await matcher.MatchAsync(httpContext);
await middleware.SetRoutingAndContinue(httpContext);
}
static async Task AwaitMatch(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matchTask)
{
await matchTask;
await middleware.SetRoutingAndContinue(httpContext);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Task SetRoutingAndContinue(HttpContext httpContext)
{
// If there was no mutation of the endpoint then log failure
var endpoint = httpContext.GetEndpoint();
if (endpoint == null)
{
Log.MatchFailure(_logger);
}
else
{
// Raise an event if the route matched
if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey))
{
// We're just going to send the HttpContext since it has all of the relevant information
_diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext);
}
Log.MatchSuccess(_logger, endpoint);
}
return _next(httpContext);
}
// Initialization is async to avoid blocking threads while reflection and things
// of that nature take place.
//
// We've seen cases where startup is very slow if we allow multiple threads to race
// while initializing the set of endpoints/routes. Doing CPU intensive work is a
// blocking operation if you have a low core count and enough work to do.
private Task<Matcher> InitializeAsync()
{
var initializationTask = _initializationTask;
if (initializationTask != null)
{
return initializationTask;
}
return InitializeCoreAsync();
}
private Task<Matcher> InitializeCoreAsync()
{
var initialization = new TaskCompletionSource<Matcher>(TaskCreationOptions.RunContinuationsAsynchronously);
var initializationTask = Interlocked.CompareExchange(ref _initializationTask, initialization.Task, null);
if (initializationTask != null)
{
// This thread lost the race, join the existing task.
return initializationTask;
}
// This thread won the race, do the initialization.
try
{
var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);
// Now replace the initialization task with one created with the default execution context.
// This is important because capturing the execution context will leak memory in ASP.NET Core.
using (ExecutionContext.SuppressFlow())
{
_initializationTask = Task.FromResult(matcher);
}
// Complete the task, this will unblock any requests that came in while initializing.
initialization.SetResult(matcher);
return initialization.Task;
}
catch (Exception ex)
{
// Allow initialization to occur again. Since DataSources can change, it's possible
// for the developer to correct the data causing the failure.
_initializationTask = null;
// Complete the task, this will throw for any requests that came in while initializing.
initialization.SetException(ex);
return initialization.Task;
}
}
private static class Log
{
private static readonly Action<ILogger, string, Exception> _matchSuccess = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(1, "MatchSuccess"),
"Request matched endpoint '{EndpointName}'");
private static readonly Action<ILogger, Exception> _matchFailure = LoggerMessage.Define(
LogLevel.Debug,
new EventId(2, "MatchFailure"),
"Request did not match any endpoints");
private static readonly Action<ILogger, string, Exception> _matchingSkipped = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(3, "MatchingSkipped"),
"Endpoint '{EndpointName}' already set, skipping route matching.");
public static void MatchSuccess(ILogger logger, Endpoint endpoint)
{
_matchSuccess(logger, endpoint.DisplayName, null);
}
public static void MatchFailure(ILogger logger)
{
_matchFailure(logger, null);
}
public static void MatchSkipped(ILogger logger, Endpoint endpoint)
{
_matchingSkipped(logger, endpoint.DisplayName, null);
}
}
}
Podemos ver desde el código fuente, EndpointRoutingMiddleware
middleware creado por primera vez matcher
, y luego llamar matcher.MatchAsync(httpContext)
para encontrar puntos finales, y, finalmente, por httpContext.GetEndpoint()
si el partido se ha verificado a la derecha Endpoint
y cruzar un lado Middleware continúa!
app.UseEndpoints()
fuente
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>();
}
internal class DefaultEndpointRouteBuilder : IEndpointRouteBuilder
{
public DefaultEndpointRouteBuilder(IApplicationBuilder applicationBuilder)
{
ApplicationBuilder = applicationBuilder ?? throw new ArgumentNullException(nameof(applicationBuilder));
DataSources = new List<EndpointDataSource>();
}
public IApplicationBuilder ApplicationBuilder { get; }
public IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New();
public ICollection<EndpointDataSource> DataSources { get; }
public IServiceProvider ServiceProvider => ApplicationBuilder.ApplicationServices;
}
Código construido DefaultEndpointRouteBuilder
punto final de enrutamiento objeto constructor, que se almacena en Endpoint
un conjunto de transacciones; Terminator mientras que los datos almacenados en el conjunto de rutas routeOptions
en el registro y el EndpointMiddleware
conducto intermedio a http; Endpoint
código objeto como sigue:
/// <summary>
/// Represents a logical endpoint in an application.
/// </summary>
public class Endpoint
{
/// <summary>
/// Creates a new instance of <see cref="Endpoint"/>.
/// </summary>
/// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
/// <param name="metadata">
/// The endpoint <see cref="EndpointMetadataCollection"/>. May be null.
/// </param>
/// <param name="displayName">
/// The informational display name of the endpoint. May be null.
/// </param>
public Endpoint(
RequestDelegate requestDelegate,
EndpointMetadataCollection metadata,
string displayName)
{
// All are allowed to be null
RequestDelegate = requestDelegate;
Metadata = metadata ?? EndpointMetadataCollection.Empty;
DisplayName = displayName;
}
/// <summary>
/// Gets the informational display name of this endpoint.
/// </summary>
public string DisplayName { get; }
/// <summary>
/// Gets the collection of metadata associated with this endpoint.
/// </summary>
public EndpointMetadataCollection Metadata { get; }
/// <summary>
/// Gets the delegate used to process requests for the endpoint.
/// </summary>
public RequestDelegate RequestDelegate { get; }
public override string ToString() => DisplayName ?? base.ToString();
}
Endpoint
Hay dos claves tipo de código de objeto de atributo son EndpointMetadataCollection
Tipo y RequestDelegate
:
EndpointMetadataCollection: tiendas Controller
y Action
conjunto asociado de elementos que comprenden Action
los Attribute
datos característicos RequestDelegate
: Acción es decir, la comisión almacenado, este es un método de controlador de acción por
Y luego volver al EndpointMiddleware
middleware y el código del núcleo, EndpointMiddleware
un código de núcleo grande es la implementación de punto final de RequestDelegate
la comisión, a saber, Controller
la Action
ejecución.
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);
}
}
}
Las dudas Respuesta:
1. Cuando se accede a una dirección de aplicación Web, Asp.Net Core es cómo llevar a cabo Controller
en Action
él?
A: El programa se inicia cuando el mapeo de acción almacenada en el controlador, con toda routeOptions
de la colección, Acción mapeadas en Endpoint
el Terminator RequestDelegate
confió propiedad, y finalmente por UseEndPoints
la adición de EndpointMiddleware
middleware ejecución, mientras que el middleware Endpoint
ruta Terminator es a través Rouing
después de expulsar el partido.
2. EndPoint
con la vía ordinaria y no es qué tipo de relación?
A: Ednpoint
ruta Terminator está encargado por la ruta ordinaria después de la conversión mapa de ruta, que contiene todos los elementos del mensaje método de enrutamiento EndpointMetadataCollection
y RequestDelegate
delegado.
3 UseRouing()
, UseAuthorization()
, UseEndpoints()
la relación entre estos tres middleware ¿Qué es?
R: UseRouing
Middleware se dirige principalmente partido, encontrar los correspondientes ruta Terminator Endpoint
; UseEndpoints
middleware para las principales UseRouing
ejecución de la operación métodos partido middleware delegado ruta. UseAuthorization
Middleware dirigido UseRouing
para el juego de enrutamiento middleware qué operación de verificación de autorización de interceptación, se realiza a través de los siguientes intermedios UseEndpoints()
relaciones, en particular puede ser visto en el siguiente diagrama de flujo:
En el diagrama de flujo por encima de omitir algunas partes, principalmente el UseRouing relación resaltado, UseAuthorization, UseEndpoint tres middleware.