.NET

Dealing with failures using custom exceptions and ASP.NET Core middleware

There are many different approaches to deal with failures in web apps. One of the most common is returning a result:

 


        public void GetItem(Guid id)
        {
            var result = ItemsRepository.Get(id);

            if(!result.IsSucceed)
            {
                //do something
            }
        }

 

The idea is simple. If the method fails because of validation or something else, the result tells it. The only thing we need is if statement and code inside which handle it somehow (it can be logging, sending an event etc.). Honestly, I’m not a big fan of this approach mostly because we end up with bigger methods which are not the easiest to understand (especially for someone who reads it for the first time). Therefore, I do something else – I throw an exception and handle it in a dedicated class. And that’s exactly what I’m going to show you.

 

NOTE: In this post, I’ll be using ASP.NET Web API & Autofac

 

Creating custom exceptions

Let’s start with creating our custom exceptions which will be thrown when some „predictable situation” will happen like mentioned validation error:

 


    public abstract class MyCustomException : Exception
    {
        public MyCustomException(string message) : base(message)
        {
        }
    }

    public class NoValuesFoundException : MyCustomException
    {
        public NoValuesFoundException() : base("No values has been found")
        {

        }

        public NoValuesFoundException(string message) : base(message)
        {
        }
    }

 

All custom exceptions will derive from an abstract class called MyCustomException. Thanks to that we’ll be able to determine whether it’s something that we predicted or not (like NullReferenceException).
The next step is to create a generic interface for each class which will handle our exceptions:

 


    public interface IExceptionHandler<TException> where TException : MyCustomException
    {
        IErrorResult Handle(TException exception);
    }

 

Here’s the example handler for our custom exception:

 


    public class NoValuesFoundExceptionHandler : IExceptionHandler<NoValuesFoundException>
    {

        public IErrorResult Handle(NoValuesFoundException exception)
        {
            //handle error in your system (Event, Logging etc.)
            Console.WriteLine("I handled custom exception!");
            return new ErrorResult(StatusCodes.Status404NotFound, ErrorCodes.ValuesNotFound);
        }
    }

 

That’s the place where you can put all your logging and other stuff. For simplicity sake, the only thing I did here was printing text in the console.
Having this we need a class which will take our exception as a parameter and looks for the proper handler for it. That’s where dependency injection pattern comes into play. In this article, I’ll be using Autofac but you can use whatever you want. Below you can see the code responsible for executing proper handler:

 


    public interface ICustomDependencyResolver
    {
        TResolved Resolve<TResolved>();
    }


    public class CustomDependencyResolver : ICustomDependencyResolver
    {
        private readonly ILifetimeScope _lifetimeScope;

        public CustomDependencyResolver(ILifetimeScope lifetimeScope)
        {
            _lifetimeScope = lifetimeScope;
        }

        public TResolved Resolve<TResolved>()
            => _lifetimeScope.Resolve<TResolved>();
    }


    public interface IExceptionHandlerExecutor
    {
        void Execute<TException>(TException exception) where TException : MyCustomException;
    }


	public class ExceptionHandlerExecutor : IExceptionHandlerExecutor
	{
		private readonly ICustomDependencyResolver _customDependencyResolver;

		public ExceptionHandlerExecutor(ICustomDependencyResolver customDependencyResolver)
		{
			_customDependencyResolver = customDependencyResolver;
		}

		public void Execute<TException>(TException exception) where TException : MyCustomException
			=> _customDependencyResolver.Resolve<IExceptionHandler<TException>>().Handle(exception);
	}

 

Note that CustomDependencyResolver is nothing more than a simple wrapper for ILifetimeScope. It’s not required to implement, but simply I didn’t want to use ILifetimeScope directly in my code (besides resolver :D). The executor uses it to resolve proper handler and invoke Handle method. Remember to register all dependencies inside ConfigureServices method inside Startup class:

 


        public IConfigurationRoot Configuration { get; }

       
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {			
			services.AddMvc();

			var containerBuilder = new ContainerBuilder();
            containerBuilder.Populate(services);
            containerBuilder.RegisterType<CustomDependencyResolver>().As<ICustomDependencyResolver>();
            containerBuilder.RegisterType<ExceptionHandlerExecutor>().As<IExceptionHandlerExecutor>();
            containerBuilder.RegisterType<NoValuesFoundExceptionHandler>().As<IExceptionHandler<NoValuesFoundException>>();
            ApplicationContainer = containerBuilder.Build();

            return new AutofacServiceProvider(this.ApplicationContainer);
        }

 

So far so good. Now the question is, how will we catch the exception in our application? Of course, we don’t want to surround each method with a try-catch. The answer – ASP.NET Core middlewares. The one for our job looks as follows:

 


    public class ErrorHandlerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IExceptionHandlerExecutor _exceptionHandlerExecutor;

        public ErrorHandlerMiddleware(RequestDelegate next, IExceptionHandlerExecutor exceptionHandlerExecutor)
        {
            _next = next;
            _exceptionHandlerExecutor = exceptionHandlerExecutor;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                var myCustomExcecption = ex as MyCustomException;

                if (myCustomExcecption != null)
                {
                    var exceptionType = exception.GetType();
                    var executorType = _exceptionHandlerExecutor.GetType();

                    executorType
                        .GetMethod(nameof(IExceptionHandlerExecutor.Execute))
                        .MakeGenericMethod(exceptionType)
                        .Invoke(_exceptionHandlerExecutor, new []{ myCustomExcecption });
                }
                else
                {
                    throw ex;
                }
            }
        }

    }

 

And below you can see how to add it to the whole pipeline:

 


        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            var executor = ApplicationContainer.Resolve<IExceptionHandlerExecutor>();
            app.UseMiddleware<ErrorHandlerMiddleware>(executor);
            app.UseMvc();
        }

 

The important thing here is that we need to place middleware’s code before MVC. The order counts in this case!

Let’s move back to our ErrorHandlerMiddleware implementation. This might look kinda complicated but I’ll do my best to make it clear. The Invoke method calls _next which is nothing more but the „step” in the pipeline which will be called after our middleware. In this case, it gonna be MVC. That’s why I wrote that order matters. Having MVC called first would give us nothing since we could not catch any exception. Inside catch block, we check whether the exception derives from MyCustomException. If so, we use a reflection to call executor and pass the exception to proper handler. Why reflection? Since Execute method is generic the only way to invoke it having only an object is a reflection. I know that’s not the nicest code to read but that’s the best solution I can come up with. Believe or not, we are ready to go! Let’s modify default ValuesController class like follows:

 


    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        // GET api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            throw new NoValuesFoundException();
        }
    }

 

After running the app, simply call your API using fiddler, postman, curl etc. The result?

 

 

As you see our middleware caught the exception thrown inside the controller and passed it to the executor. Then executor resolved proper handler and invoked Handle method.

 

Adding HTTP response

The above solution has a drawback. The HTTP response doesn’t tell anything about the error that happened. If you look at the response from the API, you can see that its status code is 200 (OK) and there’s no body. Let’s change that right now. We’ll need some additional code:

 


    public enum ErrorCodes : byte
    {
        ValuesNotFound = 1
    }


    public interface IErrorResponseBody
    {
        IEnumerable<ErrorCodes> ErrorCodes { get; }
    }


    public class ErrorResponseBody : IErrorResponseBody
    {
        public IEnumerable<ErrorCodes> ErrorCodes { get; private set; }

        public ErrorResponseBody(ErrorCodes errorCode)
		{
            ErrorCodes = new List<ErrorCodes> { errorCode };
		}

        public ErrorResponseBody(IEnumerable<ErrorCodes> errorCodes)
        {
            ErrorCodes = errorCodes;
        }

    }


    public interface IErrorResult
    {
        int ResponseStatusCode { get; }
        IErrorResponseBody ResponseBody { get; }
    }


    public class ErrorResult : IErrorResult
    {
        public int ResponseStatusCode { get; private set; }
        public IErrorResponseBody ResponseBody { get; private set; }

        public ErrorResult(int responseStatusCode, ErrorCodes errorCode)
		{
			ResponseStatusCode = responseStatusCode;
			ResponseBody = new ErrorResponseBody(errorCode);
		}

        public ErrorResult(int responseStatusCode, IEnumerable<ErrorCodes> errorCodes)
        {
            ResponseStatusCode = responseStatusCode;
            ResponseBody = new ErrorResponseBody(errorCodes);
        }
    }

 

As you see I introduced a new class called ErrorResult. This one will be returned from each handler. The result contains two pieces of information: status code for the HTTP response and its body which is a collection of error codes (they can be turned into messages for the user inside client application).
The next step is to modify the handler and executor (and their interfaces):

 


    public interface IExceptionHandler<TException> where TException : MyCustomException
    {
        IErrorResult Handle(TException exception);
    }


    public class NoValuesFoundExceptionHandler : IExceptionHandler<NoValuesFoundException>
    {

        public IErrorResult Handle(NoValuesFoundException exception)
        {
            //handle error in your system (Event, Logging etc.)
            Console.WriteLine("I handled custom exception!");
            return new ErrorResult(StatusCodes.Status404NotFound, ErrorCodes.ValuesNotFound);
        }
    }


    public interface IExceptionHandlerExecutor
    {
        IErrorResult Execute<TException>(TException exception) where TException : MyCustomException;
    }


	public class ExceptionHandlerExecutor : IExceptionHandlerExecutor
	{
		private readonly ICustomDependencyResolver _customDependencyResolver;

		public ExceptionHandlerExecutor(ICustomDependencyResolver customDependencyResolver)
		{
			_customDependencyResolver = customDependencyResolver;
		}

		public IErrorResult Execute<TException>(TException exception) where TException : MyCustomException
			=> _customDependencyResolver.Resolve<IExceptionHandler<TException>>().Handle(exception);
	}

 

The last thing is modifying our middleware to return an HTTP response:

 


    public class ErrorHandlerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IExceptionHandlerExecutor _exceptionHandlerExecutor;

        public ErrorHandlerMiddleware(RequestDelegate next, IExceptionHandlerExecutor exceptionHandlerExecutor)
        {
            _next = next;
            _exceptionHandlerExecutor = exceptionHandlerExecutor;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                var myCustomExcecption = ex as MyCustomException;

                if (myCustomExcecption != null)
                {
                    var errorResult = GetErrorResult(myCustomExcecption);
                    context.Response.StatusCode = errorResult.ResponseStatusCode;
                    await context.Response.WriteAsync(JsonConvert.SerializeObject(errorResult.ResponseBody));
                }
                else
                {
                    throw ex;
                }
            }
        }

        private IErrorResult GetErrorResult(MyCustomException exception)
        {
            var exceptionType = exception.GetType();
            var executorType = _exceptionHandlerExecutor.GetType();

            return (IErrorResult) executorType
                .GetMethod(nameof(IExceptionHandlerExecutor.Execute))
                .MakeGenericMethod(exceptionType)
                .Invoke(_exceptionHandlerExecutor, new []{ exception });
        }

    }

 

Let’s run the app and call the API once again:

 

 

As you see, the response clearly shows that something happened.

Of course, you can move even further and add some additional abstractions to aggregate similar exceptions and avoid redundant code but I didn’t want to mess up too much. To sum up, why do I like this approach?

  1. You keep your „domain code” clean
  2. Handling new failure requires only new exception and handler
  3. API returns always the same model on failure so it’s easy to handle it on the client-side using interceptors or something else

Let me know what do you think about it and what other approaches you use to deal with failures! Whole app is available on my GitHub 😉