Validation without Exceptions using a MediatR Pipeline Behavior

First post for 2019 (took me a while to get there).

This is the first post in a 2 post series:

  1. Validation without Exceptions using a MediatR Pipeline Behavior; and
  2. Manual Validation in A Mediatr RequestHandler without Exceptions

In the next 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).

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:

  • 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?
  • 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 (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):

public class CreateUserValidator: AbstractValidator<CreateUserDto>
{
	public CreateUserValidator()
	{
		RuleFor(m => m.Password).NotEmpty();
		RuleFor(m => m.UserName).NotEmpty();
	}
}

Next, I need to create a Command, as we are mutating data by creating a user. This is the command for that:

public class CreateNewUserCommand : IRequest<ValidateableResponse<ApiResponse<int>>>, IValidateable
{
    public CreateUserDto CreateUserDto { get; set; }
}

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:

public class ValidateableResponse
{
	private readonly IList<string> _errorMessages;

	public ValidateableResponse(IList<string> errors = null)
	{
		_errorMessages = errors ?? new List<string>();
	}

	public bool IsValidResponse => !_errorMessages.Any();

	public IReadOnlyCollection<string> Errors => new ReadOnlyCollection<string>(_errorMessages);
}

public class ValidateableResponse<TModel> : ValidateableResponse
	where TModel : class
{

	public ValidateableResponse(TModel model, IList<string> validationErrors = null)
		: base(validationErrors)
	{
		Result = model;
	}

	public TModel Result { get; }
}

And the CommandHandler will look like this:

public class CreateNewUserCommandHandler : IRequestHandler<CreateNewUserCommand, ValidateableResponse<ApiResponse<int>>>
{
	private readonly UserManager<IdpUser> _userManager;

	public CreateNewUserCommandHandler(UserManager<IdpUser> userManager)
	{
		_userManager = userManager;
	}

	public async Task<ValidateableResponse<ApiResponse<int>>> Handle(CreateNewUserCommand request,
		CancellationToken cancellationToken)
	{
		var dto = request.Dto;

		var newAppUser = new IdpUser
		{
			UserName = dto.UserName.Trim(),                
			Password = dto.Password.Trim()
		};

		var result = await _userManager.CreateAsync(newAppUser, dto.Password);

		if (result.Succeeded)
		{
			await _userManager.AddClaimsAsync(
				newAppUser,
				new[] 
				{
					new Claim("access", dto.UserRole),
					new Claim("given_name", dto.UserName)
				}
			);
			
            var apiResponse = new ApiResponse<int>
            {
                Data = newAppUser.Id,
                Outcome = new OperationOutcome
                {
                    Message = string.Empty, OpResult = OpResult.Success
                }
            };			

			return new ValidateableResponse<ApiResponse<int>>(apiResponse);
		}

		return new ValidateableResponse<ApiResponse<int>>(
			null,
			result.Errors
				.Select(e => string.Concat(e.Code, GeneralPurpose.UniqueDelimiter, e.Description))
				.ToList()
			);
	}
}

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:

[HttpPost]
[Route("[action]")]
public async Task<IActionResult> CreateUser([FromBody]CreateUserDto userDto)
{
    var response = await _mediator.Send(new CreateNewUserCommand {CreateUserDto = userDto});

	return result.IsValidResponse
		? CreatedAtRoute(routeValues: new
		{
			controller = "User",
			action = nameof(UserController.GetUser),
			id = result.Result.Data
			version = HttpContext.GetRequestedApiVersion().ToString()
		}, result.Result)
		: BadRequestResponse(Enumerable.Empty<string>(), operationOutcome: new OperationOutcome
		{
			OpResult =OpResult.Fail, 
			IsError = false,
			IsValidationFail = true,
			Errors = result.Errors
		});

}

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

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:

public class BusinessValidationPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
	where TResponse : class
	where TRequest : IValidateable
{
	private readonly IValidator<TRequest> _compositeValidator;
	private readonly ILogger<TRequest> _logger;
	private readonly ICurrentUser _currentUser;

	public BusinessValidationPipeline(IValidator<TRequest> compositeValidator, ILogger<TRequest> logger, ICurrentUser currentUser)
	{
		_compositeValidator = compositeValidator;
		_logger = logger;
		_currentUser = currentUser;
	}

	public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
	{
		var result = await _compositeValidator.ValidateAsync(request, cancellationToken);

		if (!result.IsValid)
		{
			_logger.LogError(EventIDs.EventIdPipelineThrown,
				MessageTemplates.ValidationErrorsLog,
				result.Errors.Select(s => s.ErrorMessage).Aggregate((acc, curr) => acc += string.Concat("_|_", curr)),
				_currentUser.UserName
				);

			var responseType = typeof(TResponse);

			if (responseType.IsGenericType)
			{
				var resultType = responseType.GetGenericArguments()[0];
				var inValidResponse = typeof(ValidateableResponse<>).MakeGenericType(resultType);

				var invalidResponse =
					Activator.CreateInstance(inValidResponse, null, result.Errors.Select(s => s.ErrorMessage).ToList()) as TResponse;

				return invalidResponse;
			}
		}

		var response = await next();

		return response;
	}
}

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.

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>