.NET CORE 终端路由中间件 app.UseEndpoints

发布时间 2023-08-28 10:49:47作者: 不争丶


public void ConfigureServices(IServiceCollection services)
{
        services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
              endpoints.MapControllers();
        });
}

一、app.UseRouting()

该中间件会检查应用中定义的终结点列表,然后通过匹配 URL 和 HTTP 方法来选择最佳的终结点
简单说,该中间件的作用是根据一定规则来选择出终结点
1、源码分析
            public static IApplicationBuilder UseRouting(this IApplicationBuilder builder)
            {
                //判空
                ArgumentNullException.ThrowIfNull(builder);

                //判断RoutingMarkerService 确保是否添加了所有服务
                VerifyRoutingServicesAreRegistered(builder);

                //VerifyRoutingServicesAreRegistered 内部
                //// 必须先执行了 AddRouting
                //if (app.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
                //{
                //    throw new InvalidOperationException(Resources.FormatUnableToFindServices(
                //        nameof(IServiceCollection),
                //        nameof(RoutingServiceCollectionExtensions.AddRouting),
                //        "ConfigureServices(...)"));
                //}

                //初始化数据源
                if (builder.Properties.TryGetValue(GlobalEndpointRouteBuilderKey, out var obj))
                {
                    endpointRouteBuilder = (IEndpointRouteBuilder)obj!;
                    //知道在设置全局路由构建器时是否调用UseRouting()
                    builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;
                }
                else
                {
                    endpointRouteBuilder = new DefaultEndpointRouteBuilder(builder);

                    //EndpointRouteBuilder为字符串常量 __EndpointRouteBuilder
                    //// builder.Properties["__EndpointRouteBuilder"] = endpointRouteBuilder;
                    //// 将 endpointRouteBuilder 放入共享字典中
                    builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;
                }

                //DefaultEndpointRouteBuilder构造类内部
                //public DefaultEndpointRouteBuilder(IApplicationBuilder applicationBuilder)
                //{
                //    ApplicationBuilder = applicationBuilder ?? throw new ArgumentNullException(nameof(applicationBuilder));
                //    DataSources = new List<EndpointDataSource>();
                //}

                // Add UseRouting function to properties so that middleware that can't reference UseRouting directly can call UseRouting via this property
                // This is part of the global endpoint route builder concept


                builder.Properties.TryAdd(UseRoutingKey, (object)UseRouting);

                //操作完成后对此实例引用
                return builder.UseMiddleware<EndpointRoutingMiddleware>(endpointRouteBuilder);
            }
2、EndpointRoutingMiddleware 中间件
EndpointRoutingMiddleware中间件先是创建matcher
然后调用matcher.MatchAsync(httpContext)去寻找Endpoint,
最后通过httpContext.GetEndpoint()验证了是否已经匹配到了正确的Endpoint并交个下个中间件继续执行!

核心就是将_endpointDataSource传递给_matcherFactory,创建matcher然后进行匹配matcher.MatchAsync(httpContext)
ASP.NET Core默认使用的 matcher 类型是DfaMatcher,DFA(Deterministic Finite Automaton)是一种被称为“确定有限状态自动机”的算法,可以从候选终结点列表中查找到匹配度最高的那个终结点。



二、app.UseAuthorization()

暂时不看

三、app.UseEndpoints()

可以向该中间件的终结点列表中添加终结点,并配置这些终结点要执行的委托,该中间件会负责运行由EndpointRoutingMiddleware中间件选择的终结点所关联的委托。
简单说,该中间件用来执行所选择的终结点委托
1、源码分析

            public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure)
            {
                ArgumentNullException.ThrowIfNull(builder);
                ArgumentNullException.ThrowIfNull(configure);

                //所以在调用UseEndpoint之前 必须要先调用UseRouting,否则报错。
                //验证路由服务是否注册了
                VerifyRoutingServicesAreRegistered(builder);

                //验证端点路由中间件是否已注册
                // 将 endpointRouteBuilder 从共享字典中取出来,如果没有,则说明之前没有调用 UseRouting
                VerifyEndpointRoutingMiddlewareIsRegistered(builder, out var endpointRouteBuilder);

                //VerifyEndpointRoutingMiddlewareIsRegistered方法内部
                //private static void VerifyEndpointRoutingMiddlewareIsRegistered(IApplicationBuilder app, out DefaultEndpointRouteBuilder endpointRouteBuilder)
                //{
                //    if (!app.Properties.TryGetValue(EndpointRouteBuilder, out var obj))
                //    {
                //        var message =
                //            $"{nameof(EndpointRoutingMiddleware)} matches endpoints setup by {nameof(EndpointMiddleware)} and so must be added to the request " +
                //            $"execution pipeline before {nameof(EndpointMiddleware)}. " +
                //            $"Please add {nameof(EndpointRoutingMiddleware)} by calling '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' inside the call " +
                //            $"to 'Configure(...)' in the application startup code.";
                //        throw new InvalidOperationException(message);
                //    }

                //    //如果此处转换失败,程序会出错。
                //    endpointRouteBuilder = (DefaultEndpointRouteBuilder)obj;

                //    
                //    //此检查处理在两者之间调用Map或其他分叉管道的情况
                //    if (!object.ReferenceEquals(app, endpointRouteBuilder.ApplicationBuilder))
                //    {
                //        var message =
                //            $"The {nameof(EndpointRoutingMiddleware)} and {nameof(EndpointMiddleware)} must be added to the same {nameof(IApplicationBuilder)} instance. " +
                //            $"To use Endpoint Routing with 'Map(...)', make sure to call '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' before " +
                //            $"'{nameof(IApplicationBuilder)}.{nameof(UseEndpoints)}' for each branch of the middleware pipeline.";
                //        throw new InvalidOperationException(message);
                //    }
                //}


                configure(endpointRouteBuilder);


                //我们正在将数据源注册到一个全局集合中
                //可用于发现端点或生成URL。
                //每个中间件都有自己的数据源集合,所有这些数据源也
                //被添加到全局集合中。
                //这里就是填充RouteOptions的EndpointDataSources了。
                //UseRouting 和 UseEndpoints 必须添加到同一个 IApplicationBuilder 实例上
                var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
                foreach (var dataSource in endpointRouteBuilder.DataSources)
                {
                    if (!routeOptions.Value.EndpointDataSources.Contains(dataSource))
                    {
                        routeOptions.Value.EndpointDataSources.Add(dataSource);
                    }
                }

                //操作完成后对此实例引用
                return builder.UseMiddleware<EndpointMiddleware>();
            }
2、EndpointMiddleware中间件    
不理解 
就是执行由EndpointRoutingMiddleware中间件附加到当前HttpContext上下文中的终结点。EndpointRoutingMiddleware中间件针对终结点的执行涉及如下所示的RouteOptions类型标识的配置选项。
1、EndpointMiddleware 初始化的时候注入了routeOptions。
2、当路由匹配到了终结点时,EndpointMiddleware则是该路由的终端中间件

四、三者关系

UseRouing 中间件主要是路由匹配,找到匹配的终结者路由Endpoint ;
UseEndpoints 中间件主要针对UseRouting 中间件匹配到的路由进行 委托方法的执行等操作。
 UseAuthorization 中间件主要针对 UseRouting 中间件中匹配到的路由进行拦截 做授权验证操作等,通过则执行下一个中间件UseEndpoints(),
具体的关系可以看下面的流程图:
在调用UseRouting之前,你可以注册一些用于修改路由操作的数据,比如UseRewriterUseHttpMethodOverrideUsePathBase等。
在调用UseRoutingUseEndpoints之间,可以注册一些用于提前处理路由结果的中间件,如UseAuthenticationUseAuthorizationUseCors等。



五、UseEndpoints 配置

MapControllers              添加了对属性路由的控制器支持
MapAreaControllerRoute  将控制器的传统路由添加区域
MapControllerRoute         添加控制器的常规路由
                endpoints.MapAreaControllerRoute(
                    name: "ApiArea", "API",
                    pattern: "API/{controller=Test}/{action=Index}/{id?}"
                    );
                endpoints.MapAreaControllerRoute(
                    name: "UapiArea", "UAPI",
                    pattern: "UAPI/{controller=Test}/{action=Index}/{id?}"
                    );
                endpoints.MapAreaControllerRoute(
                    name: "WebArea", "Web",
                    pattern: "Web/{controller=Test}/{action=Index}/{id?}"
                    );

                endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
区域的需要在控制器上加
    [Area("API")]
    [Route("/API/[controller]/[action]")]




ASP.NET Core路由中间件[4]: EndpointRoutingMiddleware和EndpointMiddleware - Artech - 博客园 (cnblogs.com)
https://www.cnblogs.com/artech/p/endpoint-middleware-04.html

重新整理 .net core 实践篇——— UseEndpoints中间件[四十八] - 敖毛毛 - 博客园 (cnblogs.com)
https://www.cnblogs.com/aoximin/p/15649807.html

理解ASP.NET Core - 路由(Routing) - xiaoxiaotank - 博客园 (cnblogs.com)
https://www.cnblogs.com/xiaoxiaotank/p/15468491.html

ASP.NET Core框架揭秘[博文汇总-持续更新] - Artech - 博客园 (cnblogs.com)
https://www.cnblogs.com/artech/p/inside-asp-net-core-3.html