Custom Exception Handling in .Net Core with a Middleware

ASP.NET Core May 23, 2019

For many developers, exceptions and error handling is a pain. It distracts from the application flow. However, it is important for any decent service or application to handle it well. To tackle this problem we will use Custom Exception Handling in this tutorial.

ASP.NET Core provides an easy concept for all sorts of extensions and handling of errors. They are called middlewares. To create an ASP.NET Core Application with RESTful Services visit this blog entry.

Middleware execution flow
Middleware execution flow - Microsoft

As stated in the above picture every request passes all middlewares. Each middleware calls the next (via next() call) and returns to the prior middleware when exiting the flow to the response message. You find more detailed information at Microsoft's official middleware documentation. Keep in mind that the order of registering middlewares is important and can cause issues when done wrong. The ordering

Exception handler Page

The simplest way is using a dedicated error page when an exception occurs. This can easily be achieved by just adding the following line of code in your Startup.cs Configure method.

app.UseExceptionHandler("/Error");

Together with this ExceptionHandler you should provide a sufficient Error Page which can be rendered. Usually, this would be a Razor page named Error.cshtml with the following content.

[AllowAnonymous]
public IActionResult Error()
{
    return View(new ErrorViewModel 
        { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}

Dedicated Status Code Pages

In many cases you want to display different error/status pages depending on the Http Status Code. Just add app.UseStatusCodePages(); instead of the UseExceptionHandler().

app.UseStatusCodePages();

There are many different ways how you can customize the behavior of the Status Code handler. To learn more about the full capabilities read this article. In my experience, the most used option is a redirect to a dedicated status code page like 401, 403, 404, etc.

app.UseStatusCodePagesWithRedirects("/StatusCode?code={0}");

Like the /Error page redirect make sure that you have an appropriate status page for each status you want to handle.

Handling the Exception with Custom Code

There are legitimate reasons that you want to handle all exception that is not caught in the business logic. For this purpose, you can use a custom exception handler and register it as a middleware. This exception handler below differentiates between development and production environments. Eventually, all exceptions are parsed as Internal Server Errors and returned as custom ApiError objects to the caller.

Custom Excpetion Handler logic.

public class JsonExceptionMiddleware
    {
        private readonly IHostingEnvironment _environment;
        private const string DefaultErrorMessage = "A server error occurred.";

        public JsonExceptionMiddleware(IHostingEnvironment environment)
        {
            _environment = environment;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

            var ex = httpContext.Features.Get<IExceptionHandlerFeature>()?.Error;

            if (ex == null)
            {
                return;
            }

            var error = new ApiError();

            if (_environment.IsDevelopment())
            {
                error.Message = ex.Message;
                error.Detail = ex.StackTrace;
                error.InnerException = ex.InnerException != null ? ex.InnerException.Message : string.Empty;
            }
            else
            {
                error.Message = DefaultErrorMessage;
                error.Detail = ex.Message;
            }

            httpContext.Response.ContentType = "application/json";

            using (var writer = new StreamWriter(httpContext.Response.Body))
            {
                new JsonSerializer().Serialize(writer, error);
                await writer.FlushAsync().ConfigureAwait(false);
            }
        }
    }

Custom ApiError Model.

public class ApiError
    {
        public string Message { get; set; }
        public string Detail { get; set; }
        public string InnerException { get; set; }


    }

Registration of Custom Exception Handler.

app.UseExceptionHandler(new ExceptionHandlerOptions
            {
                ExceptionHandler = new JsonExceptionMiddleware(env).Invoke
            });

That's it. Happy coding as always :).

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.