NetCore路由的Endpoint模式

    IdentityServer里有各种Endpoint,如TokenEndpoint,UserInfoEndpoint,Authorize Endpoint,Discovery Endpoint等等。Endpoint从字面意思来看是“终端节点"或者“终节点”的意思。无独有偶NetCore的路由也有Endpoint的概念。那么我们提出一个问题来,究竟什么是Endpoint?

    先来看一段代码,如下所示:

    

NetCore路由的Endpoint模式

public class Program     {         public static void Main(string[] args)         {             CreateHostBuilder(args).Build().Run();         }          public static IHostBuilder CreateHostBuilder(string[] args) =>             Host.CreateDefaultBuilder(args)                 .ConfigureWebHostDefaults(webBuilder =>                 {                     webBuilder.ConfigureServices(svcs => svcs.AddRouting()).Configure(                                                  app => app.UseRouting().UseEndpoints(                         //test1                         //endpoints => endpoints.MapGet("weather/{city}/{days}",                           //test2                           endpoints =>endpoints.MapGet("weather/{city}/{year}.{month}.{day}", WeatherForecast)                                                                             ));                 });          private static Dictionary<string, string> _cities = new Dictionary<string, string>() {             ["001"]="Beijing",             ["028"]="Chengdu",             ["091"]="Suzhou"                  };          //RequestDelegate         public static async Task WeatherForecast(HttpContext context)         {             var city = (string)context.GetRouteData().Values["city"];             city = _cities[city];                         //test1             //int days=int.Parse(context.GetRouteData().Values["days"].ToString());             //var report = new WeatherReport(city, days);              //test2             int year = int.Parse(context.GetRouteData().Values["year"].ToString());             int month= int.Parse(context.GetRouteData().Values["month"].ToString());             int day=int.Parse(context.GetRouteData().Values["day"].ToString());             var report = new WeatherReport(city, new DateTime(year, month, day));             await RenderWeatherAsync(context, report);         }          private static async Task RenderWeatherAsync(HttpContext context,WeatherReport report)         {             context.Response.ContentType = "text/html;charset=utf-8";             await context.Response.WriteAsync("<html><head><title>Weather</title></head><body>");             await context.Response.WriteAsync($"<h3>{report.City}</h3>");             foreach(var it in report.WeatherInfos)             {                 await context.Response.WriteAsync($"{it.Key.ToString("yyyy-MM-dd")}: ");                 await context.Response.WriteAsync($"{it.Value.LowTemperature}--{it.Value.HighTemperature} <br/>");             }             await context.Response.WriteAsync("</body></html>");         }     }

View Code

 

 

上面的代码很清晰,如果路由格式匹配”weather/{city}/{year}.{month}.{day}”,那么就调用WeatherForecast方法,这里的app.UseRouting().UseEndpoints就用到了Endpoint模式。要了解Endpoint,我们需要抽丝剥茧一步一步了解几个类的方法:

   1.我们先看最里面的endpoints =>endpoints.MapGet("weather/{city}/{year}.{month}.{day}", WeatherForecast),这里实际上调用的是EndpointRouteBuilderExtensions类的MapGet方法,代码如下所示:

 

NetCore路由的Endpoint模式

    /// <summary>     /// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add endpoints.     /// </summary>     public static class EndpointRouteBuilderExtensions     {         // Avoid creating a new array every call         private static readonly string[] GetVerb = new[] { "GET" };         private static readonly string[] PostVerb = new[] { "POST" };         private static readonly string[] PutVerb = new[] { "PUT" };         private static readonly string[] DeleteVerb = new[] { "DELETE" };         private static readonly string[] PatchVerb = new[] { "PATCH" };         /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>         public static IEndpointConventionBuilder MapGet(             this IEndpointRouteBuilder endpoints,             string pattern,             RequestDelegate requestDelegate)         {             return MapMethods(endpoints, pattern, GetVerb, requestDelegate);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP POST requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>         public static IEndpointConventionBuilder MapPost(             this IEndpointRouteBuilder endpoints,             string pattern,             RequestDelegate requestDelegate)         {             return MapMethods(endpoints, pattern, PostVerb, requestDelegate);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PUT requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>         public static IEndpointConventionBuilder MapPut(             this IEndpointRouteBuilder endpoints,             string pattern,             RequestDelegate requestDelegate)         {             return MapMethods(endpoints, pattern, PutVerb, requestDelegate);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP DELETE requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>         public static IEndpointConventionBuilder MapDelete(             this IEndpointRouteBuilder endpoints,             string pattern,             RequestDelegate requestDelegate)         {             return MapMethods(endpoints, pattern, DeleteVerb, requestDelegate);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PATCH requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>         public static IEndpointConventionBuilder MapPatch(             this IEndpointRouteBuilder endpoints,             string pattern,             RequestDelegate requestDelegate)         {             return MapMethods(endpoints, pattern, PatchVerb, requestDelegate);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests         /// for the specified HTTP methods and pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>         /// <param name="httpMethods">HTTP methods that the endpoint will match.</param>         /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>         public static IEndpointConventionBuilder MapMethods(            this IEndpointRouteBuilder endpoints,            string pattern,            IEnumerable<string> httpMethods,            RequestDelegate requestDelegate)         {             if (httpMethods == null)             {                 throw new ArgumentNullException(nameof(httpMethods));             }              var builder = endpoints.Map(RoutePatternFactory.Parse(pattern), requestDelegate);             builder.WithDisplayName($"{pattern} HTTP: {string.Join(", ", httpMethods)}");             builder.WithMetadata(new HttpMethodMetadata(httpMethods));             return builder;         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>         public static IEndpointConventionBuilder Map(             this IEndpointRouteBuilder endpoints,             string pattern,             RequestDelegate requestDelegate)         {             return Map(endpoints, RoutePatternFactory.Parse(pattern), requestDelegate);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>         public static IEndpointConventionBuilder Map(             this IEndpointRouteBuilder endpoints,             RoutePattern pattern,             RequestDelegate requestDelegate)         {             if (endpoints == null)             {                 throw new ArgumentNullException(nameof(endpoints));             }              if (pattern == null)             {                 throw new ArgumentNullException(nameof(pattern));             }              if (requestDelegate == null)             {                 throw new ArgumentNullException(nameof(requestDelegate));             }              const int defaultOrder = 0;              //实例化RouteEndpointBuilder             var builder = new RouteEndpointBuilder(                 requestDelegate,                 pattern,                 defaultOrder)             {                 DisplayName = pattern.RawText ?? pattern.DebuggerToString(),             };              // Add delegate attributes as metadata             var attributes = requestDelegate.Method.GetCustomAttributes();              // This can be null if the delegate is a dynamic method or compiled from an expression tree             if (attributes != null)             {                 foreach (var attribute in attributes)                 {                     builder.Metadata.Add(attribute);                 }             }              //Adds a RouteEndpoint to the IEndpointRouteBuilder that matches HTTP requests             /// for the specified pattern.             ///可以理解为:将某种格式的路由pattern添加至IEndpointRouteBuilder的DataSource属性,为match做准备              var dataSource = endpoints.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();             if (dataSource == null)             {                 dataSource = new ModelEndpointDataSource();                 endpoints.DataSources.Add(dataSource);             }              return dataSource.AddEndpointBuilder(builder);         }           /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="handler">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>         public static RouteHandlerBuilder MapGet(             this IEndpointRouteBuilder endpoints,             string pattern,             Delegate handler)         {             return MapMethods(endpoints, pattern, GetVerb, handler);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP POST requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="handler">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>         public static RouteHandlerBuilder MapPost(             this IEndpointRouteBuilder endpoints,             string pattern,             Delegate handler)         {             return MapMethods(endpoints, pattern, PostVerb, handler);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PUT requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="handler">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>         public static RouteHandlerBuilder MapPut(             this IEndpointRouteBuilder endpoints,             string pattern,             Delegate handler)         {             return MapMethods(endpoints, pattern, PutVerb, handler);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP DELETE requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="handler">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>         public static RouteHandlerBuilder MapDelete(             this IEndpointRouteBuilder endpoints,             string pattern,             Delegate handler)         {             return MapMethods(endpoints, pattern, DeleteVerb, handler);         }           /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PATCH requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="handler">The <see cref="Delegate" /> executed when the endpoint is matched.</param>         /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>         public static RouteHandlerBuilder MapPatch(             this IEndpointRouteBuilder endpoints,             string pattern,             Delegate handler)         {             return MapMethods(endpoints, pattern, PatchVerb, handler);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests         /// for the specified HTTP methods and pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="handler">The delegate executed when the endpoint is matched.</param>         /// <param name="httpMethods">HTTP methods that the endpoint will match.</param>         /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>         public static RouteHandlerBuilder MapMethods(            this IEndpointRouteBuilder endpoints,            string pattern,            IEnumerable<string> httpMethods,            Delegate handler)         {             if (httpMethods is null)             {                 throw new ArgumentNullException(nameof(httpMethods));             }              var disableInferredBody = false;             foreach (var method in httpMethods)             {                 disableInferredBody = ShouldDisableInferredBody(method);                 if (disableInferredBody is true)                 {                     break;                 }             }              var builder = endpoints.Map(RoutePatternFactory.Parse(pattern), handler, disableInferredBody);             // Prepends the HTTP method to the DisplayName produced with pattern + method name             builder.Add(b => b.DisplayName = $"HTTP: {string.Join(", ", httpMethods)} {b.DisplayName}");             builder.WithMetadata(new HttpMethodMetadata(httpMethods));             return builder;              static bool ShouldDisableInferredBody(string method)             {                  // GET, DELETE, HEAD, CONNECT, TRACE, and OPTIONS normally do not contain bodies                 return method.Equals(HttpMethods.Get, StringComparison.Ordinal) ||                        method.Equals(HttpMethods.Delete, StringComparison.Ordinal) ||                        method.Equals(HttpMethods.Head, StringComparison.Ordinal) ||                        method.Equals(HttpMethods.Options, StringComparison.Ordinal) ||                        method.Equals(HttpMethods.Trace, StringComparison.Ordinal) ||                        method.Equals(HttpMethods.Connect, StringComparison.Ordinal);             }         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="handler">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>         public static RouteHandlerBuilder Map(             this IEndpointRouteBuilder endpoints,             string pattern,             Delegate handler)         {             return Map(endpoints, RoutePatternFactory.Parse(pattern), handler);         }          /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="handler">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>         public static RouteHandlerBuilder Map(             this IEndpointRouteBuilder endpoints,             RoutePattern pattern,             Delegate handler)         {             return Map(endpoints, pattern, handler, disableInferBodyFromParameters: false);         }          /// <summary>         /// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match         /// requests for non-file-names with the lowest possible priority.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="handler">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>         /// <remarks>         /// <para>         /// <see cref="MapFallback(IEndpointRouteBuilder, Delegate)"/> is intended to handle cases where URL path of         /// the request does not contain a file name, and no other endpoint has matched. This is convenient for routing         /// requests for dynamic content to a SPA framework, while also allowing requests for non-existent files to         /// result in an HTTP 404.         /// </para>         /// <para>         /// <see cref="MapFallback(IEndpointRouteBuilder, Delegate)"/> registers an endpoint using the pattern         /// <c>{*path:nonfile}</c>. The order of the registered endpoint will be <c>int.MaxValue</c>.         /// </para>         /// </remarks>         public static RouteHandlerBuilder MapFallback(this IEndpointRouteBuilder endpoints, Delegate handler)         {             if (endpoints == null)             {                 throw new ArgumentNullException(nameof(endpoints));             }              if (handler == null)             {                 throw new ArgumentNullException(nameof(handler));             }              return endpoints.MapFallback("{*path:nonfile}", handler);         }          /// <summary>         /// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match         /// the provided pattern with the lowest possible priority.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="handler">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>         /// <remarks>         /// <para>         /// <see cref="MapFallback(IEndpointRouteBuilder, string, Delegate)"/> is intended to handle cases where no         /// other endpoint has matched. This is convenient for routing requests to a SPA framework.         /// </para>         /// <para>         /// The order of the registered endpoint will be <c>int.MaxValue</c>.         /// </para>         /// <para>         /// This overload will use the provided <paramref name="pattern"/> verbatim. Use the <c>:nonfile</c> route constraint         /// to exclude requests for static files.         /// </para>         /// </remarks>         public static RouteHandlerBuilder MapFallback(             this IEndpointRouteBuilder endpoints,             string pattern,             Delegate handler)         {             if (endpoints == null)             {                 throw new ArgumentNullException(nameof(endpoints));             }              if (pattern == null)             {                 throw new ArgumentNullException(nameof(pattern));             }              if (handler == null)             {                 throw new ArgumentNullException(nameof(handler));             }              var conventionBuilder = endpoints.Map(pattern, handler);             conventionBuilder.WithDisplayName("Fallback " + pattern);             conventionBuilder.Add(b => ((RouteEndpointBuilder)b).Order = int.MaxValue);             return conventionBuilder;         }          private static RouteHandlerBuilder Map(             this IEndpointRouteBuilder endpoints,             RoutePattern pattern,             Delegate handler,             bool disableInferBodyFromParameters)         {             if (endpoints is null)             {                 throw new ArgumentNullException(nameof(endpoints));             }              if (pattern is null)             {                 throw new ArgumentNullException(nameof(pattern));             }              if (handler is null)             {                 throw new ArgumentNullException(nameof(handler));             }              const int defaultOrder = 0;              var routeParams = new List<string>(pattern.Parameters.Count);             foreach (var part in pattern.Parameters)             {                 routeParams.Add(part.Name);             }              var routeHandlerOptions = endpoints.ServiceProvider?.GetService<IOptions<RouteHandlerOptions>>();              var options = new RequestDelegateFactoryOptions             {                 ServiceProvider = endpoints.ServiceProvider,                 RouteParameterNames = routeParams,                 ThrowOnBadRequest = routeHandlerOptions?.Value.ThrowOnBadRequest ?? false,                 DisableInferBodyFromParameters = disableInferBodyFromParameters,             };              var requestDelegateResult = RequestDelegateFactory.Create(handler, options);              var builder = new RouteEndpointBuilder(                 requestDelegateResult.RequestDelegate,                 pattern,                 defaultOrder)             {                 DisplayName = pattern.RawText ?? pattern.DebuggerToString(),             };              // REVIEW: Should we add an IActionMethodMetadata with just MethodInfo on it so we are             // explicit about the MethodInfo representing the "handler" and not the RequestDelegate?              // Add MethodInfo as metadata to assist with OpenAPI generation for the endpoint.             builder.Metadata.Add(handler.Method);              // Methods defined in a top-level program are generated as statics so the delegate             // target will be null. Inline lambdas are compiler generated method so they can             // be filtered that way.             if (GeneratedNameParser.TryParseLocalFunctionName(handler.Method.Name, out var endpointName)                 || !TypeHelper.IsCompilerGeneratedMethod(handler.Method))             {                 endpointName ??= handler.Method.Name;                 builder.DisplayName = $"{builder.DisplayName} => {endpointName}";             }              // Add delegate attributes as metadata             var attributes = handler.Method.GetCustomAttributes();              // Add add request delegate metadata             foreach (var metadata in requestDelegateResult.EndpointMetadata)             {                 builder.Metadata.Add(metadata);             }              // This can be null if the delegate is a dynamic method or compiled from an expression tree             if (attributes is not null)             {                 foreach (var attribute in attributes)                 {                     builder.Metadata.Add(attribute);                 }             }              var dataSource = endpoints.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();             if (dataSource is null)             {                 dataSource = new ModelEndpointDataSource();                 endpoints.DataSources.Add(dataSource);             }              return new RouteHandlerBuilder(dataSource.AddEndpointBuilder(builder));         }     } }

View Code

 

在这里我们重点看看下面签名的方法:

 

NetCore路由的Endpoint模式

 /// <summary>         /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests         /// for the specified pattern.         /// </summary>         /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>         /// <param name="pattern">The route pattern.</param>         /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>         /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>         public static IEndpointConventionBuilder Map(             this IEndpointRouteBuilder endpoints,             RoutePattern pattern,             RequestDelegate requestDelegate)         {             if (endpoints == null)             {                 throw new ArgumentNullException(nameof(endpoints));             }              if (pattern == null)             {                 throw new ArgumentNullException(nameof(pattern));             }              if (requestDelegate == null)             {                 throw new ArgumentNullException(nameof(requestDelegate));             }              const int defaultOrder = 0;              //实例化RouteEndpointBuilder             var builder = new RouteEndpointBuilder(                 requestDelegate,                 pattern,                 defaultOrder)             {                 DisplayName = pattern.RawText ?? pattern.DebuggerToString(),             };              // Add delegate attributes as metadata             var attributes = requestDelegate.Method.GetCustomAttributes();              // This can be null if the delegate is a dynamic method or compiled from an expression tree             if (attributes != null)             {                 foreach (var attribute in attributes)                 {                     builder.Metadata.Add(attribute);                 }             }              //Adds a RouteEndpoint to the IEndpointRouteBuilder that matches HTTP requests             /// for the specified pattern.             ///可以理解为:将某种格式的路由pattern添加至IEndpointRouteBuilder的DataSource属性,为match做准备              var dataSource = endpoints.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();             if (dataSource == null)             {                 dataSource = new ModelEndpointDataSource();                 endpoints.DataSources.Add(dataSource);             }              return dataSource.AddEndpointBuilder(builder);         }

View Code

 

在这个方法里有一个实例化RouteEndpointBuilder的语句:

  var builder = new RouteEndpointBuilder(                 requestDelegate,                 pattern,                 defaultOrder)             {                 DisplayName = pattern.RawText ?? pattern.DebuggerToString(),             };

 

其中RouteEndpointBuilder从字面意思看得出,就是RouteEndpoint的创建者,我们看下它的代码:

NetCore路由的Endpoint模式

 public sealed class RouteEndpointBuilder : EndpointBuilder     {         /// <summary>         /// Gets or sets the <see cref="RoutePattern"/> associated with this endpoint.         /// </summary>         public RoutePattern RoutePattern { get; set; }          /// <summary>         ///  Gets or sets the order assigned to the endpoint.         /// </summary>         public int Order { get; set; }          /// <summary>         /// Constructs a new <see cref="RouteEndpointBuilder"/> instance.         /// </summary>         /// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>         /// <param name="routePattern">The <see cref="RoutePattern"/> to use in URL matching.</param>         /// <param name="order">The order assigned to the endpoint.</param>         public RouteEndpointBuilder(            RequestDelegate requestDelegate,            RoutePattern routePattern,            int order)         {             RequestDelegate = requestDelegate;             RoutePattern = routePattern;             Order = order;         }          //生成Route         /// <inheritdoc />         public override Endpoint Build()         {             if (RequestDelegate is null)             {                 throw new InvalidOperationException($"{nameof(RequestDelegate)} must be specified to construct a {nameof(RouteEndpoint)}.");             }              var routeEndpoint = new RouteEndpoint(                 RequestDelegate,                 RoutePattern,                 Order,                 new EndpointMetadataCollection(Metadata),                 DisplayName);              return routeEndpoint;         }     }

View Code

 

再来看下Endpoint的代码:

NetCore路由的Endpoint模式

   /// <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; }          /// <summary>         /// Returns a string representation of the endpoint.         /// </summary>         public override string? ToString() => DisplayName ?? base.ToString();     }

View Code

 

从Build()方法看得出,它利用路由匹配的RoutePattern,生成一个RouteEndpoint实例。RouteEndpoint叫做路由终点,继承自Endpoint。Endpoint有一个重要的RequestDelegate属性,用来处理当前的请求。看到这里,我们开始推断:所谓的Endpoint,无非就是用要匹配的pattern,构造一个RouteEndpoint的过程,其中RouteEndpoint继承自Endpoint。NetCore无非就是利用这个RouteEndpoint来匹配当前的Url,如果匹配得上,就执行RequestDelegate所代表的方法,上文就是WeatherForecast方法,如果匹配不上,则不执行WeatherForecast方法,仅此而已。

  2.为了验证,我们再来看 app.UseRouting().UseEndpoints(),实际上调用的是EndpointRoutingApplicationBuilderExtensions类的两个方法:

NetCore路由的Endpoint模式

   public static class EndpointRoutingApplicationBuilderExtensions     {         private const string EndpointRouteBuilder = "__EndpointRouteBuilder";         private const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";          /// <summary>         /// Adds a <see cref="EndpointRoutingMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>.         /// </summary>         /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>         /// <returns>A reference to this instance after the operation has completed.</returns>         /// <remarks>         /// <para>         /// A call to <see cref="UseRouting(IApplicationBuilder)"/> must be followed by a call to         /// <see cref="UseEndpoints(IApplicationBuilder, Action{IEndpointRouteBuilder})"/> for the same <see cref="IApplicationBuilder"/>         /// instance.         /// </para>         /// <para>         /// The <see cref="EndpointRoutingMiddleware"/> defines a point in the middleware pipeline where routing decisions are         /// made, and an <see cref="Endpoint"/> is associated with the <see cref="HttpContext"/>. The <see cref="EndpointMiddleware"/>         /// defines a point in the middleware pipeline where the current <see cref="Endpoint"/> is executed. Middleware between         /// the <see cref="EndpointRoutingMiddleware"/> and <see cref="EndpointMiddleware"/> may observe or change the         /// <see cref="Endpoint"/> associated with the <see cref="HttpContext"/>.         /// </para>         /// </remarks>         public static IApplicationBuilder UseRouting(this IApplicationBuilder builder)         {             if (builder == null)             {                 throw new ArgumentNullException(nameof(builder));             }              VerifyRoutingServicesAreRegistered(builder);              IEndpointRouteBuilder endpointRouteBuilder;             if (builder.Properties.TryGetValue(GlobalEndpointRouteBuilderKey, out var obj))             {                 endpointRouteBuilder = (IEndpointRouteBuilder)obj!;                 // Let interested parties know if UseRouting() was called while a global route builder was set                 builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;             }             else             {                 endpointRouteBuilder = new DefaultEndpointRouteBuilder(builder);                 builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;             }              //先交由EndpointRoutingMiddleware中间件匹配Endpoint,然后交由下面的EndpointMiddleware处理             return builder.UseMiddleware<EndpointRoutingMiddleware>(endpointRouteBuilder);         }          /// <summary>         /// Adds a <see cref="EndpointMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>         /// with the <see cref="EndpointDataSource"/> instances built from configured <see cref="IEndpointRouteBuilder"/>.         /// The <see cref="EndpointMiddleware"/> will execute the <see cref="Endpoint"/> associated with the current         /// request.         /// </summary>         /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>         /// <param name="configure">An <see cref="Action{IEndpointRouteBuilder}"/> to configure the provided <see cref="IEndpointRouteBuilder"/>.</param>         /// <returns>A reference to this instance after the operation has completed.</returns>         /// <remarks>         /// <para>         /// A call to <see cref="UseEndpoints(IApplicationBuilder, Action{IEndpointRouteBuilder})"/> must be preceded by a call to         /// <see cref="UseRouting(IApplicationBuilder)"/> for the same <see cref="IApplicationBuilder"/>         /// instance.         /// </para>         /// <para>         /// The <see cref="EndpointRoutingMiddleware"/> defines a point in the middleware pipeline where routing decisions are         /// made, and an <see cref="Endpoint"/> is associated with the <see cref="HttpContext"/>. The <see cref="EndpointMiddleware"/>         /// defines a point in the middleware pipeline where the current <see cref="Endpoint"/> is executed. Middleware between         /// the <see cref="EndpointRoutingMiddleware"/> and <see cref="EndpointMiddleware"/> may observe or change the         /// <see cref="Endpoint"/> associated with the <see cref="HttpContext"/>.         /// </para>         /// </remarks>         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)             {                 if (!routeOptions.Value.EndpointDataSources.Contains(dataSource))                 {                     routeOptions.Value.EndpointDataSources.Add(dataSource);                 }             }              //交由EndpointMiddleware处理             return builder.UseMiddleware<EndpointMiddleware>();         }          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(...)"));             }         }          private static void VerifyEndpointRoutingMiddlewareIsRegistered(IApplicationBuilder app, out IEndpointRouteBuilder 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);             }              endpointRouteBuilder = (IEndpointRouteBuilder)obj!;              // This check handles the case where Map or something else that forks the pipeline is called between the two             // routing middleware.             if (endpointRouteBuilder is DefaultEndpointRouteBuilder defaultRouteBuilder && !object.ReferenceEquals(app, defaultRouteBuilder.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);             }         }     }

View Code

   2-1首先看UseRouting()方法,这个方法其实就是调用EndpointRoutingMiddleware中间件进行Endpoint的匹配,我们可以看下它的几个方法:

  

NetCore路由的Endpoint模式

internal sealed partial 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 matching 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;                 //根据httpContext进行匹配                 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             {                 //用EndpointDataSource初始化matcher                 //EndpointDataSource来源于IEndpointRouteBuilder的DataSource属性                 //见EndpointRouteBuilderExtensions类的201行                 var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);                  _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 partial class Log         {             public static void MatchSuccess(ILogger logger, Endpoint endpoint)                 => MatchSuccess(logger, endpoint.DisplayName);              [LoggerMessage(1, LogLevel.Debug, "Request matched endpoint '{EndpointName}'", EventName = "MatchSuccess")]             private static partial void MatchSuccess(ILogger logger, string? endpointName);              [LoggerMessage(2, LogLevel.Debug, "Request did not match any endpoints", EventName = "MatchFailure")]             public static partial void MatchFailure(ILogger logger);              public static void MatchSkipped(ILogger logger, Endpoint endpoint)                 => MatchingSkipped(logger, endpoint.DisplayName);              [LoggerMessage(3, LogLevel.Debug, "Endpoint '{EndpointName}' already set, skipping route matching.", EventName = "MatchingSkipped")]             private static partial void MatchingSkipped(ILogger logger, string? endpointName);         }     }

View Code

从Invoke方法看得出来,它根据当前的HttpContext进行Endpoint的匹配,如果当前的HttpContext路由格式匹配成功,那么将当前HttpContext传递给下一个中间件处理,这个从SetRoutingAndContinue方法看得出来。

 

 2-2其次看下UseEndpoints()方法,这个方法就是调用EndpointMiddleware中间件,对上面匹配成功的HttpContext进行处理,并调用HttpContext的EndPoint的RequestDelegate处理当前请求。我们重点看下它的Invoke方法,重点关注var requestTask = endpoint.RequestDelegate(httpContext):

NetCore路由的Endpoint模式

  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                 {                     //调用HttpContext的Endpoint的RequestDelegate方法处理当前请求                     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);                 }             }         }

View Code

 

  总结:从上面的分析,我们粗略的了解了netcore路由的Endpoint模式其实就是一种用匹配模式构建的终端节点,它主要用来对HttpContext进行路由的匹配,如果匹配成功,则执行Endpoint上的RequestDelegate方法。

 

 

  

 

发表评论

相关文章