Julian Jelfs’ Blog

“Patch” model binding with Asp.Net MVC

Posted in MVC by julianjelfs on March 8, 2013

Let’s say you have an update Action on a controller in Asp.Net MVC that accepts a domain object which you use for updating something like this:

[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ViewResult UpdateCustomer(Customer model)
{
    //persist your model somehow etc etc
    return View(model);
}

When you use the DefaultModelBinding the MVC framework will create an instance of the Customer class and fill in any properties that it finds according to the registered ValueProviders (which will pull parameters from various places like the Form and the QueryString etc). This is all very well described here.

But what happens if you have more than one view of the data and sometimes not all of the data is passed back with the form? When might this happen? Perhaps you have a workflow where one user enters some of the data and another user completes the data. You could achieve this with two models and two views and two controller actions but you might have a dynamic view and just pass back the data that user is required to fill in (or has access to).

In this scenario, the DefaultModelBinder will cause us a problem because it creates a new instance of the Customer class to bind to. So suppose that user A has filled out the customer’s name and saved, but user B has to fill out the customer’s address but does not have access to the customer’s name (not very realistic I know). In this case the form posted by user B will not contain a value for the customer’s name, so that value will not be bound and user A’s data will get overwritten with a null value when the model is persisted.

You might be able to handle this with validation, but there is no point giving user B a validation error message relating to a field that he cannot see.

One way to deal with this is to create a custom model binder that loads the existing state of the model before binding rather than loading a blank instance of the type. It might look something like this:

public class CustomerModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, 
                                    ModelBindingContext bindingContext, Type modelType)
    {
        var session = ServiceLocator.Current.GetInstance<IDocumentSession>();
        var val = bindingContext.ValueProvider.GetValue("model.Id");    //this could be parameterised
        if (val != null)
        {
            var customer = session.Load<Customer>(val.AttemptedValue);
            if (customer != null)
            {
                return customer;
            }
        }
        return base.CreateModel(controllerContext, bindingContext, modelType);
    }
}

In this code, we are checking to see if the ValueProvider has a value for the key to our model object. If it does, we load the object (I’m using Raven but that doesn’t matter), if not we fallback to the DefaultModelBinder.

We then need to register this custom model binder for the specific type we are interested in at application start up;

ModelBinders.Binders.Add(typeof(Customer), new CustomerModelBinder());
And now, when user B’s partially complete POST arrives, it will be bound to the instance of the Customer object that user A created and we will not lose any data. Nice.
Advertisements

Dynamic validation with FluentValidation and MVC 4

Posted in MVC, Uncategorized by julianjelfs on March 7, 2013

FluentValidation is a very nice way to perform custom validations in .Net and integrating it into the built in validation system used in MVC 4 is relatively straightforward. But it is not immediately clear how you go about performing dynamic validation on a MVC model based on the state of that model. If you are implementing a workflow, for example, this is something that you may well want to do. For example, let’s say that you have the following model:

   1: public class Customer

   2: {

   3:     public int Id {get;set;}

   4:     public string Name {get;set;}

   5:     public string Email {get;set;}

   6:     public string Status {get;set;

   7: }

Suppose that you would like the Name to be mandatory if the Status property is set to “New”, but if the Status property is set to “Existing” you would like the Name and the Email to be mandatory. The standard integration of FluentValidation with MVC 4 allows you to correlate a controller action’s input parameter with a validator, by decorating the model type with a Validator attribute. It will then call the Validate method on that validator at the right point by plugging in to the MVC model validation infrastructure. So far this will only allow you to run the same validation regardless of context. The validation can be further customised by adding a CustomizeValidator attribute to the model parameter on your controller action.

The CustomizeValidator attribute allows you to override the properties to validate, the rule sets to validate and an interceptor to call before and after MVC validation takes place. To take advantage of model state to apply specific validation we first need to capture our different scenarios as rule sets in the validator as follows:

   1: public class CustomerValidator : AbstractValidator

   2: {

   3:     public CustomerValidator()

   4:     {

   5:             RuleSet("New", () => {

   6:             RuleFor(c => c.Name).NotEmpty();

   7:         });

   8:

   9:         RuleSet("Existing", () => {

  10:             RuleFor(c => c.Name).NotEmpty();

  11:             RuleFor(c => c.Email).NotEmpty();

  12:         });

  13:     }

  14: }

then attribute the model class with this validator:

   1: [Validator(typeof(CustomerValidator))]

   2: public class Customer ....

Create a validation interceptor to specify the correct selector on the ValidationContext based on the model state like this:

   1: public class CustomerValidatorInterceptor : IValidatorInterceptor

   2: {

   3:     public ValidationContext BeforeMvcValidation(ControllerContext controllerContext,

   4:                 ValidationContext validationContext)

   5:     {

   6:         if (validationContext.InstanceToValidate is Customer)

   7:         {

   8:             var customer = (Customer) validationContext.InstanceToValidate;

   9:             return new ValidationContext(customer, validationContext.PropertyChain,

  10:                 new RulesetValidatorSelector(customer.Status));

  11:         }

  12:         return validationContext;

  13:     }

  14:

  15:     public ValidationResult AfterMvcValidation(ControllerContext controllerContext,

  16:                 ValidationContext validationContext,

  17:                 ValidationResult result)

  18:     {

  19:         return result;

  20:     }

  21: }

  22:

and finally add this interceptor to your controller action via the CustomizeValidation attribute like this:

   1: public ViewResult UpdateCustomer(

   2:     [CustomizeValidator(Interceptor = typeof(CustomerValidatorInterceptor))]Customer model)

   3: {

   4:     ...

   5: }

And now we will execute the correct RuleSet according to the state of the Customer object and it will all integrate with the MVC validation infrastructure nicely.