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;
}
}
}
Simply attribute/annotate controller class or methods using [TypeFilter(typeof(UnhandledExceptionFilter))]
.
- https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0#exception-filters
- https://medium.com/@jelleverheyen/automatically-handle-exceptions-in-dotnet-core-api-2090d2e574dd
- https://www.thecodebuzz.com/exception-filters-in-net-core
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.