Fluent Validation Rules with ASP.NET Core
When it comes to model validation in ASP.NET, we've always have had the option of using Data Annotations to declaratively define validation rules with attributes. The situation may arise where using attributes just might not be the thing for you. It might be the case that you don't have access to the code to apply attributes classes you want to have validated. It could also be that your validation logic is fairly complex and you prefer to have it separated from the model. Maybe you just have a personal distaste for using attributes in general.
In this post, we'll take a look at using the FluentValidation library as an alternative to Data Annotations.
Introducing FluentValidation
FluentValidation
is an open source validation library for .NET. It supports a fluent API, and leverages lambda expressions to build validation rules. The current stable version (6.4.1) supports both .NET Framework 4.5+ and .NET Standard 1.0. In addition to that, it provides integrations for ASP.NET MVC 5, ASP.NET Web API and ASP.NET Core MVC.
Creating Validators
We're going to use the dotnet CLI to create a simple MVC application that we can use.
$ dotnet new mvc --auth None --framework netcoreapp1.1
The first thing we'll need is a model to validate. Here's a simple class that we can start off with.
public class RegistrationViewModel { public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } }
Next, using the CLI, we'll add the FluentValidation
NuGet package for ASP.NET Core. If you're using Visual Studio, you can use the Package Manager if you'd like. Either option works equally well.
$ dotnet add package FluentValidation.AspNetCore
To create a validator for RegistrationViewModel
, will need to create a class that inherits from AbstractValidator
<T>. The validation rules are will be defined in the constructor of our newly created validator starting off with the RuleFor
method.
public class RegistrationViewModelValidator : AbstractValidator { public RegistrationViewModelValidator() { RuleFor(reg => reg.FirstName).NotEmpty(); RuleFor(reg => reg.LastName).NotEmpty(); RuleFor(reg => reg.Email).NotEmpty(); } }
You can pass RuleFor
a lambda expression that it will use to determine the property that the proceeding rules will be associated with. Then, we can make use of some of built-in validators like NotEmpty
, NotEqual
, Matches
and Must
.
Adding the FluentValidation services
To wire up FluentValidation
with ASP.NET Core MVC, within the ConfigureServices
method inside of Startup.cs
, we will use the AddFluentValidation
extension method.
public void ConfigureServices(IServiceCollection services) { services.AddMvc() .AddFluentValidation(); }
Calling AddFluentValidation
will add the FluentValidation
services to the default container in ASP.NET Core. This includes a custom IObjectModelValidator
that allows FluentValidation
to plug into ASP.NET Core's validation system. However, it does not register the custom RegistrationViewModelValidator
validator that we created. To automatically register your validators with the container, you have the option to use either the AddFromAssemblyContaining
or RegisterValidatorsFromAssembly
methods.
public void ConfigureServices(IServiceCollection services) { services.AddMvc() .AddFluentValidation(fvc => fvc.RegisterValidatorsFromAssemblyContaining()); }
With that in place, all we need is a simple Razor view to test out our setup.
@model RegistrationViewModel
Registration Form
Now the validation logic for our model is being handled by FluentValidation
, and we can check the ModelState
inside of our MVC Controllers just as we always have.
[HttpPost] public IActionResult FormValidation(RegistrationViewModel model) { if (this.ModelState.IsValid) { ViewBag.SuccessMessage = "Great!"; } return View(); }
Chaining Validation Rules
So far we've setup some basic validation rules. One way we can create more complex rules is by chaining on validations using the fluent syntax. Let's take the Email
field on our model for example. Right now the email validation rule only checks for an non-empty string. What if we wanted to check that the property actually contains a valid email? In that case we can tack on another rule like this.
public RegistrationViewModelValidator() { RuleFor(reg => reg.FirstName).NotEmpty(); RuleFor(reg => reg.LastName).NotEmpty(); RuleFor(reg => reg.Email).NotEmpty().EmailAddress(); }
Now what if we wanted to take it a little further. What if we only wanted to accept emails from a certain domain? A rule like that is a little more specific to our application, so one thing we can make use of is the Must
validation rule and supply it with a predicate.
public RegistrationViewModelValidator() { RuleFor(reg => reg.FirstName).NotEmpty(); RuleFor(reg => reg.LastName).NotEmpty(); RuleFor(reg => reg.Email).NotEmpty() .EmailAddress() .Must(email => email.EndsWith("microsoft.com")); }
Creating Custom Property Validators
The built-in validation rules will usually be more than enough for most of your needs. If not, FluentValidation
allows you to create custom property validators by deriving from PropertyValidator
and implementing it's IsValid
method.
public class EmailFromDomainValidator : PropertyValidator { private readonly string _domain; public EmailFromDomainValidator(string domain) : base("Email address {PropertyValue} is not from domain {domain}") { _domain = domain; } protected override bool IsValid(PropertyValidatorContext context) { if (context.PropertyValue == null) return false; var split = (context.PropertyValue as string).Split('@'); if (split.Length == 2 && split[1].ToLower().Equals(_domain)) return true; return false; } }
IsValid
gets passed an instance of PropertyValidatorContext
. This gives you access to information such as the name of the property being validated, value of the property and a reference to the model itself.
To use this custom PropertyValidator to extend FluentValidation's fluent DSL, we can create an extension method for IRuleBuilder
and use the SetValidator
to include the EmailFromDomainValidator that we created..
public static class CustomValidatorExtensions { public static IRuleBuilderOptions EmailAddressFromDomain( this IRuleBuilder ruleBuilder, string domain) { return ruleBuilder.SetValidator(new EmailFromDomainValidator(domain)); } }
Now we can update the rule definition to use the EmailFromDomainValidator
like this.
public RegistrationViewModelValidator() { RuleFor(reg => reg.FirstName).NotEmpty(); RuleFor(reg => reg.LastName).NotEmpty(); RuleFor(reg => reg.Email).EmailAddressFromDomain("microsoft.com"); }
Conclusion
FluenValidation
is a useful library for not only creating validation rules but also allows you to separate your validation logic from your model. In this post, we've only covered a few of its features. Definitely check out the project on Github to see some of the other configuration options at your disposal.
Subscribe to my newsletter
Read articles from Cecil Phillip directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Cecil Phillip
Cecil Phillip
.NET Developer, Podcaster, Teacher, Swimmer, & Music Lover. Born and Raised in Antigua