ASP.NET Core 中 IApplicationBuilder
不是线程安全的,别在中间件里并发修改它
IApplicationBuilder是构建请求处理管道的“装配线”,只在应用启动时(
Startup.Configure或
Program.cs的
WebApplication配置阶段)被顺序调用。它的
Use、
UseMiddleware、
Map等方法不是为运行时并发调用设计的。 如果在某个中间件内部(比如异步任务中)偷偷调用
app.Use(...),会触发
InvalidOperationException:“Builder is already in use” 或 “The builder has already been built”
IApplicationBuilder内部持有对
RequestDelegate链的引用,且其
Build()方法仅执行一次;重复或并发调用会破坏管道一致性 常见误用场景:在健康检查中间件里动态注册新路由、在灰度中间件里根据 Header 注册临时终结点——这些都该移出
IApplicationBuilder生命周期
IEndpointRouteBuilder
的线程安全性取决于具体实现,但 MapControllers()
等扩展方法不是运行时 API
IEndpointRouteBuilder(如
WebApplication实现的
EndpointRouteBuilder)本身是线程不安全的,但它通常只在应用初始化阶段被使用。关键在于:所有
MapXxx()方法(
MapControllerRoute、
MapRazorPages、
MapHub)都只是往内部路由表注册终结点描述符(
EndpointDataSource),不涉及实时请求分发。 这些注册操作必须在
app.MapXXX()或
endpoints.MapXXX()调用期间完成,不能在
HttpContext.RequestServices解析出的任意服务中调用 若需运行时动态添加路由(例如插件系统),应通过自定义
EndpointDataSource+
IEndpointRouteBuilder的底层机制实现,而非直接调用
Map方法
MapControllers()本质是批量扫描程序集并生成
ControllerActionEndpointConventionBuilder,它不检查并发,但也不允许在
Build()后再调用
真正支持运行时动态路由的是 DynamicRouteValueTransformer
和 EndpointDataSource
如果你需要根据请求上下文(如租户 ID、Header、查询参数)改变路由行为,
IApplicationBuilder和
IEndpointRouteBuilder都不是目标接口。正确路径是: 用
DynamicRouteValueTransformer在匹配后动态改写
RouteValueDictionary,它天然支持异步和并发请求 实现自定义
EndpointDataSource并注入
IServiceCollection,配合后台定时刷新或事件驱动更新终结点集合(注意:更新时需保证
ChangeToken通知消费者) 避免在
Map链中嵌套
app.Run(async ctx => { ... }) 并在里面调用 endpoints.MapGet—— 这会导致
InvalidOperationException: Cannot create child container from a resolved service
public class TenantRouteTransformer : DynamicRouteValueTransformer
{
public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
var tenant = httpContext.Request.Headers["X-Tenant"].FirstOrDefault();
if (!string.IsNullOrEmpty(tenant))
{
values["tenant"] = tenant;
}
return values;
}
}
并发模型的本质:ASP.NET Core 的请求处理是 per-request 的,但配置阶段是单次、顺序、不可重入的
整个框架把“配置”和“执行”严格分离:
IApplicationBuilder和
IEndpointRouteBuilder属于配置期对象,它们的生命周期止于
app.Build();之后所有并发请求都走同一个已构建好的
RequestDelegate链,由
EndpointRoutingMiddleware和
EndpointMiddleware协同完成路由匹配与执行。 不要试图在
HttpContext中拿到
IApplicationBuilder实例 —— 它根本不会被注入到 DI 容器
IEndpointRouteBuilder也不是 Scoped 或 Transient 服务,它只作为
WebApplication的内部属性存在,无法从
HttpContext.RequestServices获取 最易忽略的一点:即使你用
WebHostBuilder手动构建多个
IWebHost,每个实例的
IApplicationBuilder仍只在其自身启动阶段有效,跨实例共享毫无意义
