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

ASP.NET Core 6 MVC自定义全局错误页无法显示求助

问题排查:ASP.NET Core 6 MVC自定义错误页不显示,返回JSON的问题

问题描述

开发ASP.NET Core 6 MVC项目,需实现包含Transaction ID、Span ID等附加信息的全局自定义错误页。已创建ErrorHandlingMiddleware捕获错误信息,但测试时仅返回序列化的JSON字符串,自定义error.cshtml页面未展示,中间件已正常捕获并记录错误。

现有代码片段

1. 修改后的ErrorViewModel类

public class ErrorViewModel
{
    public string? RequestId { get; set; }
    public Activity? CurrentActivity { get; set; }
    public string TraceId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}

2. ErrorHandlingMiddleware中间件

using MVCLogging.Models;
using System.Diagnostics;
using System.Text.Json;

namespace MVCLogging.Middleware
{
    public class ErrorHandlingMiddleware: IMiddleware
    {
        public ErrorViewModel Err;
        public ILogger<ErrorHandlingMiddleware> _logger { get; }

        public ErrorHandlingMiddleware(ILogger<ErrorHandlingMiddleware> logger)
        {
            _logger = logger;
            Err = new();
        }

        public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next)
        {
            try
            {
                await next(httpContext);
            }
            catch(Exception ex)
            {
                Err = new();
                Err.TraceId = httpContext.TraceIdentifier;
                Err.RequestId = Activity.Current?.Id ?? httpContext.TraceIdentifier;
                Err.CurrentActivity = Activity.Current;

                var result = JsonSerializer.Serialize(Err);
                httpContext.Response.ContentType = "application/json";
                httpContext.Response.WriteAsync(result);

                _logger.LogError(result);

                var resp = httpContext.Response;
                resp.Redirect("/Home/Error");                
            }
        }
    }
}

3. Program.cs注册中间件

using MVCLogging.Configurations;
using MVCLogging.Middleware;
using MVCLogging.Models;
using NLog;
using NLog.Web;

// Early init of NLog to allow startup and exception logging, before host is built
var logger = NLog.LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Debug("Nlog init main");

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddTransient<ErrorHandlingMiddleware>();

builder.Services.AddProductSvc();

builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Host.UseNLog();

var app = builder.Build();

//use error handling middleware
app.UseExceptionHandler("/Home/Error");
app.UseHsts();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMiddleware<ErrorHandlingMiddleware>(); 
app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

4. 修改后的error.cshtml页面

@model ErrorViewModel
@{
    ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
    <p>
        <strong>Request ID:</strong> <code>@Model.RequestId</code>
        <br />
        <strong>HTTP TraceId:</strong> <code>@Model.TraceId</code>
        <br />
        <strong>Activity.Id:</strong> <code>@Model.CurrentActivity?.Id</code>
        <br />
        <strong>Activity.SpanId:</strong> <code>@Model.CurrentActivity?.SpanId</code>
        <br />
        <strong>Activity.TraceId:</strong> <code>@Model.CurrentActivity?.TraceId</code>
        <br />
        <strong>Activity.Parent:</strong> <code>@Model.CurrentActivity?.Parent</code>
        <br />
        <strong>Activity.ParentId:</strong> <code>@Model.CurrentActivity?.ParentId</code>
        <br />
        <strong>Activity.ParentSpanId:</strong> <code>@Model.CurrentActivity?.ParentSpanId</code>
        <br />
        <strong>Activity.RootId:</strong> <code>@Model.CurrentActivity?.RootId</code>
        <br />
        <strong>Activity.Kind:</strong> <code>@Model.CurrentActivity?.Kind</code>
    </p>
}

5. HomeController中的测试Action

public IActionResult TriggerError()
{
    int valueA = 7;
    int valueB = 0;
    ViewData["result"] = valueA / valueB;

    return View();
}

测试现象

中间件正常捕获并记录错误信息,但页面仅返回序列化的JSON字符串,未展示自定义错误页:

{"RequestId":"00-ecfcaba10ec348ff8a531c532b4d6983-417e523e0e66c711-00","CurrentActivity":{"Status":0,"StatusDescription":null,"Kind":0,"OperationName":"Microsoft.AspNetCore.Hosting.HttpRequestIn","DisplayName":"Microsoft.AspNetCore.Hosting.HttpRequestIn","Source":{"Name":"","Version":""},"Parent":null,"Duration":"00:00:00","StartTimeUtc":"2023-07-29T14:20:00.1658232Z","Id":"00-ecfcaba10ec348ff8a531c532b4d6983-417e523e0e66c711-00","ParentId":null,"RootId":"ecfcaba10ec348ff8a531c532b4d6983","Tags":[],"TagObjects":[],"Events":[],"Links":[],"Baggage":[],"Context":{"TraceId":{},"SpanId":{},"TraceFlags":0,"TraceState":null,"IsRemote":false},"TraceStateString":null,"SpanId":{},"TraceId":{},"Recorded":false,"IsAllDataRequested":true,"ActivityTraceFlags":0,"ParentSpanId":{},"IdFormat":2},"TraceId":"0HMSG7RGCGSKG:00000007","ShowRequestId":true}

问题原因及修复方案

核心问题

  1. 响应已写入内容,无法重定向:catch块中先调用httpContext.Response.WriteAsync(result)写入JSON,此时HTTP响应已开始输出,后续Redirect操作无法生效。
  2. 中间件顺序错误:UseMiddleware<ErrorHandlingMiddleware>放在UseExceptionHandler之后,导致异常被自定义中间件捕获后,UseExceptionHandler无法再处理。
  3. 错误信息传递无效:直接返回JSON,未将错误数据传递给/Home/Error页面的Model。

修复步骤

步骤1:修正ErrorHandlingMiddleware逻辑

移除JSON写入操作,将错误信息存入HttpContext.Items后重新抛出异常,交给UseExceptionHandler处理:

using MVCLogging.Models;
using System.Diagnostics;

namespace MVCLogging.Middleware
{
    public class ErrorHandlingMiddleware: IMiddleware
    {
        public ILogger<ErrorHandlingMiddleware> _logger { get; }

        public ErrorHandlingMiddleware(ILogger<ErrorHandlingMiddleware> logger)
        {
            _logger = logger;
        }

        public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next)
        {
            try
            {
                await next(httpContext);
            }
            catch(Exception ex)
            {
                var errorModel = new ErrorViewModel
                {
                    TraceId = httpContext.TraceIdentifier,
                    RequestId = Activity.Current?.Id ?? httpContext.TraceIdentifier,
                    CurrentActivity = Activity.Current
                };

                // 将错误模型存入HttpContext,供错误页面读取
                httpContext.Items["ErrorModel"] = errorModel;
                
                // 记录包含异常详情的日志
                _logger.LogError(ex, "未处理异常,RequestId: {RequestId}", errorModel.RequestId);

                // 重新抛出异常,让UseExceptionHandler处理跳转
                throw;
            }
        }
    }
}

步骤2:调整Program.cs中间件顺序

将自定义中间件放在UseExceptionHandler之前,确保异常能被捕获并传递:

var app = builder.Build();

// 先使用自定义中间件捕获异常
app.UseMiddleware<ErrorHandlingMiddleware>(); 
// 再用ExceptionHandler处理异常并跳转错误页
app.UseExceptionHandler("/Home/Error");
app.UseHsts();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

步骤3:修改HomeController的Error Action

从HttpContext.Items读取错误模型并传递给视图:

public IActionResult Error()
{
    var errorModel = HttpContext.Items["ErrorModel"] as ErrorViewModel 
                     ?? new ErrorViewModel 
                     { 
                         RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
                         TraceId = HttpContext.TraceIdentifier
                     };

    return View(errorModel);
}

步骤4:移除无效的Redirect操作

原中间件中的resp.Redirect("/Home/Error")已不需要,UseExceptionHandler会自动跳转至指定错误页。

额外优化建议

  • 提取Activity关键字段:避免直接传递Activity对象,将TraceId、SpanId等需要的字段单独提取到ErrorViewModel,减少不必要的数据传递。
  • 优化日志内容:日志中记录完整异常信息(如堆栈跟踪),而非序列化的模型,便于问题排查。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 01:25:54