While Data Annotations.aspx) approach is generally used for user input data validation, FluentValidation that I’m going to introduce in this article might be better for delelopers with more benefits. Source code used in this post can be found at:
Typical User Input Data Validation with Data Annotations
Generally, for user input validation, data annotations approach like below is used. Each property that needs to be validated is required to have attribute classes:
public class RegisterViewModel
{
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
public string ConfirmPassword { get; set; }
}
`</pre>
Attribute classes like `Required`, `StringLength` and `Compare` are responsible for data validation. If we use them, `ModelState.IsValid` value in the controller can be either `true` (valid) or `false` (invalid).
<pre>`[HttpPost]
public virtual async Task<ActionResult> Register(RegisterViewModel form)
{
var vm = form;
if (ModelState.IsValid)
{
vm.Validated = true;
}
return View(vm);
}
`</pre>
It's perfectly OK for data validation. However, personally, this looks too verbose to me. I need a single point for data validation. If you are like me, [`FluentValidation`](https://github.com/JeremySkinner/FluentValidation) will be yours. Let's change this model using `FluentValidation`
## User Input Data Validation with FluentValidation
You might need to download two packages from [NuGet](https://nuget.org):
- FluentValidation
-
Once you download both, then the
RegisterViewModel
class can be changed to:`[Validator(typeof(RegisterViewModelValidator))] public class RegisterViewModel { [Display(Name = "Email")] public string Email { get; set; } [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] public string ConfirmPassword { get; set; } } `
As you can see,
Validator(typeof(RegisterViewModelValidator))
has been added andRequired
,StringLength
andCompare
have been removed. Yes, that’s right.RegisterViewModelValidator
defines all the validation rules like:`public class RegisterViewModelValidator : AbstractValidator<RegisterViewModel> { public RegisterViewModelValidator() { RuleFor(x => x.Email) .NotNull().WithMessage("Required") .EmailAddress().WithMessage("Invalid email"); RuleFor(x => x.Password) .NotNull().WithMessage("Required") .Length(6, 100).WithMessage("Too short or too long"); RuleFor(x => x.ConfirmPassword) .NotNull().WithMessage("Required") .Equal(x => x.Password).WithMessage("Not matched"); } } `
Once of benefits using
FluentValidation
is that setting validation rules looks very intuitive. For example, theEmail
property is required (Not NULL) and formatted as an email. In addition to this, each validation rule has its own error message when the validation fails. Once validators are defined,Application_Start()
fromGlobal.asax.cs
should callFluentValidationModelValidatorProvider.Configure()
to activate those validators defined.`public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { ... FluentValidationModelValidatorProvider.Configure(); } } `
Now,
ModelState.IsValid
property still hastrue
orfalse
after the validation. Let’s move on to setting up views.Setting up Views for User Input
Corresponding Razore view might look like this:
`@using (Html.BeginForm(MVC.Home.ActionNames.Register, MVC.Home.Name, FormMethod.Post))
{
<div>@Html.LabelFor(m => m.Email) <div> @Html.TextBoxFor(m => m.Email, new Dictionary<string, object>() { { "placeholder", "Email" } }) @Html.ValidationMessageFor(m => m.Email) </div>
</div>
<div>@Html.LabelFor(m => m.Password) <div> @Html.PasswordFor(m => m.Password, new Dictionary<string, object>() { { "placeholder", "Password" } }) @Html.ValidationMessageFor(m => m.Password) </div>
</div>
<div>@Html.LabelFor(m => m.ConfirmPassword) <div> @Html.PasswordFor(m => m.ConfirmPassword, new Dictionary<string, object>() { { "placeholder", "Confirm Password" } }) @Html.ValidationMessageFor(m => m.ConfirmPassword) </div>
</div>
<div><div> <input type="submit" name="Submit" /> </div>
</div>
}
`This will be rendered in a web browser like:
`<form action="/Home/Register" method="post"> <div> <label for="Email">Email</label> <div> <input data-val="true" data-val-email="Invalid email" data-val-required="Required" id="Email" name="Email" placeholder="Email" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true"/> </div> </div> <div> <label for="Password">Password</label> <div> <input data-val="true" data-val-length="Too short or too long" data-val-length-max="100" data-val-length-min="6" data-val-required="Required" id="Password" name="Password" placeholder="Password" type="password" /> <span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"/> </div> </div> <div> <label for="ConfirmPassword">Confirm password</label> <div> <input data-val="true" data-val-equalto="Not matched" data-val-equalto-other="*.Password" data-val-required="Required" id="ConfirmPassword" name="ConfirmPassword" placeholder="Confirm Password" type="password" /> <span class="field-validation-valid" data-valmsg-for="ConfirmPassword" data-valmsg-replace="true"/> </div> </div> <div> <div> <input type="submit" name="Submit" /> </div> </div> </form> `
Therefore, the controller performs server-side validation. If you add a javascript validation library like jQuery Validation, client-side validation can also be easily developed.
Setting up IoC Container for Dependency Injection for Validation
Any validator using
FluentValidation
library inheritsAbstractValidator<T>
which implements theIValidator
interface. It’s great because classes implementing interfaces can easily be both unit-testable and dependency-injectable. Let’s have a look how to setup IoC containers for those validators using Autofac. First of all, changeApplication_Start()
fromGlobal.asax.cs
like below:`private void Application_Start(object sender, EventArgs e) { ... DependencyConfig.RegisterDependencies(); } `
As you can see,
FluentValidationModelValidatorProvider.Configure()
within theApplication_Start()
method has been replaced withDependencyConfig.RegisterDependencies()
.DependencyConfig
class under theApp_Start
directory might look like:`public static class DependencyConfig { public static void RegisterDependencies() { var builder = new ContainerBuilder(); ... RegisterValidators(builder); var container = builder.Build(); DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); RegisterValidationProviders(container); } private static void RegisterValidators(ContainerBuilder builder) { builder.RegisterType<RegisterViewModelValidator>() .Keyed<IValidator>(typeof(IValidator<RegisterViewModel>)) .As<IValidator>(); } ... } `
For more details to build dependencies can be found at
Autofac
website. Instead, we’re focusing on two private methods –RegisterValidators()
andRegisterValidationProviders()
. All validator classes should go into theRegisterValidators()
method for IoC registration. TheRegisterValidationProviders()
method actually activates validators implemented byFluentValidation
library like:`private static void RegisterValidationProviders(IContainer container) { ModelValidatorProviders.Providers.Clear(); #1 DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false; #2 var fvmvp = new FluentValidationModelValidatorProvider(new ValidatorFactory(container)) { AddImplicitRequiredValidator = false, }; #3 ModelValidatorProviders.Providers.Add(fvmvp); #4 } `
#1
: There might be existing validator providers within memory. This clears the memory first.#2
: This explicitly declares to performDataAnnotations
model validation.#3
: This creates validators provided byFluentValidation
usingValidatorFactory
class. This also explicitly declares to perform validations.#4
: This adds validators usingFluentValidation
library.There might be many validator classes inheriting
AbstractValidator<T>
for each model. Therefore, all registered validators intoAutofac
should be instantiated by thisValidatorFactory
class. Here’s the details:`public class ValidatorFactory : ValidatorFactoryBase { private readonly IContainer container; public ValidatorFactory(IContainer container) { this.container = container; } public override IValidator CreateInstance(Type validatorType) { var validator = container.ResolveOptionalKeyed<IValidator>(validatorType); return validator; } } `
As stated above, this is possible because all validators implements
IValidator
interface. Once we completes this step, we should remove all[Validator(typeof(TValidator))]
attribute classes on models. For this case, it will be[Validator(typeof(RegisterViewModelValidator))]
.Unit Testing Validators
As
IValidator
interface gives us much flexibility, we can also perform unit tests. With NUnit and FluentAssertions, we can easily write unit tests.`[TestFixture] public class RegisterModelValidatorTest { private IValidator _validator; [SetUp] public void Init() { this._validator = new RegisterViewModelValidator(); } [Test] [TestCase("email", "password", "password", false)] [TestCase(null, "password", "password", false)] [TestCase("e@mail.com", "password", "password", true)] public void RegisterViewModel_Should_Be_Validated(string email, string password, string confirmPassword, bool expected) { var vm = new RegisterViewModel() { Email = email, Password = password, ConfirmPassword = confirmPassword, }; var result = this._validator.Validate(vm); result.IsValid.Should().Be(expected); } `
Any validator implementing the
IValidator
interface has theValidate()
method that actually performs the validation. Like above, the model is validated and tested. However, this conducts validation for whole properties in the model. If I want to test only one property, what can I do?FluentValidation
provides a few helper methods for this case.ShouldHaveValidationErrorFor()
ShouldNotHaveValidationErrorFor()
ShouldHaveChildValidator()
Therefore, testing one property can be written like:
`[Test] [TestCase("password", false)] [TestCase(null, true)] public void Password_Should_Be_Validated(string password, bool exceptionExpected) { var validator = this._validator as RegisterViewModelValidator; try { validator.ShouldNotHaveValidationErrorFor(p => p.Password, password); } catch (ValidationTestException ex) { if (exceptionExpected) { Assert.Pass(); } else { Assert.Fail(ex.Message); } } catch (Exception ex) { Assert.Fail(ex.Message); } } `
The
Password
property of theRegisterViewModel
model has a validation rule inRegisterViewModelValidator
class. Hence, it will be tested. The helper method,ShouldNotHaveValidationErrorFor()
, will be passed, if the validation succeeds. However, if the validation fails, it will throw theValidationTestException
exception. If this is expected, it passes the test; otherwise it fails the test. Likewise, theConfirmPassword
property can be tested like below:`[Test]
[TestCase(“password”, “password”, false)]
[TestCase(“password”, “different”, true)]
public void ConfirmPassword_Should_Be_Validated(string password, string confirmPassword, bool exceptionExpected)
{
var validator = this._validator as RegisterViewModelValidator;
try
{validator.ShouldNotHaveValidationErrorFor( p => p.ConfirmPassword, new RegisterViewModel() { Password = password, ConfirmPassword = confirmPassword });
}
catch (ValidationTestException ex)
{if (exceptionExpected) { Assert.Pass(); } else { Assert.Fail(ex.Message); }
}
catch (Exception ex)
{Assert.Fail(ex.Message);
}
}
So far, we’ve take a brief look at using FluentValidation
library for our ASP.NET MVC web app. Instead of scattering those validation rules all over the places, we can place them into one spot so that we can get benefits, in terms of maintainablilty.
One of downsides using FluentValidation
is that it supports Web API with many limitations. Of course, we can validate models through Web API with many walkarounds. I don’t think, however, it’s efficient. According to the good news from the library creator/maintainer, Jeremy Skinner, he has been focusing on ASP.NET MVC 6 with new MVC/Web API features. So, we hope the next version of FluentValidation
will fully support both MVC and Web API.