.NET Core全局ActionFilter跳转维护页无限重定向问题及方案咨询
问题1 解决多次重定向问题
你遇到的无限重定向核心是两个原因:
- 全局注册的ActionFilter会拦截所有MVC请求,包括你跳转到的Maintenance控制器本身的请求
- 默认路由规则下,Maintenance控制器Index动作的实际访问路径是
/Maintenance/Index,你的判断逻辑只排除了/Maintenance,跳转后的请求依然满足重定向条件,就会触发循环。
调整后的Filter代码如下,除了排除维护相关请求,还额外排除了静态资源请求,避免维护页面的样式、脚本资源被拦截导致页面加载异常:
public override void OnActionExecuting(ActionExecutingContext filterContext) { if (filterContext == null) return; var isMaintenanceOn = bool.Parse(_config.GetSection("Maintenance:Mode").Value); if (!isMaintenanceOn) { base.OnActionExecuting(filterContext); return; } // 从配置读取需要排除的路径,避免硬编码,适配你的场景 var excludePaths = _config.GetSection("Maintenance:ExcludePaths").Get<string[]>(); var currentPath = filterContext.HttpContext.Request.Path.Value ?? string.Empty; if (excludePaths.Any(p => currentPath.StartsWith(p, StringComparison.OrdinalIgnoreCase))) { base.OnActionExecuting(filterContext); return; } // 排除静态资源请求 var staticExts = new[] { ".js", ".css", ".png", ".jpg", ".ico", ".svg", ".woff", ".ttf" }; if (staticExts.Any(ext => currentPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) { base.OnActionExecuting(filterContext); return; } filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Maintenance", action = "Index" })); }
你只需要在配置文件里添加对应的排除路径即可,示例配置:
"Maintenance": { "Mode": true, "ExcludePaths": [ "/Maintenance", "/healthcheck" ] }
问题2 更合适的实现方案
维护模式拦截属于全管道的全局逻辑,用中间件实现确实比ActionFilter更合适,中间件的执行时机更早,不需要经过MVC管道,性能更高,也可以直接渲染带动态内容的Razor视图,不需要跳转。
中间件实现代码
public class MaintenanceMiddleware { private readonly RequestDelegate _next; private readonly IConfiguration _config; private readonly IRazorViewEngine _viewEngine; private readonly ITempDataProvider _tempDataProvider; public MaintenanceMiddleware(RequestDelegate next, IConfiguration config, IRazorViewEngine viewEngine, ITempDataProvider tempDataProvider) { _next = next; _config = config; _viewEngine = viewEngine; _tempDataProvider = tempDataProvider; } public async Task InvokeAsync(HttpContext context) { var isMaintenanceOn = bool.Parse(_config.GetSection("Maintenance:Mode").Value); if (!isMaintenanceOn) { await _next(context); return; } // 排除路径和静态资源的逻辑和上面Filter一致 var excludePaths = _config.GetSection("Maintenance:ExcludePaths").Get<string[]>(); var currentPath = context.Request.Path.Value ?? string.Empty; if (excludePaths.Any(p => currentPath.StartsWith(p, StringComparison.OrdinalIgnoreCase))) { await _next(context); return; } var staticExts = new[] { ".js", ".css", ".png", ".jpg", ".ico", ".svg", ".woff", ".ttf" }; if (staticExts.Any(ext => currentPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) { await _next(context); return; } // 直接渲染Razor视图返回,不需要跳转,用户地址栏不变,体验更好 context.Response.StatusCode = 503; // 返回503服务不可用状态码,对SEO更友好 context.Response.ContentType = "text/html; charset=utf-8"; var actionContext = new ActionContext(context, context.GetRouteData(), new ActionDescriptor()); // 指定你要渲染的维护页视图路径 var viewResult = _viewEngine.FindView(actionContext, "~/Views/Maintenance/Index.cshtml", false); if (!viewResult.Success) { await context.Response.WriteAsync("系统维护中,请稍后再试"); return; } // 给视图传动态参数,比如维护结束时间、公告内容等 var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = new { Notice = _config.GetValue<string>("Maintenance:Notice"), EndTime = _config.GetValue<DateTime?>("Maintenance:EndTime") } }; using var sw = new StringWriter(); var viewContext = new ViewContext( actionContext, viewResult.View, viewData, new TempDataDictionary(context, _tempDataProvider), sw, new HtmlHelperOptions() ); await viewResult.View.RenderAsync(viewContext); await context.Response.WriteAsync(sw.ToString()); } }
注册中间件
在Startup.cs的Configure方法最开头注册这个中间件,保证所有请求优先经过维护模式校验:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 第一个注册维护中间件 app.UseMiddleware<MaintenanceMiddleware>(); // 后面再注册其他中间件,比如静态文件、路由、MVC等 app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); }); }
这种方案你甚至不需要维护Maintenance控制器,视图里可以正常使用所有Razor语法读取你传入的动态参数。
内容的提问来源于stack exchange,提问作者Viswa
相关产品推荐
相关产品推荐

