Skip to content

Instantly share code, notes, and snippets.

@moosetraveller
Last active March 11, 2021 18:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save moosetraveller/a16388a80ce205eeebd6fc6b492759fa to your computer and use it in GitHub Desktop.
Save moosetraveller/a16388a80ce205eeebd6fc6b492759fa to your computer and use it in GitHub Desktop.
UnhandledExceptionFilter

Example of an Exception Filter

using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace ApplicationX
{
    /// <summary>
    /// Deals with unhandled exceptions and logs them.
    /// </summary>
    public class UnhandledExceptionFilter : ExceptionFilterAttribute, IAsyncExceptionFilter
    {
        private readonly ILogger<UnhandledExceptionFilter> _logger;
        private readonly IWebHostEnvironment _environment;

        public UnhandledExceptionFilter(ILogger<UnhandledExceptionFilter> logger, IWebHostEnvironment environment)
        {
            _logger = logger;
            _environment = environment;
        }

        private ProblemDetails GetProblemDetails(Exception exception)
        {
            var title = "Internal Server Error";
            var status = (int)HttpStatusCode.InternalServerError;
            var message = $"An internal server error occured: {exception.Message}";

            if (exception is ArgumentNullException) 
            {
                // We could deal with specifc exceptions. 
                // However, I would refrain from doing so. Specific exception can still be
                // caught and dealt with in the controller methods.
                title = "Invalid Request Parameter";
            }

            var details = new ProblemDetails() // see RFC7807
            {
                Status = status,
                Title = title,
                Detail = message,
            };

            // Stacktrace should not be returned to user unless in development 
            // environment because of the risk of exposing PII and vulnerable
            // application internal information.
            // Alternative: check for IdentityModelEventSource.ShowPII.
            if (_environment.IsDevelopment())
            {
                details.Extensions.Add("exception", exception.ToString());
            }

            return details;
        }

        public async override Task OnExceptionAsync(ExceptionContext context)
        {
            var details = GetProblemDetails(context.Exception);

            context.HttpContext.Response.ContentType = "application/problem+json"; // see RFC7807
            context.HttpContext.Response.StatusCode = details.Status ?? (int)HttpStatusCode.InternalServerError;

            _logger.LogError(context.Exception, $"Unhandled {nameof(context.Exception)}");

            context.Result = new JsonResult(details);
            await Task.CompletedTask;
        }
    }
}

Using Filter

Simply attribute/annotate controller class or methods using [TypeFilter(typeof(UnhandledExceptionFilter))].

Source and Further Reading

Alternative: Using a Middleware

According to the Microsoft Documentation, middleware should be prefered if the error handling does not need to differ between which action method was called.

Prefer middleware for exception handling. Use exception filters only where error handling differs based on which action method is called. For example, an app might have action methods for both API endpoints and for views/HTML. The API endpoints could return error information as JSON, while the view-based actions could return an error page as HTML.

Source and Further Reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment