mykeels.com

How I handle Exceptions in ASP.NET

This is my preferred method of handling errors in ASP.NET projects. Perhaps, you’ll find it useful, or maybe you’ll find holes in it and…

How I handle Exceptions in ASP.NET

This is my preferred method of handling errors in ASP.NET projects. Perhaps, you’ll find it useful, or maybe you’ll find holes in it and poke at it in the comments, or improve it in your own projects.

I try to not have try-catch statements in controllers, preferring to use a GlobalExceptionFilter or whichever construct is most appropriate for the version of .NET I am using.

I have annotated the GlobalExceptionFilter code below, providing explanations for the choices I made.

using System.Net;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Foo.Filters;

public class GlobalExceptionFilter : IActionFilter, IOrderedFilter
{
  public int Order => int.MaxValue - 10; // Ensure it runs last in the Filter pipeline
  
  public void OnActionExecuted(ActionExecutedContext context)
    {
        if (context.Exception is null) return;

        var exception = context.Exception;

        int statusCode = context.GetHttpStatusCode(
          new Dictionary<Type, HttpStatusCode>() // Provide a mapping of Exception Type to HttpStatusCode
          {
              { typeof(NotImplementedException), HttpStatusCode.NotImplemented },
              { typeof(TimeoutException), HttpStatusCode.RequestTimeout },
              { typeof(JwtException), HttpStatusCode.Unauthorized },
              { typeof(UnauthorizedAccessException), HttpStatusCode.Unauthorized },
              { typeof(System.ComponentModel.DataAnnotations.ValidationException), HttpStatusCode.BadRequest },
              { typeof(FluentValidation.ValidationException), HttpStatusCode.BadRequest },
          }
        ) ?? HttpStatusCode.InternalServerError; // fallback to 500 internal server error

        var telemetry = context.HttpContext.RequestServices.GetService(typeof(TelemetryClient)) as TelemetryClient;
        var operationId = telemetry?.Context.Operation.Id ?? Guid.NewGuid().ToString();
        var logger = context.HttpContext.RequestServices.GetService(typeof(ILogger)) as ILogger;
        logger?.Error(exception, new
        {
            // Provides a predictable, searchable message in the Logs. 
            // The original Exception's Message will still be avaiilable as Exception.Message in the Logs.
            Message = $"Error in {context.GetControllerName()}.{context.GetControllerActionName()}",
            StatusCode = statusCode,
            Request = new {
              Controller = context.GetControllerName(),
              Action = context.GetControllerActionName(),
              context.HttpContext.Request.Path,
              context.HttpContext.Request.Method
            },
            Application = Assembly.GetEntryAssembly().GetName().FullName,
            HostedIP = System.Environment.MachineName,
            Auth = new
            {
                Scheme = context.HttpContext.Items["AuthenticationScheme"],
            },
            // So we can search the logs by the OperationId to find out why the request failed.
            OperationId = operationId
        });
        
        // Include the OperationId in the response Headers, so we can use it to lookup the exception in the logs
        context.HttpContext.Response.Headers.Add("X-Operation-Id", operationId);
        
        context.Result = new ObjectResult(
          new ErrorResponse()
          {
            // Include actual error details in non-production environments
            Errors = _configuration.ShouldIncludeExceptionDetails
              ? new List<string>
                {
                    exception.Message,
                    exception.StackTrace
                }
              : new List<string> { "Exception Occured. Please contact administrator" }
          })
        {
            StatusCode = Convert.ToInt32(statusCode)
        };

        context.ExceptionHandled = true;
    }
}

GlobalExceptionFilter.cs view raw

Related Articles

Tags