.NET core middleware that checks whether an exception is one of the well-known type(s), creates and returns an appropriate HTTP status code and performs additional logging about the exception.
// -----------------------------------------------------------------------------
// File: DuplicateKeyException.cs
// Source: https://github.com/drwatson1/AspNet-Core-REST-Service/blob/master/ProjectTemplates/ReferenceProject/Exceptions/DuplicateKeyException.cs
// Article: https://github.com/drwatson1/AspNet-Core-REST-Service/wiki#unhandled-exceptions-handling
// -----------------------------------------------------------------------------
using System;
using System.Runtime.Serialization;
namespace ReferenceProject.Exceptions
{
[Serializable]
internal class DuplicateKeyException : Exception
{
public DuplicateKeyException()
: this("Duplicate key")
{
}
public DuplicateKeyException(string message) : base(message)
{
}
public DuplicateKeyException(string message, Exception innerException) : base(message, innerException)
{
}
protected DuplicateKeyException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
// -----------------------------------------------------------------------------
// File: ExceptionMiddleware.cs
// Source: https://github.com/drwatson1/AspNet-Core-REST-Service/blob/master/ProjectTemplates/ReferenceProject/Middleware/ExceptionMiddleware.cs
// Article: https://github.com/drwatson1/AspNet-Core-REST-Service/wiki#unhandled-exceptions-handling
// -----------------------------------------------------------------------------
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Hosting;
using ReferenceProject.Exceptions;
using Microsoft.Extensions.Hosting;
namespace ReferenceProject.Middleware
{
/// <summary>
/// Middleware to handle exceptions.
/// It separates exceptions based on their type and returns different status codes and answers based on it, instead of 500 Internal Server Error code in all cases.
/// In addition, it writes them in the log.
/// </summary>
/// <remarks>
/// There is another way to do this - an exception filter.
/// However, a middleware is a preferred way to achieve this according to the official documentation.
/// To learn more see https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.1#exception-filters
///
/// See also: https://github.com/drwatson1/AspNet-Core-REST-Service/wiki#unhandled-exceptions-handling
/// </remarks>
public class ExceptionMiddleware
{
RequestDelegate Next { get; }
ILogger Logger { get; }
IHostEnvironment Environment { get; }
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger, IHostEnvironment environment)
{
Environment = environment ?? throw new ArgumentNullException(nameof(environment));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Next = next ?? throw new ArgumentNullException(nameof(next));
}
public async Task InvokeAsync(HttpContext context)
{
var body = context.Response.Body;
try
{
await Next(context);
}
catch (Exception ex)
{
// If context.Response.HasStarted == true, then we can't write to the response stream anymore. So we have to restore the body.
// If we don't do that we get an exception.
context.Response.Body = body;
await HandleExceptionAsync(context, ex);
}
}
async Task HandleExceptionAsync(HttpContext context, Exception ex)
{
int statusCode = 500;
context.Response.ContentType = "application/json";
context.Response.StatusCode = statusCode;
// We can decide what the status code should return
if (ex is KeyNotFoundException)
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
}
else if (ex is DuplicateKeyException)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
}
await context.Response.WriteAsync(
JsonConvert.SerializeObject(
new ErrorResponse(ex, Environment.IsDevelopment())));
if (context.Response.StatusCode == StatusCodes.Status500InternalServerError)
{
Logger.LogError(ex, "Unhandled exception occurred");
}
else
{
Logger.LogDebug(ex, "Unhandled exception occurred");
}
}
class ErrorResponse
{
public ErrorResponse(Exception ex, bool includeFullExceptionInfo)
{
Error = new ExceptionDescription(ex);
if (includeFullExceptionInfo)
{
Error.Exception = ex;
}
}
public ExceptionDescription Error { get; set; }
}
class ExceptionDescription
{
public ExceptionDescription(Exception ex)
{
Message = ex.Message;
}
public string Message { get; set; }
public Exception Exception { get; set; }
}
}
}
// -----------------------------------------------------------------------------
// File: ExceptionMiddlewareExtensions.cs
// Source: https://github.com/drwatson1/AspNet-Core-REST-Service/blob/master/ProjectTemplates/ReferenceProject/Middleware/ExceptionMiddlewareExtensions.cs
// Article: https://github.com/drwatson1/AspNet-Core-REST-Service/wiki#unhandled-exceptions-handling
// -----------------------------------------------------------------------------
using Microsoft.AspNetCore.Builder;
namespace ReferenceProject
{
public static class ExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseExceptionHandler(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<Middleware.ExceptionMiddleware>();
}
}
}