Search code examples
error-handlingasp.net-core-mvcasp.net-core-middleware

How to fix the problems in my global error handling page?


I am working on an ASP.NET Core 6 MVC project and trying to implement global error handling for web pages.

Currently, the code is local on my laptop with Windows 11, Visual Studio 2022 and using Micorosoft.Extentions.Logging.

I am trying to replace the default error page with a customized page that has additional info, such as Transaction ID, Span ID, etc.

I created a middleware to capture error related info and plan to use it for customized error page. But the customized error page is never displayed. I need help to figure out what I did wrong and how to fix it.

The following are the code segments I added to the ASP.NET Core 6 MVC app.

  1. Modified the existing ErrorViewModel class:
public class ErrorViewModel
{
    public string? RequestId { get; set; }
    public Activity? CurrentActivity { get; set; }
    public string TraceId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
  1. Created the ErrorHandlingMiddleware middleware (see code below). It captures the error related info and logs the info. At the end, I was trying to redirect to the customized error.cshtml page using httpContext.Response.Redirect.
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 msg = ex.Message.ToString();
                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");                
            }
        }
    }
}
  1. In program.cs file, I registered the middleware with 2 statements:
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();
  1. I modified the error.cshtml page:
@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>
}
  1. To test the error page, I added an action in HomeController like this:
public IActionResult TriggerError()
{
    int valueA = 7;
    int valueB = 0;
    ViewData["result"] = valueA / valueB;

    return View();
}

When running the test, the middleware piece worked and error info was logged.

But, the error.cshtml page was not displayed. The screen shows the serialized json string as following

{"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}

Solution

  • It was because you've called

    httpContext.Response.ContentType = "application/json";
    httpContext.Response.WriteAsync(result);
    

    try remove it and add

    var url = String.Format("/Home/Error?Err={0}", result);
    context.Response.Redirect(url);
    

    In ErrorController:

    public IActionResult Error(string? Err)
        {
            if(!String.IsNullOrEmpty(Err))
            {
                var errmodel = JsonSerializer.Deserialize<ErrorViewModel>(Err);
                return View(errmodel);
            }
            .....
        }