• NetCore路由的Endpoint模式


        IdentityServer里有各种Endpoint,如TokenEndpoint,UserInfoEndpoint,Authorize Endpoint,Discovery Endpoint等等。Endpoint从字面意思来看是“终端节点"或者“终节点”的意思。无独有偶NetCore的路由也有Endpoint的概念。那么我们提出一个问题来,究竟什么是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("Weather");
                await context.Response.WriteAsync($"

    {report.City}

    "); 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}
    "); } await context.Response.WriteAsync(""); } }
    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方法,代码如下所示:

     

        /// 
        /// Provides extension methods for  to add endpoints.
        /// 
        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" };
            /// 
            /// Adds a  to the  that matches HTTP GET requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static IEndpointConventionBuilder MapGet(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                RequestDelegate requestDelegate)
            {
                return MapMethods(endpoints, pattern, GetVerb, requestDelegate);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP POST requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static IEndpointConventionBuilder MapPost(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                RequestDelegate requestDelegate)
            {
                return MapMethods(endpoints, pattern, PostVerb, requestDelegate);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP PUT requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static IEndpointConventionBuilder MapPut(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                RequestDelegate requestDelegate)
            {
                return MapMethods(endpoints, pattern, PutVerb, requestDelegate);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP DELETE requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static IEndpointConventionBuilder MapDelete(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                RequestDelegate requestDelegate)
            {
                return MapMethods(endpoints, pattern, DeleteVerb, requestDelegate);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP PATCH requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static IEndpointConventionBuilder MapPatch(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                RequestDelegate requestDelegate)
            {
                return MapMethods(endpoints, pattern, PatchVerb, requestDelegate);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP requests
            /// for the specified HTTP methods and pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// HTTP methods that the endpoint will match.
            /// A  that can be used to further customize the endpoint.
            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;
            }
    
            /// 
            /// Adds a  to the  that matches HTTP requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static IEndpointConventionBuilder Map(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                RequestDelegate requestDelegate)
            {
                return Map(endpoints, RoutePatternFactory.Parse(pattern), requestDelegate);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            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().FirstOrDefault();
                if (dataSource == null)
                {
                    dataSource = new ModelEndpointDataSource();
                    endpoints.DataSources.Add(dataSource);
                }
    
                return dataSource.AddEndpointBuilder(builder);
            }
    
    
            /// 
            /// Adds a  to the  that matches HTTP GET requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static RouteHandlerBuilder MapGet(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                Delegate handler)
            {
                return MapMethods(endpoints, pattern, GetVerb, handler);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP POST requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static RouteHandlerBuilder MapPost(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                Delegate handler)
            {
                return MapMethods(endpoints, pattern, PostVerb, handler);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP PUT requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static RouteHandlerBuilder MapPut(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                Delegate handler)
            {
                return MapMethods(endpoints, pattern, PutVerb, handler);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP DELETE requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static RouteHandlerBuilder MapDelete(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                Delegate handler)
            {
                return MapMethods(endpoints, pattern, DeleteVerb, handler);
            }
    
    
            /// 
            /// Adds a  to the  that matches HTTP PATCH requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The  executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static RouteHandlerBuilder MapPatch(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                Delegate handler)
            {
                return MapMethods(endpoints, pattern, PatchVerb, handler);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP requests
            /// for the specified HTTP methods and pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// HTTP methods that the endpoint will match.
            /// A  that can be used to further customize the endpoint.
            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);
                }
            }
    
            /// 
            /// Adds a  to the  that matches HTTP requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static RouteHandlerBuilder Map(
                this IEndpointRouteBuilder endpoints,
                string pattern,
                Delegate handler)
            {
                return Map(endpoints, RoutePatternFactory.Parse(pattern), handler);
            }
    
            /// 
            /// Adds a  to the  that matches HTTP requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            public static RouteHandlerBuilder Map(
                this IEndpointRouteBuilder endpoints,
                RoutePattern pattern,
                Delegate handler)
            {
                return Map(endpoints, pattern, handler, disableInferBodyFromParameters: false);
            }
    
            /// 
            /// Adds a specialized  to the  that will match
            /// requests for non-file-names with the lowest possible priority.
            /// 
            /// The  to add the route to.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            /// 
            /// 
            ///  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.
            /// 
            /// 
            ///  registers an endpoint using the pattern
            /// {*path:nonfile}. The order of the registered endpoint will be int.MaxValue.
            /// 
            /// 
            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);
            }
    
            /// 
            /// Adds a specialized  to the  that will match
            /// the provided pattern with the lowest possible priority.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            /// 
            /// 
            ///  is intended to handle cases where no
            /// other endpoint has matched. This is convenient for routing requests to a SPA framework.
            /// 
            /// 
            /// The order of the registered endpoint will be int.MaxValue.
            /// 
            /// 
            /// This overload will use the provided  verbatim. Use the :nonfile route constraint
            /// to exclude requests for static files.
            /// 
            /// 
            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>();
    
                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().FirstOrDefault();
                if (dataSource is null)
                {
                    dataSource = new ModelEndpointDataSource();
                    endpoints.DataSources.Add(dataSource);
                }
    
                return new RouteHandlerBuilder(dataSource.AddEndpointBuilder(builder));
            }
        }
    }
    View Code

     

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

     

     /// 
            /// Adds a  to the  that matches HTTP requests
            /// for the specified pattern.
            /// 
            /// The  to add the route to.
            /// The route pattern.
            /// The delegate executed when the endpoint is matched.
            /// A  that can be used to further customize the endpoint.
            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().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的创建者,我们看下它的代码:

     public sealed class RouteEndpointBuilder : EndpointBuilder
        {
            /// 
            /// Gets or sets the  associated with this endpoint.
            /// 
            public RoutePattern RoutePattern { get; set; }
    
            /// 
            ///  Gets or sets the order assigned to the endpoint.
            /// 
            public int Order { get; set; }
    
            /// 
            /// Constructs a new  instance.
            /// 
            /// The delegate used to process requests for the endpoint.
            /// The  to use in URL matching.
            /// The order assigned to the endpoint.
            public RouteEndpointBuilder(
               RequestDelegate requestDelegate,
               RoutePattern routePattern,
               int order)
            {
                RequestDelegate = requestDelegate;
                RoutePattern = routePattern;
                Order = order;
            }
    
            //生成Route
            /// 
            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的代码:

       /// 
        /// Represents a logical endpoint in an application.
        /// 
        public class Endpoint
        {
            /// 
            /// Creates a new instance of .
            /// 
            /// The delegate used to process requests for the endpoint.
            /// 
            /// The endpoint . May be null.
            /// 
            /// 
            /// The informational display name of the endpoint. May be null.
            /// 
            public Endpoint(
                RequestDelegate? requestDelegate,
                EndpointMetadataCollection? metadata,
                string? displayName)
            {
                // All are allowed to be null
                RequestDelegate = requestDelegate;
                Metadata = metadata ?? EndpointMetadataCollection.Empty;
                DisplayName = displayName;
            }
    
            /// 
            /// Gets the informational display name of this endpoint.
            /// 
            public string? DisplayName { get; }
    
            /// 
            /// Gets the collection of metadata associated with this endpoint.
            /// 
            public EndpointMetadataCollection Metadata { get; }
    
            /// 
            /// Gets the delegate used to process requests for the endpoint.
            /// 
            public RequestDelegate? RequestDelegate { get; }
    
            /// 
            /// Returns a string representation of the endpoint.
            /// 
            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类的两个方法:

       public static class EndpointRoutingApplicationBuilderExtensions
        {
            private const string EndpointRouteBuilder = "__EndpointRouteBuilder";
            private const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";
    
            /// 
            /// Adds a  middleware to the specified .
            /// 
            /// The  to add the middleware to.
            /// A reference to this instance after the operation has completed.
            /// 
            /// 
            /// A call to  must be followed by a call to
            ///  for the same 
            /// instance.
            /// 
            /// 
            /// The  defines a point in the middleware pipeline where routing decisions are
            /// made, and an  is associated with the . The 
            /// defines a point in the middleware pipeline where the current  is executed. Middleware between
            /// the  and  may observe or change the
            ///  associated with the .
            /// 
            /// 
            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(endpointRouteBuilder);
            }
    
            /// 
            /// Adds a  middleware to the specified 
            /// with the  instances built from configured .
            /// The  will execute the  associated with the current
            /// request.
            /// 
            /// The  to add the middleware to.
            /// An  to configure the provided .
            /// A reference to this instance after the operation has completed.
            /// 
            /// 
            /// A call to  must be preceded by a call to
            ///  for the same 
            /// instance.
            /// 
            /// 
            /// The  defines a point in the middleware pipeline where routing decisions are
            /// made, and an  is associated with the . The 
            /// defines a point in the middleware pipeline where the current  is executed. Middleware between
            /// the  and  may observe or change the
            ///  associated with the .
            /// 
            /// 
            public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action 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>();
                foreach (var dataSource in endpointRouteBuilder.DataSources)
                {
                    if (!routeOptions.Value.EndpointDataSources.Contains(dataSource))
                    {
                        routeOptions.Value.EndpointDataSources.Add(dataSource);
                    }
                }
    
                //交由EndpointMiddleware处理
                return builder.UseMiddleware();
            }
    
            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的匹配,我们可以看下它的几个方法:

      

    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? _initializationTask;
    
            public EndpointRoutingMiddleware(
                MatcherFactory matcherFactory,
                ILogger 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 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 InitializeAsync()
            {
                var initializationTask = _initializationTask;
                if (initializationTask != null)
                {
                    return initializationTask;
                }
    
                return InitializeCoreAsync();
            }
    
            private Task InitializeCoreAsync()
            {
                var initialization = new TaskCompletionSource(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):

      public Task Invoke(HttpContext httpContext)
            {
                var endpoint = httpContext.GetEndpoint();
                if (endpoint?.RequestDelegate != null)
                {
                    if (!_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
                    {
                        if (endpoint.Metadata.GetMetadata() != null &&
                            !httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
                        {
                            ThrowMissingAuthMiddlewareException(endpoint);
                        }
    
                        if (endpoint.Metadata.GetMetadata() != 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方法。

     

     

      

     

  • 相关阅读:
    为什么要注册软件著作权
    2022牛客多校(四)
    MyBatis-Plus(二)- 进阶使用
    数据库(二)
    短视频seo矩阵系统源码开发部署搭建分享(一)
    EasyExcel实现复杂导入 适用于导入1对N类型数据如组合商品,订单和订单明细等等
    数据结构与算法:配对堆
    《Java基础入门第2版》--黑马程序员 课后答案及其详解 第1章 Java开发入门
    Spring实例化源码解析之ComponentScanAnnotationParser(四)
    计算机毕业设计django基于python仓库管理系统(源码+系统+mysql数据库+Lw文档)
  • 原文地址:https://www.cnblogs.com/wangsanfeng/p/16567144.html