A recent addition to the MvcContrib project are a set of HTML helpers that use a fluent interface written by Tim Scott.

One of the great things about these helpers is that you can change how the HTML is rendered by writing custom IMemberBehavior objects. For example, the helpers come with a set of attributes that you can use to decorate your model:

public class Customer {
   [MaxLength(50)]
   public string Name { get; set; }
}

Then, when you render your view, the HTML helper will automatically detect the presence of this attribute and render a “maxlength” attribute accordingly. For example, this textbox helper…

<%= this.TextBox(m => m.Customer.Name) %>

…would render this html:

<input type="text" maxlength="50" id="Customer_Name" name="Customer.Name" />

I wanted to take this a step further and integrate it with my validation library, so the HTML helpers will automatically pick up maxlength/required attributes from my validator classes.

Step 1: Custom Validator Class

The first stage was to create a metadata class to hold information about a validator.

public class PropertyModel {
	public int MaxLength { get; set; }
	public bool Required { get; set; }
	public PropertyInfo Property { get; private set; }
 
	public PropertyModel(PropertyInfo property) {
		Property = property;
	}
}

Next, I introduced a new interface, IPropertyDescriptor, that all my validator classes will implement. This interface defines the signature for a method that creates PropertyModel instances based on a PropertyInfo:

public interface IPropertyDescriptor {
	PropertyModel GetPropertyModel(PropertyInfo property);
}

Next, I added a custom base-class for all my validators which implements the above interface:

public abstract class Validator<T> : AbstractValidator<T>, IPropertyDescriptor {
	public PropertyModel GetPropertyModel(PropertyInfo property) {
		var model = new PropertyModel(property);
 
		var validators = this.OfType<IPropertyValidatorContainer<T>>().Where(x => x.Property == property).Select(x => x.Validator);
		foreach (var validator in validators) {
			if (HandleLengthValidator(validator, model)) continue;
			if (HandleRequiredValidator(validator, model)) continue;
		}
 
		return model;
	}
 
	private bool HandleLengthValidator(IPropertyValidator<T> validator, PropertyModel model) {
		var length = validator as ILengthValidator;
		if(length != null) {
			model.MaxLength = length.Max;
			return true;
		}
		return false;
	}
	private bool HandleRequiredValidator(IPropertyValidator<T> validator, PropertyModel model) {
		var required = validator as INotNullValidator;
		if(required != null) {
			model.Required = true;
			return true;
		}
		return false;
	}	
}

Essentially, the GetPropertyModel method iterates through all of the property validators to see if any required or max-length validators have been defined. If so, it records them in the PropertyModel object.

I can now define my validators as usual, but they now inherit from my new Validator<T> class, rather than AbstractValidator<T>.

public class CustomerValidator : Validator<Customer> {
   public CustomerValidator() {
     RuleFor(customer => customer.Name).Length(0, 50);
   }
}

Step 2: Custom Validator Factory

The next stage is to impelement a custom validator factory that can be used to instantiate validators. For this example, I’ll be using the Windsor IoC container.

Firstly, in my application startup routine I need to register all of my validators with Windsor:

var container = new WindsorContainer();
container.Register(AllTypes.Of(typeof(IValidator<>)).FromAssembly(typeof(CustomerValidator).Assembly).WithService.Base());

Next, I create a validator factory that uses Windsor (or more accurently, Microkernel) to create the validator instances.

public class ValidatorFactory : IValidatorFactory {
	private readonly IKernel container;
 
	public ValidatorFactory(IKernel container) {
		this.container = container;
	}
 
	public IValidator<T> GetValidator<T>() {
		return container.TryResolve<IValidator<T>>();
	}
 
	public IValidator GetValidator(Type type) {
		var genericType = typeof(IValidator<>).MakeGenericType(type);
		return (IValidator)container.TryResolve(genericType);
	}
}

Note that TryResolve is an extension method on IKernel that will attept to resolve a component and return null if it fails (by default, Windsor/Microkernel will throw an exception).

This validator factory can now be registered with the container:

container.Register(Component.For<IValidatorFactory>().ImplementedBy<ValidatorFactory>());

Step 3: The PropertyModelFactory

The next stage is to create a class that can be used to locate the correct validator from a PropertyInfo object (by using the validator factory) and return a corresponding PropertyModel.

public class PropertyModelFactory : IPropertyDescriptor {
   private IValidatorFactory validatorFactory;
 
   public PropertyModelFactory(IValidatorFactory validatorFactory) {
     this.validatorFactory = validatorFactory;
   }
 
  public PropertyModel GetPropertyModel(PropertyInfo property) {
     var descriptor = validatorFactory.GetValidator(property.ReflectedType) as IPropertyDescriptor;
     if(descriptor != null) {
        return descriptor.GetPropertyModel(property);
     }
     return null;
  }
}

Again, this needs to be registered with the container:

container.Register(Component.For<IPropertyDescriptor>().ImplementedBy<PropertyModelFactory>());

Step 4: Custom IMemberBehavior

Next, we need to create a custom IMemberBehavior that will make use of the PropertyModelFactory:

public class ValidatorMemberBehaviour : IMemberBehavior {
   private readonly IPropertyDescriptor descriptor;
 
   public ValidatorMemberBehaviour(IPropertyDescriptor descriptor) {
      this.descriptor = descriptor;
   }
 
   public void Execute(IMemberElement element) {
      if (element.ForMember == null) return;
 
      var property = element.ForMember.Member as PropertyInfo;
      if(property == null) return;
 
      var model = descriptor.GetPropertyModel(property);
      if (model == null) return;
 
      var maxLengthMethod = element.GetType().GetMethod("MaxLength");
 
      if(maxLengthMethod != null) {
         element.Builder.MergeAttribute("maxlength", model.MaxLength.ToString());				
      }
      if(model.Required) {
         element.Builder.AddCssClass("required");
      }
   }
}

MemberBehaviors are invoked when certain HTML helper objects are created, so this behaviour now needs to be registered with our ViewPage:

public class MyViewPage<T> : ModelViewPage<T> where T : class {
   public MyViewPage() 
     : base(new ValidatorMemberBehaviour(ServiceLocator.Resolve<IPropertyDescriptor>())) { }
}

Note that the ServicLocator static class is just a wrapper around Windsor.

So now the HTML Helpers will use my validator definitions to generate required/maxlength attributes. I think the extensibility of these helpers is incredibly powerful and I am planning on using them going forward in my next project.