Andrei M Andrei M - 3 months ago 353
C# Question

ASP.NET Core Web API exception handling

I started using ASP.NET Core for my new REST API project after using regular ASP.NET Web API for many years. I don't see a good way to handle exceptions in ASP.NET Core Web API. I tried to implement exception handling filter/attribute:

public class ExceptionFilter : ActionFilterAttribute, IExceptionFilter
{
public async void OnException(ExceptionContext context)
{
if (context.Exception != null)
{
await ResponseExceptionHelper.HandleExceptionAsync(context, context.Exception);
context.ExceptionHandled = true;
}
}
}


If exception occurs I want to return a specific JSON message back to the client. I had problems with disposed response object, I also couldn't catch exceptions that occur in other filters.

This is how I tried to return a response when exception happens:

public static class ResponseExceptionHelper
{
public static async Task HandleExceptionAsync(ActionContext context, Exception exception)
{
if (exception == null) return;

if (exception is MyNotFoundException)
await WriteExceptionAsync(context, exception, HttpStatusCode.NotFound);
else if (exception is MyUnauthorizedException)
await WriteExceptionAsync(context, exception, HttpStatusCode.Unauthorized);
else if (exception is MyException)
await WriteExceptionAsync(context, exception, HttpStatusCode.BadRequest);
else
await WriteExceptionAsync(context, exception, HttpStatusCode.InternalServerError);
}

private static async Task WriteExceptionAsync(ActionContext context, Exception exception, HttpStatusCode code)
{
var response = context.HttpContext.Response;

response.ContentType = "application/json";
response.StatusCode = (int)code;

await response.WriteAsync(JsonHelper.SerializeObject(new
{
Message = exception.Message,
Exception = exception.GetType().Name
}));
}
}


So the main question, what is the right way to handle exceptions in Core Web API?

UPDATE: It works more stable on IIS, though I see issues with DbContext life-cycle. Sometimes it is disposed when I try to use it in authorization filter. DbContext is injecting with standard ASP.NET Core DI.

Answer

Full example of Exception Handling Middleware

After many experiments I can say that middleware is the best for exception handling in ASP.NET Core. I implemented this exception handling middleware class (no need to implement interface or inherit some class. public async Task Invoke(...) method must be present. You can inject dependencies in constructor if needed):

public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate next;

    public ErrorHandlingMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(HttpContext context) // you can inject dependencies here
    {
        try
        {
            await next(context);
        }
        catch (Exception ex)
        {
            await ResponseExceptionHelper.HandleExceptionAsync(context, ex);
        }
    }
}

I registered it before Mvc in Startup class:

app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();

And here is my ResponseExceptionHelper where I modify Response if exception happens:

public static class ResponseExceptionHelper
{
    public static async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        if (exception == null) return;

        if (exception is HereNotFoundException)
            await WriteExceptionAsync(context, exception, HttpStatusCode.NotFound);
        else if (exception is HereUnauthorizedException)
            await WriteExceptionAsync(context, exception, HttpStatusCode.Unauthorized);
        else if (exception is HereException)
            await WriteExceptionAsync(context, exception, HttpStatusCode.BadRequest);
        else
            await WriteExceptionAsync(context, exception, HttpStatusCode.InternalServerError);
    }

    private static async Task WriteExceptionAsync(HttpContext context, Exception exception, HttpStatusCode code)
    {
        var response = context.Response;

        response.ContentType = "application/json";
        response.StatusCode = (int)code;

        await response.WriteAsync(JsonHelper.SerializeObject(new
        {
            Error = new
            {
                Message = exception.Message,
                Exception = exception.GetType().Name
            }
        }));
    }
}

If exception happens response looks like that:

{
  "error": {
    "message": "Facebook authentication token is not valid.",
    "exception": "HereUnauthorizedException"
  }
}
Comments