如何在ASP.NET Core Web API中为现有控制器动作路由附加自定义中间件?
如何将自定义中间件附加到已有的ASP.NET Core控制器动作路由
你遇到的问题其实很典型:用Map()手动创建的路由和控制器自带的路由是完全独立的端点条目,当请求/weatherforecast时,ASP.NET Core的路由系统会优先匹配控制器注册的端点,而不是你手动Map的那个,所以你的中间件根本没被执行到。
下面给你几个不用硬编码if判断的可行方案,按灵活性排序:
方案1:用自定义特性+条件中间件(最推荐)
这个方法通过给目标动作标记特性,让中间件自动识别并应用,完全不需要硬编码路由路径:
步骤1:定义一个标记特性
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class ApplyCustomMiddlewareAttribute : Attribute, IEndpointMetadata { // 空特性,仅用于标记元数据 }
步骤2:给控制器动作添加标记
[ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { // 给这个动作标记需要应用中间件 [ApplyCustomMiddleware] [HttpGet] public IEnumerable<WeatherForecast> Get() { // 原动作逻辑 } }
步骤3:在Configure中添加条件中间件
用UseWhen()方法,检查请求匹配的端点是否带有我们的标记特性,再应用中间件:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ...其他前置中间件(比如异常处理、静态文件) // 针对带有标记特性的端点应用自定义中间件 app.UseWhen(context => { var endpoint = context.GetEndpoint(); // 判断当前端点是否有我们的标记特性 return endpoint?.Metadata.Any(m => m is ApplyCustomMiddlewareAttribute) == true; }, appBuilder => { appBuilder.UseMiddleware<MyMiddleware>(); appBuilder.UseMiddleware<VersionMiddleware>(); }); app.UseRouting(); // ...其他中间件(比如授权) app.UseEndpoints(endpoints => { endpoints.MapControllers(); // 正常注册控制器路由 }); }
这个方案的优势是灵活可控:你可以给单个动作、整个控制器甚至全局添加标记,后续修改路由也不用同步调整中间件配置。
方案2:基于请求路径的管道分支
如果你的路由路径是固定的,可以用MapWhen()直接匹配路径,然后在分支管道里应用中间件,再继续执行控制器逻辑:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ...其他中间件 // 匹配/weatherforecast路径的请求,进入这个分支管道 app.MapWhen(context => context.Request.Path.StartsWithSegments("/weatherforecast"), appBuilder => { // 应用自定义中间件 appBuilder.UseMiddleware<MyMiddleware>(); appBuilder.UseMiddleware<VersionMiddleware>(); // 必须重新添加路由和端点注册,让请求能走到控制器 appBuilder.UseRouting(); appBuilder.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }); // 处理其他路径的请求 app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
这个方案适合路由固定的场景,但如果后续路由修改,你需要同步更新这里的路径判断。
方案3:直接修改控制器端点的请求管道
你可以在注册控制器路由时,通过端点约定直接给特定动作的管道插入中间件:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ...其他中间件 app.UseRouting(); // ...其他中间件 app.UseEndpoints(endpoints => { endpoints.MapControllers() .Add(endpointBuilder => { // 获取当前端点的控制器动作元数据 var actionDescriptor = endpointBuilder.Metadata.GetMetadata<ControllerActionDescriptor>(); if (actionDescriptor != null && actionDescriptor.ControllerName == "WeatherForecast" && actionDescriptor.ActionName == "Get") { // 创建包含自定义中间件的管道 var appBuilder = endpoints.CreateApplicationBuilder(); appBuilder.UseMiddleware<MyMiddleware>(); appBuilder.UseMiddleware<VersionMiddleware>(); // 把原控制器动作的请求委托和新中间件合并 var originalDelegate = endpointBuilder.RequestDelegate; endpointBuilder.RequestDelegate = appBuilder.Build().Use(originalDelegate).Invoke; } }); }); }
这个方案直接修改了控制器端点的内部管道,不需要额外的分支,但需要硬编码控制器和动作名称,灵活性稍差。
为什么你之前的方法没生效?
endpoints.Map("/weatherforecast", ...)是新建了一个独立的端点,而控制器的/weatherforecast是由MapControllers()注册的另一个端点。当请求过来时,路由系统会匹配到控制器的端点(因为它的元数据更明确,属于控制器路由范畴),所以你新建的端点根本不会被命中,中间件自然不会执行。
内容的提问来源于stack exchange,提问作者Royi Namir
相关产品推荐
相关产品推荐

