16 Jul 2011, 23:05

Enable Custom Fluent Validation Validators on the Client Side

Fluent Validation is a great, powerful, fluent (obviously) validation library for .NET. It does a very good job of simplifying and applying validation rules to your models. It also does a great job melding with MVC3. Combine that with jQuery Validate and jquery.validate.unobtrusive, and you have some really simple, really powerful validation that mirrors itself on the client-side, effectively preventing a user to make a round-trip server request for a form that’s going to fail validation anyway. All great stuff!

That simplification, though, is quite possibly its downfall. If you’re doing any kind of complex validation, while Fluent can probably accommodate you, getting those rules to the client side becomes a bit more tricky. Both Fluent and the jQuery Validate libraries are extensible, but Fluent won’t send most of its complex rules to the client side. So what do you do when you have more complex validation and you still want the slick client-side validation? It turns out, you do quite a lot. But the result is fantastic. Let’s take a look at creating a custom PropertyValidator for Fluent, registering it with Fluent using FluentValidationModelValidatorProvider, creating an extension method to give us the slick chaining with the rest of the Fluent validators, and finally wiring it all up on the client side for jQuery’s Validate libraries.

Let’s get to it!

This is a VERY trivial example just to illustrate a pattern to implement your own more complex validation logic. Let’s start by taking a look at creating a custom PropertyEvaluator. In this case, we’re going to create one that requires the value passed to it be equal to the string “foo”.

internal interface IEqualsFooValidator { }

public class EqualsFooValidator : PropertyValidator, IEqualsFooValidator {
	private PropertySelector _propertyFunc;

	public EqualsFooValidator(PropertySelector propertySelector)
		: base(() => "EqualsFooValidator Error") {
		_propertyFunc = propertySelector;
	}

	protected override bool IsValid(PropertyValidatorContext context) {
		string value = (string)_propertyFunc(context.Instance);

		if (string.Equals(value, "foo")) {
			return true;
		}
		return false;
	}
}

Pretty straight forward stuff there. We have our EqualsFooValidator as a Fluent PropertyValidator which takes a PropertySelector as its parameter. We use that parameter to get the value of the property that has to equal “foo”, do that comparison, and return our validator state. Ignore the fact that you can pull the property value from context.PropertyValue and skip passing in a PropertySelector. I’m showing more complex behavior here. :)

Now after we’ve got that set up, we have to create a FluentValidationPropertyValidator to use as our adaptor for MVC to get its little mitts on and generate our client side rules. A little magic like so:

public class EqualsFooValidatorAdaptor : FluentValidationPropertyValidator {
	private const string ValidationType = "equalsfoo";

	private IEqualsFooValidator FooValidator {
		get { return (IEqualsFooValidator)Validator; }
	}

	public EqualsFooValidatorAdaptor(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator)
		: base(metadata, controllerContext, rule, validator) {
		ShouldValidate = false;
	}

	public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() {
		if (!ShouldGenerateClientSideRules()) yield break;

		var formatter = new MessageFormatter().AppendPropertyName(Rule.PropertyName);
		string message = formatter.BuildMessage(Validator.ErrorMessageSource.GetString());

		yield return new ModelClientValidationRule {
			ValidationType = ValidationType,
			ErrorMessage = message
		};
	}
}

The magic here is in GetClientValidationRules, which is returning our ValidationType. ValidationType is what we want our client-side rule to be called and what we’ll wire up for jQuery Validate and unobtrusive.

Now that we have our validator and adaptor, let’s tie those up to our model by creating some extenions for us. In your typical extension method fashion, you’ll have this little number:

 public static IRuleBuilderOptions<TModel, TProperty> EqualsFoo<TModel, TProperty>(
	this IRuleBuilder<TModel, TProperty> ruleBuilder,
	PropertySelector propertySelector) {
		return ruleBuilder.SetValidator(new EqualsFooValidator(propertySelector));
}

Again, nothing terribly special here, just setting the validator from the ruleBuilder, and returning itself so we can chain like we normally do with Fluent.

That’s all we’ve got for the server side stuff. Now we have the client-side things to wire up. That voodoo (which isn’t really voodoo) looks like so:

jQuery.validator.addMethod('equalsfoo', function (value, element) {
	var fooVal = $(element).val();

	if (fooVal == "foo") {
		return true;
	}
	return false;
}, jQuery.validator.messages.equalsfoo);

jQuery.validator.unobtrusive.adapters.addBool("equalsfoo");

This tells jQuery Validate and unobtrusive to look for our validator, and how to handle it.

Now that we’ve got all of the functional parts there, we have to make sure that Fluent knows to register our new custom rule. We do that in our Global.asax like this:

var provider = new FluentValidationModelValidatorProvider(new AttributedValidatorFactory());
provider.Add(typeof(EqualsFooValidator), (metadata, context, rule, validator) => new EqualsFooValidatorAdaptor(metadata, context, rule,validator));
ModelValidatorProviders.Providers.Add(provider);
comments powered by Disqus