FluentValidation 1.2 beta 2 is now available to download. This release focuses mainly on integration with ASP.NET MVC 2.

Since my post on Limitations of MVC2’s ModelValidatorProviders ASP.NET MVC2 RC2 has been released which addresses some of the issues that I raised in the post.

Firstly, validation in MVC2 RC2 now performs model-validation rather than input validation. This is much more in line with how FluentValidation works which makes the FluentValidation integration much more consistent with the built-in validation.

Secondly, it is now possible to disable the DataAnnotationsModelValidatorProvider’s “greedy” required rule. Out of the box, the DataAnnotationsModelValidatorProvider will *always* validate non-nullable value types, irrespective of whether the property is decorated with a [Required] attribute. This meant that you had to un-register the DataAnnotations provider if you didn’t want to get duplicate errors. It is now possible to turn this off by calling DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false; in your application startup routine.

Finally, I added the ability to be able to execute some of FluentValidation’s Property Validators without needing to validate the entire object. This means it is now possible to stop the default “A value was required” message from being added to ModelState.

Enabling the FluentValidationModelValidatorProvider

After downloading the 1.2 beta 2 release you’ll need to add a reference to FluentValidation.dll and FluentValidation.Mvc.dll

To enable the FluentValidationModelValidatorProvider you will need to add it to the ModelValidatorProviders collection in your application startup routine:

protected void Application_Start() {
	RegisterRoutes(RouteTable.Routes);
 
	DataAnnotationsModelValidatorProvider
		.AddImplicitRequiredAttributeForValueTypes = false;
 
	ModelValidatorProviders.Providers.Add(
		new FluentValidationModelValidatorProvider(new AttributedValidatorFactory()));
}

Note that you need to pass an IValidatorFactory to the FluentValidationModelValidatorProvider so that it knows how to create your validator instances. I’m using the AttributedValidatorFactory in this example, but in a real application you would probably use an implementation that wraps an IoC container instead.

Next, you can create your model object and associated validator:

[Validator(typeof(PersonValidator))]
public class Person {
	public int Id { get; set; }
	public string Name { get; set; }
	public string Email { get; set; }
	public int Age { get; set; }
}
 
public class PersonValidator : AbstractValidator<Person> {
	public PersonValidator() {
		RuleFor(x => x.Id).NotNull();
		RuleFor(x => x.Name).Length(0, 10);
		RuleFor(x => x.Email).EmailAddress();
		RuleFor(x => x.Age).InclusiveBetween(18, 60);
	}
}

Finally, you can create the controller and associated view:

public class PeopleController : Controller {
	public ActionResult Create() {
		return View();
	}
 
	[AcceptVerbs(HttpVerbs.Post)]
	public ActionResult Create(Person person) {
 
		if(! ModelState.IsValid) {
			return View("Create", person);
		}
 
		TempData["notice"] = "Person successfully created";
		return RedirectToAction("Index");
 
	}
}
 
<%= Html.ValidationSummary() %>
 
<% using (Html.BeginForm()) { %>
	Id: <%= Html.TextBoxFor(x => x.Id) %><%= Html.ValidationMessageFor(x => x.Id) %>
	<br />
	Name: <%= Html.TextBoxFor(x => x.Name) %><%= Html.ValidationMessageFor(x => x.Name) %>			
	<br />
	Email: <%=Html.TextBoxFor(x => x.Email) %><%= Html.ValidationMessageFor(x => x.Email) %>
	<br />
	Age: <%= Html.TextBoxFor(x => x.Age) %><%= Html.ValidationMessageFor(x => x.Age) %>
 
	<br /><br />
 
	<input type="submit" value="submit" />
<% } %>

Now when you post the form MVC’s DefaultModelBinder will validate the Person object using the FluentValidationModelValidatorProvider.

Clientside Validation

This release also has preliminary support for clientside validation. This is enabled by calling <% Html.EnableClientValidation(); %> in your views. At the moment, only the NotNull, NotEmpty, RegularExpression and Length validators are supported.

Note that if you try to use the clientside validation support with the default error messages, you will see error messages like this:

'{PropertyName}' must not be empty.

This is because FluentValidation uses templated error messages but the MVC Framework’s clientside validation runner does not know how to correctly format these. For now, the only workaround is to explicitly specify the error message:

RuleFor(x => x.Surname).NotNull().WithMessage("'Surname' must not be empty");

For FluentValidation 1.3 I will consider providing a customised version of MVC’s MicrosoftMvcValidation.js that knows how to properly format FluentValidation error templates.

Metadata Support

Out of the box, MVC2 allows you to use the DataAnnotations attributes to provide metadata for editor/display templates. This release of FluentValidation allows you to use extra extension methods on your validator classes to define metadata using method chaining.

Firstly, you will need to replace the DataAnnotationsModelMetadataProvider with the new FluentValidationModelMetadataProvider in your Application_Start:

ModelMetadataProviders.Current = new FluentValidationModelMetadataProvider(
	new AttributedValidatorFactory());

Now you can start using the metadata extension in your validator classes (you will need to import the FluentValidation.Mvc.MetadataExtensions namespace):

public CustomerValidator() {
   RuleFor(x => x.Surname)
      .NotNull() //regular validator
      .UIHint("Surname"); //MVC Metadata
}

The FluentValidationModelMetadataProvider currently supports the following metadata-related extension methods which correspond to the DataAnnotations attributes with the same name:

  • HiddenInput
  • UIHint
  • Scaffold
  • DataType
  • DisplayName
  • DisplayFormat
  • ReadOnly

Edit: 8th FebNote that there is a bug in the current beta build where MetaData won’t work in some circumstances. This is already fixed for the next beta, so you may want to use the latest binaries from the build server.

As usual, binaries are on CodePlex and the source is on GitHub.