3

Overview of the problem:

I want to have some conditional logic in my form. The user makes a choice with a radio button and depending on the selection I want to hide some fields and show some other fields. This should also affect validation (hidden fields shouldn't be validated). I think this is a typical problem, but I couldn't find any examples and my own solution seems to have a lot of plumbing.

My actual problem:

Let's start with the viewmodel classes (somewhat simplified for the needs of this question):

public class Scenario
{
   public Request Request { get; set; }
   public Response Response { get; set; }
   // … some other properties
}

public class Request
{
   //some properties
}

public class Response
{
    [Required]
    public string ResponseType { get; set; }

    [Required]
    public string State { get; set; }

    [Required]
    [NotZero] //this is my custom validation attribute
    public string ErrorCode { get; set; }

    public string ErrorDescr { get; set; }
}

In my create/edit view for the Scenario model I have a rather big form consisting of 3 tabs. On the 3rd tab I display a partial view based on the Response model. This is where I want the conditional logic. ResponseType property is the radio button on the form. It can have two values: NORMAL and ERROR. In the case of ERROR I want to display and validate ErrorCode and ErrorDescr properties. In the case of NORMAL I want to display and validate only the State property.

My solution:

  1. In the Response partial view I have some jquery .hide() and .show() calls hooked up to hide/show relevant input elements.
  2. I modified jquery unobtrusive validation script to stop it from validating hidden fields ( http://blog.waynebrantley.com/2011/01/mvc3-validates-hidden-fields-using.html )
  3. In the Scenario controller I have code like this:

    public ActionResult Edit(int id, Scenario scenario)
    {
       Response response=scenario.Response;
       if (response.ResponseType != null)
       {
          if (response.ResponseType == "NORMAL")
          {
             //in this case remove validation for errorcode
             this.ModelState.Remove("Response.ErrorCode");
          }
          else
          {
             //in this case remove validation for state
             this.ModelState.Remove("Response.State");
          }
       }
       if (ModelState.IsValid)
       {
           //map to entity and save to database
       }
    }
    

This is a whole lot of ugly plumbing (especially the controller code - removing items from ModelState with a string key... no type safety etc), surely there must be a better way?

2 Answers 2

1

You might try inheriting from IValidateableObject in your Response class and performing the conditional validation in Validate. Like so:

public class Response : IValidateableObject
{
    [Required]
    public string ResponseType { get; set; }
    public string State { get; set; }   
    public string ErrorCode { get; set; }
    public string ErrorDescr { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (ResponseType == "NORMAL")
        {
            if (State.IsNullOrWhiteSpace)
                yield return new ValidationResult("State is required", new [] { "State" });
        }

        // additional validations...
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Unfortunately validations made in this way don't work on the client side. I really don't like that because it introduces a two-phase validation effect (the user fills the form correctly according to client-side validation, submits the form, and hey, he gets some new errors back... bad user experience).
0

This is quite old now and I don't like this hanging around without an answer so I'll answer it myself.

The correct solution is to implement a custom validation attribute and also implement the IClientValidatable interface. This satisfies all the requirements - no plumbing inside the controller and consistent client side validation.

Nowadays I probably wouldn't even implement this myself - I would use an existing library like ExpressiveAnnotations which provides a nice and flexible RequiredIf attribute:

public class Response
{
    [Required]
    public string ResponseType { get; set; }

    [RequiredIf("ResponseType == 'NORMAL'"]
    public string State { get; set; }

    [RequiredIf("ResponseType == 'ERROR'"]
    public string ErrorCode { get; set; }

    public string ErrorDescr { get; set; }
}

The attribute works both on server side and on client side once you configure it by adding a few lines to Global.asax and including a javascript with the validators. You can read the details on the project page.

Comments

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.