You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core全局ActionFilter跳转维护页无限重定向问题及方案咨询

问题1 解决多次重定向问题

你遇到的无限重定向核心是两个原因:

  1. 全局注册的ActionFilter会拦截所有MVC请求,包括你跳转到的Maintenance控制器本身的请求
  2. 默认路由规则下,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 03:24:03