Model Binding – is the ‘auto-magic’ step performed by the ASP.NET MVC framework to convert user submitted data (either http post values, querystring values or url route values) into a strongly typed model, used in your controller actions.
Out of the box, the MVC framework also allows you to set validation attributes on your models which are inspected at the model binding stage, meaning that your controller actions can inspect the ModelState.IsValid property to assess whether the user submitted data meet expectations. This attribute based approach to validation provides a clean way to handle validation (a cross-cutting concern) without introducing additional code into your controller action.
One of the features needed when putting together the Fortune Cookie Personalization Engine for EPiServer was to perform validation on a user submitted criteria value. As a reminder, in the context of the Personalization Engine, a criteria is an editor submitted string which is persisted and used by a ContentProvider to allow for a more granular method of content retrieval. For further background and explanation, check out one of my earlier posts – Personalization Engine – ContentProvider Criteria Models
In the full Personalization Engine domain model, criteria properties belong to IContentModel objects which are used to specify the user interface displayed to an editor to allow them to enter the criteria. Below is an example of a TextBoxCriteriaModel which renders as a textbox in the Admin interface.
The string value from this criteria input is posted to the controller action. However as the type of IContentModel depends on the value of the ContentProvider dropdown, validation attributes cannot be set directly on the model passed to/from this view as different concrete IContentModel types need to be able to specify different validation rules.
To provide validation of the composite IContentModel using validation attributes, we have to hook in to one of the extension points of the ASP.NET MVC framework and create a custom model binder.
Validating composite models
Our custom ModelBinder needs to perform the following tasks:
- Bind the incoming data against an AdminViewModel (the model passed from the user interface shown above).
- Obtain an instance of the specified ICriteriaModel and update the ICriteriaModel’s criteria property with the value posted by the form.
- Validate the composite (and now populated) ICriteriaModel
- Update the ModelState with the validation results from the composite model, along with the original parent model validation results
As the custom ModelBinder needs to perform all of the existing validation and binding that the DefaultModelBinder would, I’ve chosen to inherit from it and add the additional composite model validation logic into the overriden BindModel method. An instantiated IContentModel is obtained from the AdminViewModel, and the ModelValidator framework class allows us to validate the composite IContentModel, before updating the bindingContext.ModelState with an validation errors.
public class CriteriaValidationModelBinder : DefaultModelBinder { const string ValidationPropertyName = "Criteria"; public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext.Model != null) return bindingContext.Model; var model = base.BindModel(controllerContext, bindingContext); var adminViewModel = model as AdminViewModel; if (adminViewModel == null) return model; var criteriaValue = bindingContext.ValueProvider.GetValue(ValidationPropertyName); adminViewModel.CriteriaModel.Criteria = criteriaValue != null ? criteriaValue.AttemptedValue : string.Empty; ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => adminViewModel.CriteriaModel, typeof(ICriteriaModel)); ModelValidator compositeValidator = ModelValidator.GetModelValidator(modelMetadata, controllerContext); foreach (ModelValidationResult result in compositeValidator.Validate(null)) bindingContext.ModelState.AddModelError(ValidationPropertyName, result.Message); return model; } }
Our custom ModelBinder can be hooked into our application in Global.asax, or in an EPiServer IInitializableModule using the following method.
private void AddCriteriaValidationModelBinder() { ModelBinders.Binders.Add(typeof(AdminViewModel), new CriteriaValidationModelBinder()); }
And that’s it…. ModelBinders are an important piece of the MVC framework, and in the majority of scenarios you can rely on the DefaultModeBinder to handle all of your requirements. However creating your own ModelBinder for more advanced requirements is pretty straightforward, depending on your requirements for your binder