5

I have a ViewModel class like this:

class CaseModel {
    public Boolean     ClientPresent { get; set; }
    public ClientModel Client        { get; set; }
}

class ClientModel {
    [Required]
    public String      FirstName     { get; set; }
    [Required]
    public String      LastName      { get; set; }
}

The view page consists of a <input type="checkbox" name="ClientPresent" /> and a Html.EditorFor( m => m.Client ) partial view.

The idea being that when the user if providing information about a case (a business-domain object) that they can choose to not specify any information about the client (another biz object) by unchecking the ClientPresent box.

I want ASP.NET MVC to not perform any validation of the child ClientModel object - however the CaseModel.Client property is automatically populated when a form is POSTed back to the server, but because FirstName and LastName aren't (necessarily) provided by the user it means it fails the [Required] validation attributes, consequently ViewData.ModelState.IsValid returns false and the user gets a validation error message.

How can I get it so CaseModel.Client will not be validated if CaseModel.ClientPresent is false?

Note that ClientModel is a fully independent ViewModel class and is used elsewhere in the application (such as in the ClientController class which lets the user edit individual instances of Clients).

1
  • I updated the answer hope that helps Commented Jun 14, 2012 at 3:19

2 Answers 2

3

I recognise that my problem is not to do with binding but actually with validation: by keeping the values it means the same form fields will be populated when the user reloads the page, I just needed the validation messages to be discarded as they weren't applicable.

To that end I realised I can perform the model property validation, but then use some custom logic to remove the validation messages. Here's something similar to what I did:

public class CaseModel {
    public void CleanValidation(ModelStateDictionary dict) {
        if( this.ClientPresent ) {
            dict.Keys.All( k => if( k.StartsWith("Client") dict[k].Errors.Clear() );
        }
    }
}

(Obviously my actual code is more robust, but you get the general idea)

The CleanValidation method is called directly by the controller's action method:

public void Edit(Int64 id, CaseModel model) {
    model.CleanValidation( this.ModelState );
}

I can probably tidy this up by adding CleanValidation as a method to a new interface IComplexModel and having a new model binder automatically call this method so the controller doesn't need to call it itself.

Update:

I have this interface which is applied to any ViewModel that requires complicated validation:

public interface ICustomValidation {

    void Validate(ModelStateDictionary dict);
}

In my original example, CaseModel now looks like this:

 public class CaseClientModel : ICustomValidation {

      public Boolean ClientIsNew { get; set; } // bound to a radio-button
      public ClientModel ExistingClient { get; set; } // a complex viewmodel used by a partial view
      public ClientModel NewClient { get; set; } // ditto

      public void Validate(ModelStateDictionary dict) {

          // RemoveElementsWithPrefix is an extension method that removes all key/value pairs from a dictionary if the key has the specified prefix.
          if( this.ClientIsNew ) dict.RemoveElementsWithPrefix("ExistingClient");
          else                   dict.RemoveElementsWithPrefix("NewClient");
      }
 }

The validation logic is invoked by OnActionExecuting in my common BaseController class:

protected override void OnActionExecuting(ActionExecutingContext filterContext) {
    base.OnActionExecuting(filterContext);
    if( filterContext.ActionParameters.ContainsKey("model") ) {

        Object                    model = filterContext.ActionParameters["model"];
        ModelStateDictionary modelState = filterContext.Controller.ViewData.ModelState; // ViewData.Model always returns null at this point, so do this to get the ModelState.

        ICustomValidation modelValidation = model as ICustomValidation;
        if( modelValidation != null ) {
            modelValidation.Validate( modelState );
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

2

You have to create a custom model binder by inheriting from the default model binder.

  public class CustomModelBinder: DefaultModelBinder
  {
    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
    {
      if (propertyDescriptor.Name == "Client")
      {
          var clientPresent = bindingContext.ValueProvider.GetValue("ClientPresent");

          if (clientPresent == null || 
                string.IsNullOrEmpty(clientPresent.AttemptedValue))
              return;
      }

      base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
    }
  }

Global.asax.cs

ModelBinders.Binders.Add(typeof(CaseModel), new CustomModelBinder());

2 Comments

This isn't necessary to complete the answer, but I'd recommend bindingContext.ValueProvider to access values rather than Request.Form. It's a good practice to decouple binding from getting values, so you can seamlessly move between, say, form post and posting JSON using AJAX.
This has the problem of discarding submitted Client data when the request is received by the ASP.NET code rather than merely disabling the validation of said data.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.