开发者

What is the right way for creating conditional forms in ASP.Net MVC3?

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 jqu开发者_如何学JAVAery 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?


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...
    }
}


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜