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

ASP.NET Core中如何通过StatusCodePagesWithRedirects传递错误详情

解决ASP.NET Core中带内容的错误无法触发UseStatusCodePagesWithRedirects的问题

你碰到的这个情况完全是UseStatusCodePagesWithRedirects的设计导致的——正如你查到的文档和源码,它只对没有响应体的400-599状态码响应生效。当你返回BadRequestObjectResult这类带错误内容的结果时,响应体已经被填充,中间件就会直接跳过处理,把内容原样返回给客户端了。

要实现「返回带额外信息的错误,同时触发统一错误页面展示」的需求,这里有几个实用的方案,既能集中管理错误逻辑,又不用让业务控制器耦合太多错误处理代码:

方案1:使用UseStatusCodePagesWithReExecute替代重定向

UseStatusCodePagesWithReExecute是服务器端的重执行逻辑(不会改变客户端URL),它比重定向更适合传递错误上下文信息。我们可以配合一点自定义逻辑来捕获响应体中的错误内容:

步骤1:替换中间件配置

把原来的UseStatusCodePagesWithRedirects换成UseStatusCodePagesWithReExecute,并添加一个自定义中间件来拦截错误内容:

// Startup.cs 或 Program.cs
app.UseStatusCodePagesWithReExecute("/Error/{0}");

// 添加自定义中间件,用于捕获带内容的错误响应
app.Use(async (context, next) =>
{
    await next();

    // 只处理400-599的错误,且响应有内容的情况
    if (context.Response.StatusCode >= 400 && context.Response.StatusCode < 600 && context.Response.ContentLength > 0)
    {
        // 先保存当前响应的内容
        context.Response.Body.Seek(0, SeekOrigin.Begin);
        var errorContent = await new StreamReader(context.Response.Body).ReadToEndAsync();
        context.Response.Body.Seek(0, SeekOrigin.Begin);

        // 把错误内容存入HttpContext.Items,供错误页面读取
        context.Items["ErrorDetails"] = errorContent;
    }
});

步骤2:修改错误控制器

更新错误控制器的Action,从HttpContext.Items中读取错误信息,传递给视图:

[Route("Error/{statusCode?}")]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error(int? statusCode)
{
    var viewModel = new ErrorViewModel 
    { 
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
        StatusCode = statusCode ?? HttpContext.Response.StatusCode
    };

    // 读取错误详情
    if (HttpContext.Items.TryGetValue("ErrorDetails", out var errorDetails))
    {
        viewModel.ErrorDetails = errorDetails.ToString();
        // 可以在这里把JSON字符串反序列化成强类型对象,方便视图展示
        // viewModel.Errors = JsonSerializer.Deserialize<ErrorModel>(errorDetails.ToString());
    }

    return View(viewModel);
}

优点:

  • 服务器端重执行,URL保持不变,用户体验更流畅
  • 可以无缝捕获带响应体的错误,无需修改业务控制器的返回逻辑

方案2:全局Action过滤器处理错误结果

如果你的应用是纯MVC场景,可以写一个全局Action过滤器,在结果执行前拦截带内容的错误结果,把信息传递给错误页面:

步骤1:实现自定义过滤器

public class ErrorHandlingFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context) { }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // 只处理400-599的ObjectResult(带内容的错误)
        if (context.Result is ObjectResult objectResult && 
            objectResult.StatusCode >= 400 && 
            objectResult.StatusCode < 600)
        {
            // 把错误信息存入TempData
            context.HttpContext.TempData["ErrorDetails"] = JsonSerializer.Serialize(objectResult.Value);
            context.HttpContext.TempData["StatusCode"] = objectResult.StatusCode;

            // 替换结果为重定向到错误页面
            context.Result = new RedirectToActionResult("Error", "Home", null);
        }
    }
}

步骤2:注册全局过滤器

在Startup/Program中注册这个过滤器:

// Startup.cs
services.AddControllersWithViews(options =>
{
    options.Filters.Add<ErrorHandlingFilter>();
});

// 或者 Program.cs
builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<ErrorHandlingFilter>();
});

步骤3:修改错误控制器读取TempData

[Route("Error")]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
    var viewModel = new ErrorViewModel 
    { 
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
    };

    if (TempData.TryGetValue("StatusCode", out var statusCode))
    {
        viewModel.StatusCode = int.Parse(statusCode.ToString());
    }

    if (TempData.TryGetValue("ErrorDetails", out var errorDetails))
    {
        viewModel.ErrorDetails = errorDetails.ToString();
        // 反序列化为强类型对象
        // viewModel.Errors = JsonSerializer.Deserialize<ErrorModel>(errorDetails.ToString());
    }

    return View(viewModel);
}

优点:

  • 完全在MVC管道内处理,逻辑更贴合MVC场景
  • 业务控制器无需修改任何代码,直接返回带内容的错误即可

方案3:自定义错误中间件(最灵活)

如果需要更细粒度的控制,可以完全自定义一个错误处理中间件,接管所有400-599的响应,不管有没有响应体:

public class CustomErrorMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<CustomErrorMiddleware> _logger;

    public CustomErrorMiddleware(RequestDelegate next, ILogger<CustomErrorMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);

            // 处理400-599的错误
            if (context.Response.StatusCode >= 400 && context.Response.StatusCode < 600)
            {
                await HandleErrorAsync(context);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An unhandled exception occurred.");
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            await HandleErrorAsync(context, ex);
        }
    }

    private async Task HandleErrorAsync(HttpContext context, Exception ex = null)
    {
        // 保存原始响应信息
        var originalStatusCode = context.Response.StatusCode;
        var originalContent = string.Empty;

        if (context.Response.ContentLength > 0)
        {
            context.Response.Body.Seek(0, SeekOrigin.Begin);
            originalContent = await new StreamReader(context.Response.Body).ReadToEndAsync();
        }

        // 重置响应,准备返回错误页面
        context.Response.Clear();
        context.Response.ContentType = "text/html";

        // 把错误信息存入HttpContext.Items
        context.Items["OriginalStatusCode"] = originalStatusCode;
        context.Items["OriginalErrorContent"] = originalContent;
        context.Items["Exception"] = ex;

        // 重执行错误页面的Action
        var routeData = new RouteData();
        routeData.Values.Add("controller", "Home");
        routeData.Values.Add("action", "Error");

        var actionContext = new ActionContext(context, routeData, new ControllerActionDescriptor());
        var errorController = new HomeController(); // 或者用依赖注入获取控制器实例
        await errorController.Error().ExecuteResultAsync(actionContext);
    }
}

// 注册中间件的扩展方法
public static class CustomErrorMiddlewareExtensions
{
    public static IApplicationBuilder UseCustomErrorHandling(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<CustomErrorMiddleware>();
    }
}

然后在Program/Startup中注册:

app.UseCustomErrorHandling();

优点:

  • 完全掌控错误处理流程,支持捕获未处理异常和已返回的错误响应
  • 可以灵活处理不同类型的请求(比如区分MVC和API请求)

总结

如果是纯MVC应用,**方案2(全局过滤器)**最简洁,无需修改中间件管道;如果需要保留URL不变或者处理API场景,方案1(UseStatusCodePagesWithReExecute+自定义中间件)更合适;如果需要极致的灵活性,就选方案3(自定义中间件)。

内容的提问来源于stack exchange,提问作者Matthew Champion

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:00:30