Validating API input in ASP.NET Core 1.1 with FluentValidation
January 14, 2017 by Michael
It doesn’t matter if your API is nothing more than a facade for simple CRUD operations built on-top of an Active Record implementation or if your API is merely the gateway into your complex Domain Driven Design that leverages the latest and greatest CQSR/ES patterns to scale on demand: you need to validate your API input.
Writing code to validate input is quite possibly one of the most tedious task ever created (right next to doing any sort of processing that involves any file format from the medical world). Thankfully we are far from the days of having to manually handle that task.
When I started my latest ASP.NET Core 1.1 project I wanted a more expressive way to handle validation. Enter FluentValidation
: a small library that does an excellent job handling input validation (high level validation before you get into the heart of your business logic). Below I show you the three phases my validation code went through before I finally end up where I probably should have started.
Getting Started
Before getting to far into this tutorial you’ll want to make sure you:
- Have a basic understanding of IoC containers (preferably StructureMap)
- Have the latest .NET Core SDK installed
- Know how to create an ASP.Net Core 1.1 Web API project (I’ll let you handle your own Google search)
The First Pass (Also Known as the Really Bad Idea)
I knew I wanted to use FluentValidation
and I know that ASP.Net has built in model validation. What I didn’t know was how to bring them together. Eventually I got there, but it took a few passes. The first step was a given, add the required dependencies to my project.json
file:
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.1.0",
"type": "platform"
},
"Microsoft.AspNetCore.Mvc": "1.1.0",
// other dependencies removed for brevity
"StructureMap.Microsoft.DependencyInjection": "1.3.0",
"FluentValidation.AspNetCore": "6.4.0-beta9",
"Serilog": "2.3.0",
"Serilog.Extensions.Logging": "1.3.1",
"Serilog.Sinks.Literate": "2.0.0"
},
// other sections removed for brevity
}
With that in place I wrote a few validators for the input into my POST
and PUT
action handlers. You can find all of the documentation for FluentValidation
here. Below is an example command and it’s related validator:
public class AddRecipe
{
public Guid Id {get; set;}
public Guid CreatedBy {get; set;}
public DateTime CreatedAt {get ;set;}
public string Title {get; set;}
public string Instructions {get; set;}
public List<string> Ingredients {get; set;}
}
public class AddRecipeValidator : AbstractValidator<AddRecipe>
{
private static readonly DateTime MinDate = new DateTime(2000, 1, 1);
private static readonly DateTime MaxDate = new DateTime(2100, 1, 1);
public AddRecipeValidator()
{
RuleFor(cmd => cmd.Id).NotEmpty();
RuleFor(cmd => cmd.CreatedBy).NotEmpty().NotEqual(cmd => cmd.Id);
RuleFor(cmd => cmd.CreatedAt).Must(BeValidDate)
.WithMessage("'Created At' must be a valid date");
RuleFor(cmd => cmd.Title).Length(5, 100);
RuleFor(cmd => cmd.Ingredients).NotEmpty();
}
private bool BeValidDate(DateTime input)
{
return input.Date > MinDate && input.Date < MaxDate;
}
}
Next, I wanted a generic way to get the validator for a given class based on the class’s type. I didn’t want to have to know what the exact type of the validator implementation was. This seemed like a good time to use StructureMap (in reality, it wasn’t). Here’s how you could use StructureMap if you really really wanted to (for some reason):
In your StructureMap scanner configuraiton add the following line:
s.ConnectImplementationsToTypesClosing(typeof(AbstractValidator<>));
ConnectImplementationsToTypesClosing
will allow you to get an instance of a given validator knowing only the type of the object the validator handles. I wanted to be able to get an instance of a validator using the same syntax I would use to get any other object from my IoC container. While I generally avoid extension methods, this seems like a good place to put the pattern to use:
With that in place, you can validate the input to your controller’s action methods as follows:
[HttpPost]
public IActionResult Post([FromBody,Required] AddIngredient command)
{
if(command == null)
return BadRequest();
ValidationResult result =
_container
.TryGetValidatorInstance<AddIngredient>()?.Validate(command);
if(result?.IsValid == false)
return BadRequest(result);
return CreatedAtRoute("GetIngredient", new {id = Guid.NewGuid()}, null);
}
That’s, ummm…. not good. There are a few major problems with this solution:
- It requires injecting my IoC container into the controller (leaky abstraction, I might as well have my repositories return
IQueryable
while I’m at it) - I have to type the same few lines at the beginning of every method where I want to validate the input
The Second (Less Bad) Solution
Surface level validation is kind of a cross-cutting concern right? Aspect Oriented Programming is a way to handle cross-cutting concerns… AOP frequently uses attributes… I can use an attribute to handle my validation. A few degrees short of Kevin Bacon and I have a new direction: ValidateInputAttribute:
public class ValidateInputAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if(context.ModelState.IsValid)
return;
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
Progress.
This solution allowed me to pull the IoC container out of my controllers. It also simplified the code for validating input:
[HttpPost]
[ValidateInput]
public IActionResult Post([FromBody,Required] AddReview command)
{
return CreatedAtRoute("GetReview", new {id = Guid.NewGuid()}, null);
}
Unfortunately I still had one problem: I can’t inject my logging framework of choice into an Attribute. Well, where there’s a will there’s a way… but if you have to work that hard to get something to work, there’s probably a better solution…
The Third and Final Attempt (for Now)
Filters allow you to execute code in the MVC Action Pipeline prior to the action being executed but after the model had been bound via Action Filters. This seems like the perfect place to solve my problem.
Before we can handle validation errors in a Filter we first need to update our Startup
class’s ConfigureServices
method:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddFluentValidation(
fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());
return services.AddStructureMap();
}
The highlighted lines above will configure things so that FluentValidation
handles validating input for you and updates the ModelState
accordingly. Note, using this approach does not require StructureMap
.
Next you need to add an implementation of IActionFilter
to handle validation errors:
public class ValidateInputFilter : IActionFilter
{
#region
private readonly ILogger _logger;
#endregion
#region Constructor
public ValidateInputFilter(ILogger logger)
{
_logger = logger.ForContext<ValidateInputFilter>();
}
#endregion
#region IActionFilter Implementation
public void OnActionExecuting(ActionExecutingContext context)
{
if(context.ModelState.IsValid)
return;
using(LogContext.PushProperties(BuildIdentityEnrichers(context.HttpContext.User)))
{
_logger.Warning("Model validation failed for {@Input} with validation {@Errors}",
context.ActionArguments,
context.ModelState?
.SelectMany(kvp => kvp.Value.Errors)
.Select(e => e.ErrorMessage));
}
context.Result = new BadRequestObjectResult(
from kvp in context.ModelState
from e in kvp.Value.Errors
let k = kvp.Key
select new ValidationError(ValidationError.Type.Input, null, k, e.ErrorMessage));
}
public void OnActionExecuted(ActionExecutedContext context)
{
// This filter doesn't do anything post action.
}
#endregion
}
The above code should be pretty straight forward. If there are no errors in the given model, simply return and do nothing. Let the next filter do it’s thing. If there is a problem with the ModelState
, log it and let the client know that a Bad Request was made (HTTP status code 400).
Note, in the current version of FluentValidation
if a null
object is passed into your action method context.ModelState.IsValid
will return true. Given what I read here, that’s not what I expected.
Conclusion
I double my current solution is perfect and I’m almost certain it’ll go through another refactoring (or two) as I continue to work on the project. Hopefully you found something useful above. If not, thanks for taking the time to read this article and I would appreciate any feedback you might have.
You can find a working example on GitHub: https://github.com/mlindegarde/blog–api-input-validation
Useful links
The links below my help answer any questions you may have: