« Blog Home

Fluent Validation

Posted on 1/24/2010 at 3:16pm

I've recently taken an interest in some of the powerful Fluent-ly built libraries for C#. Fluent NHibernate is now my O/RM of choice and I'm really trying hard to find an excuse to implement Autofac, a Fluent Inversion of Control container, in one of my projects (as an aside: I still haven't found the tipping point that justifies introducing an IoC container).

Object validation is one area that I thought could benefit from a Fluent implementation. Fluent Validation provides a more expressive means of enforcing both data and business rules, capturing them in a way that is expressly human-readable.

In it's simplest form, a validation rule could be enforced as such:

var validator = new Validator<SomeModelObjectType>(someModelObjectInstance);

validator.Validate(m => m.SomePropertyThatCantBeEmpty).IsNotEmpty();

If this validation rule fails, the name of the property and a default error message will be placed in a Dictionary accessible via the validator instance. The validation result can also be manipulated in the following ways:

validator.Validate(m => m.SomePropertyThatCantBeEmpty)
.IsNotEmpty()
.WithMessage("You better put something here!");

Or

validator.Validate(m => m.SomePropertyThatCantBeEmpty)
.IsNotEmpty()
.AsBoolean();

Or

validator.Validate(m => m.SomePropertyThatCantBeEmpty)
.IsNotEmpty()
.WithMessage("You better put something here!")
.AsBoolean();

The WithMessage() method provides a way to override the default error value and AsBoolean() is useful for conditional statements.

This covers the basics. Here is a simple real-world example that we'll use to go into a little more depth:

public bool Validate()
{
var validator = new Validator<AccountCreationModel>(this, Errors);

return validator.Validate(m => m.Password)
.IsNotEmpty()
.WithMessage("Password is required")
.AsBoolean()

& validator.Validate(m => m.ConfirmPassword)
.IsEqualTo(m => m.Password)
.WithMessage("Passwords must match")
.AsBoolean()

& validator.Validate(m => m.Email)
.IsEmailFormat()
.WithMessage("Email must be in the proper format")
.AsBoolean()

& validator.Validate(m => m.Email)
.IsNotEmpty()
.WithMessage("Email is required")
.AsBoolean()

&& validator.Validate(m => m.Email)
.AccountIsAvailable(Repository)
.WithMessage("An Account with this email address already exists")
.AsBoolean();
}

This validates an account-creation form model that consists of three fields: Email, Password, and Confirm Password. I'm passing into the Validator constructor an Error dictionary that will be used further downstream to report validation errors to the user. Notice the AccountIsAvailable method that takes a Repository object; this is an example of how Fluent Validation can be used to enforce data-centric rules.

At the core, there are four classes at work here:

  1. Validator<T>
  2. ValidationAction<T, TResult>
  3. ValidationResult
  4. ValidationActionExtensions

The Validator class provides the Validate() method that kicks everything off:

public class Validator<T>
{
public Dictionary<string, string> ErrorDictionary
{
get;
private set;
}

private T ObjectToValidate
{
get;
set;
}

public Validator(T objectToValidate) : this(objectToValidate, null) { }

public Validator(T objectToValidate, Dictionary<string, string> errorDictionary)
{
ErrorDictionary = errorDictionary ?? new Dictionary<string, string>();
ObjectToValidate = objectToValidate;
}

public ValidationAction<T, TResult> Validate<TResult>(Expression<Func<T, TResult>> propertyToVerify)
{
var expression = ValidationHelper.AssertExpressionIsNotNull(propertyToVerify);

TResult value = propertyToVerify.Compile()(ObjectToValidate);

return new ValidationAction<T, TResult>(value, ObjectToValidate, expression, ErrorDictionary);
}
}

The call to Validate() returns an instance of the ValidationAction<T, TResult> class:

public class ValidationAction<T, TResult> : IValidationLeaf
{
public TResult PropertyValue { get; private set; }
private MemberExpression Property { get; set; }
public T ObjectToValidate { get; private set; }

internal ValidationAction(TResult propertyValue,
T objectToValidate,
MemberExpression property,
Dictionary<string, string> errors)
{
PropertyValue = propertyValue;
Property = property;
ObjectToValidate = objectToValidate;
ErrorDictionary = errors;
}

public ValidationResult GenerateResult(bool isValid)
{

if (!isValid)
{
ErrorDictionary[ValidationHelper.GetFullPropertyName(Property)] =
ValidationHelper.GetFullPropertyName(Property) + " has errors";
}

return new ValidationResult(isValid,
ValidationHelper.GetFullPropertyName(Property),
ErrorDictionary);
}

#region IValidationLeaf Members

public Dictionary<string, string> ErrorDictionary { get; private set; }

#endregion
}

And the call to GenerateResult() returns an instance of ValidationResult:

public class ValidationResult : IValidationLeaf
{
private readonly bool _validationState;
private readonly string _propertyName;

internal ValidationResult(bool validationState,
string propertyName,
Dictionary<string, string> errors)
{
ErrorDictionary = errors;
_validationState = validationState;
_propertyName = propertyName;
}

public ValidationResult WithMessage(string message)
{
if (!_validationState)
{
ErrorDictionary[_propertyName] = message;
}

return this;
}

public bool AsBoolean()
{
return _validationState;
}

#region IValidationLeaf Members

public Dictionary<string, string> ErrorDictionary { get; private set; }

#endregion
}

So where, you might ask, are all the IsEmailFormat(), IsNotEmpty(), etc. methods?

These exist entirely as extension methods, the core of which live in ValidationActionExtensions. Here is an example of one such method:

public static ValidationResult IsNotEmpty<T>(this ValidationAction<T, string> validationAction)
{
return validationAction.GenerateResult(!string.IsNullOrEmpty(validationAction.PropertyValue));
}

These extension methods do whatever special work needs to be done to enforce the validation rule and then simply returns the result of the GenerateResult() method of ValidationAction.

Extension Methods allow you to hang domain-specific validation rules onto the Fluent Validation framework. The AccountIsAvailable() method used in the example above was simply an extension added within the namespace of the web application.

Another great advantage of using Extension Methods to encapsulate the validation logic is that you can specify the type of the property to which the method will apply via the TResult parameter of the ValidationAction that it extends. For example, the following made-up rule will only apply to model properties that are of type int:

public static ValidationResult IsGreaterThanFive<T>(this ValidationAction<T, int> validationAction)
{
bool isGreaterThanFive = validationAction.PropertyValue > 5;
return validationAction.GenerateResult(isGreaterThanFive);
}

Finally, with some small changes, the ValidationResult class can be extended to perform special actions in the case of a failed validation rule (by making the _validationState and _propertyName fields publicly accessible).

For future development, I would love to apply the Fluent Validation structure such that validation can be mapped as a configuration, analogous to the way properties and relationships are mapped to Value Objects with Fluent NHibernate

The project is hosted here: http://code.google.com/p/sharpvalidation/

0 Comments

Leave a Comment