Validation without Exceptions using a MediatR Pipeline Behavior

David Rogers
Apr 6, 2020 · 5 min read

This is the first post in a 2 post series:

(1) Validation without Exceptions using a MediatR Pipeline Behavior; and
(2) Manual Validation in the Business Layer without Exceptions

In these 2 posts, I’m going to outline a couple of strategies that I use for returning validation failures in the business layer, without throwing an exception. The posts will also be in the context of Mediatr, which is a very handy library for modelling your HTTP requests (as either queries or commands — think CQRS).

Using a Pipeline Behavior for validating business objects is basically a way where we can let our framework perform the validation for us, rather than manually validating them.

Setting up Mediatr and creating pipelines is beyond the scope of this post. You will see a pipeline behavior being used in the post and can refer to the Mediatr documentation to get up and running quickly, if you are not familiar with Mediatr and how it works (the Github repo also has some good sample code).

The genesis of these posts came from my desire to handle validation failures gracefully and without throwing exceptions. This post will focus on validation in a Mediatr Pipeline behaviour. You may find a lot of code like this on StackOverflow, where an exception is thrown due to a validation failure/s. The exception is caught downstream and everything goes along swimmingly.

But this is not good. Whilst it is easy to implement (that’s a pro), there are a number of cons with this:

  1. you should not use Exceptions for control flow. There’s a number of reasons for this, including the high perf cost in throwing exceptions. You don’t do it elsewhere in your code, so why do it for validation failures?
  2. a validation failure is not exceptional in nature. It is actually expected. Hence, the validation check. Invoking the exception handling infrastructure should be confined to those things which are not expected.

With that in mind, I needed to find a way to return the validation failures to the client from a Mediatr pipeline, without just throwing (and later catching) an exception. It takes a little bit more effort, but I’m of the view that it is definitely worth it. To the code!

I’ll use an example of a request, where the submitted form:

  • is valid from a “form validation” perspective
  • but invalid from a “business layer” validation perspective

Lets say we have a user creation scenario and the user submits a POST request to the server with all valid fields (including the username). However, in this example, there is a business rule which checks to see if the username already exists (in the database). So, whilst the posted form is valid (i.e. the username is not null), the overall request is ultimately invalid because the user submits a username which already exists in the database.

There’s a lot of moving parts in this, so I’ll start with a validator that is not central to our story. The following validator just validates the POST request. Nothin’ fancy, but it is used here to highlight the point that if the POST has valid inputs, the ModelState will be valid (FluentValidation, handily, bolts on any errors to ASP.NET’s ModelState):

I will create a validator that we are interested in (soon). But first we need a command object, as we are mutating data by creating a user. This is the command for that:

There’s a couple of key things I need to explain here. The IValidateable interface is just an empty marker interface. This tells my pipeline that this command requires validation. The ValidateableResponse class is the other big one here.

In the normal Mediatr workflow, I would just return the ApiResponse by itself. However, if validation fails, that will not be the object type which is returned. ApiResponse has no information about errors, which we need to convey back to the client. So, we wrap it in a ValidateableResponse, which looks like this:

And the CommandHandler will look like this:

By doing this, despite the outcome, the same type of object is returned i.e. a ValidateableResponse. If all goes well, we just get the Data property, which in this case, will be an ApiResponse. If validation fails, there will be a populated Errors collection. The outcome is easily checked by querying the IsValidResponse property. This enables us to write code like this, in our Controller:

As you can see, there is no try/catch block (we will save that for the custom action filters or middleware).

But back to the validator for our Command. The pipeline which I will be showing shortly will need a validator object to invoke, to perform validation on the command which has been sent by the mediator. That validator which be strongly typed to our command:

Finally, let’s now look at the pipeline behavior class which draws this all together; the component which validates our command and returns a ValidateableResponse (with a populated Errors list, where validation has failed):

Because our validator is strongly typed, it will be injected into the Composite validator, which gets injected into the BusinessValidationPipeline. If the request fails validation, log it, then generate a ValidateableResponse. This must be done and returned before the next pipeline behavior is invoked (to short-circuit the pipeline). Thus, it is returned before the call to await next(), which would be called if everything went well.

Another really cool aspect of this setup is that this pipeline behavior won’t even run unless the TRequest implements IValidateable. Notice how the following generic constraint is placed on the TRequest parameter — where TRequest : IValidateable. To achieve this, you use dependency injection to ensure that this part of the pipeline does not even get injected where the command, which is moving through the pipleline, does not implement IValidateable. So, no need for any if statement in the pipleline behavior class like if(TRequest is IValidateable). An example of such an IOC configuration, using SimpleInjector, would look like this:

Container.RegisterSingleton<IMediator, Mediator>();
Container.Register(typeof(IRequestHandler<,>), assemblies, Lifestyle.Singleton);

Container.Collection.Register(typeof(IPipelineBehavior<,>), new[]
{
typeof(LogContextPipeline<,>),
typeof(BusinessValidationPipeline<,>),
typeof(TransactionPipelineBehaviour<,>),
typeof(GenericPipelineBehavior<,>)
});

I’ll also just touch on the reflection which has been used to create the return object where validation fails. Obviously, as this pipeline behavior serves a variety of requests, generics is used to provide that flexibility. That also means we can’t just “new” up an object as we don’t know the closed generic type of TResponse. So, we use reflection to interrogate the response object and from there are able to construct the ValidateableResponse, using the Activator class. Some people will assert that reflection is expensive. It’s not. It really isn’t (unless you are building something where performance is absolutely critical, like a web-server). The perf expense of such a manoeuvre is negligible and pales in comparison to the expense of throwing an exception.

My next post is going to deal with the same notion of validations without exceptions, except the validation will happen in a different place (in the business layer layer itself [i.e. not in a pipeline behavior]) and I will be using a functional programming technique to achieve that end.

This Github repo contains sample code which demonstrates the concepts set out in this article. Note, I’ve created a branch specific to this article called validation-article-1.

The Cloud Builders Guild

Cloud enthusiasts building things in the cloud.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store