views:

240

answers:

1

I currently have a form that I am building that needs to support two different versions. Each version might use a different subset of form fields. I have to do this to support two different clients, but I don't want to have entirely different controller actions for both.

So, I am trying to come up with a way to use a strongly typed model with validation attributes but have some of these attributes be conditional.

Some approaches I can think of is similar to steve sanderson's partial validation approach.

Where I would clear the model errors in a filter OnActionExecuting based on which version of the form was active.

The other approach I was thinking of would to break the model up into pieces using something like

class FormModel
{

public Form1 Form1Model {get; set;}
public Form2 FormModel {get; set;}
}

and then find some way to just validate the appropriate property depending on the version. There would also be common properties on the model that apply to both which would be always validated.

Does anyone have a good suggestion on this?

+2  A: 

I've had reasonable success with using ModelBinders to remove errors I don't need from the ModelState.

Here's an example for an Address model binder. In the UI I have a <SELECT> for US States, but this is hidden when the country is not 'US' in favor of a <INPUT ID=StateOrProvince> textbox.

The modelbinder looks at the country and removes the unneeded values.

As far as doing this with validation attributes - I'd think you'd quickly get yourself in a big mess unless you have very simple rules.

Tip: You can have as many modelbinders as you want to discreet pieces of your overall model. For instance - I have 2 Address objects in my model and they each automatically get this behavior applied.

Register:

ModelBinders.Binders[typeof(UI.Address)] = new AddressModelBinder();

ModelBinder:

public class AddressModelBinder : DefaultModelBinder
{
    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        base.OnModelUpdated(controllerContext, bindingContext);

        // get the address to validate
        var address = (Address)bindingContext.Model;

        // remove statecd for non-us
        if (address.IsUSA)
        {
            address.StateOrProvince = string.IsNullOrEmpty(address.StateCd) ? null : CountryCache.GetStateName(address.StateCd);
            bindingContext.ModelState.Remove(bindingContext.ModelName + ".StateOrProvince");
        }
        else
        {
            address.StateCd = null;
            bindingContext.ModelState.Remove(bindingContext.ModelName + ".StateCd");
        }


        // validate US zipcode
        if (address.CountryCode == "US")
        {
            if (new Regex(@"^\d{5}([\-]\d{4})?$", RegexOptions.Compiled).Match(address.ZipOrPostal ?? "").Success == false)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".ZipOrPostal", "The value " + address.ZipOrPostal + " is not a valid zipcode");
            }
        }

        // all other modelbinding attributes such as [Required] will be processed as normal
    }
}
Simon_Weaver
I like this idea Simon. I will give it a go.
Jeff
@jeff i didn't like it at first - so I'm glad you already do. working quite well for the simple tasks i'm putting it to
Simon_Weaver