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.
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);
}
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");
}
}
}
}
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();
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>
}
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}
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);
}
.....
}