0
votes

What I'm doing: I have 3 combo boxes. The selection in the first 2 boxes determines the list in the 3rd box. Whenever a selection is made in either of the first 2 boxes, I run some jQuery that makes an $.ajax call to get the list for the 3rd box. The 3rd box is my "Item Description" box.

The problem: The jquery ajax call works. When I pick the values in the first 2 boxes, the Item Descriptions are loaded into the 3rd box. However, when I try to submit the form, the ModelState.IsValid = false and the ModelState error message says, "Item Description is required."

This is from the meta data for my model:

[Required(ErrorMessage = "Item Description is required.")]
public int ItemDescriptionID { get; set; }

I don't want the user to be able to submit the form without selecting a description. I could just validate after submission, but I was hoping MVC would do the validation.

Here's the JavaScript I am using...

function getModels() {
    catId = $('#ItemModel_ItemCategoryID').val();
    manuId = $('#ItemModel_ItemManufacturerID').val();

    // remove all of the current options from the list
    $('#ItemDescriptionID').empty();

    // send request for model list based on category and manufacturer
    var jqxhr = $.ajax({
        url: '/Threshold/GetModels',
        type: 'POST',
        data: '{ CategoryID: ' + catId.toString() + ', ManufacturerID: ' + manuId.toString() + ' }',
        contentType: 'application/json',
        dataType: 'json',
        cache: false,
        async: true
    });

    // received list of models...
    jqxhr.done(function (data) {
        if (data == null) return;

        try {
            var ddl = $('#ItemDescriptionID');

            // add each item to DDL
            $.each(data, function (index, value) {
                ddl.append($('<option/>', {value: data[index].ItemDescriptionID}).html(data[index].ItemDescription))
            });
        }
        catch (result) {
            alert("Done, but with errors! " + result.responseText);
        }
    });

    // failed to retrieve data
    jqxhr.error(function (result) {
        alert("Error! Failed to retrieve models! " + result.responseText);
    });
}

So what am I doing wrong? The form will submit if I remove the Required metadata tag. Also, after the form submits, I am able to grab the ID from the dropdown and everything works. The problem is with the modelstate/validation somehow.

EDIT:

This is my Razor for the description box...

@Html.DropDownListFor(m => m.ItemDescriptionID, new SelectList(new List<SelectListItem>()) )
@Html.ValidationMessageFor(m => m.ItemDescriptionID)
1
When you say "When I pick the values in the first 2 boxes, the Item Descriptions are loaded into the 3rd box" is the description box selected by the user after being populated? Or just click submit after description option box is populated without selecting any value? - locorecto
The description box is selected by the user before the user clicks submit. If nothing is selected in the description box, I do not want the user to submit. - acbarberi

1 Answers

1
votes

If I understand your question correctly the problem you perceive is that even if the user hasn't selected anything valid from your dropdown they can still post to the server. The Model appears to be validating correctly, when the user hasn't selected anything ModelState.IsValid is false and you get the correct error message. But you want to avoid the data being posted at all if a selection hasn't been made.

The issue here is that ModelState and all the MVC validation is actually done server side. So in order for your application to detect that the user's choices aren't valid according to your model's Data Annotations a form submit really is required.

What you want in order to stop users posting is client side validation, which is not part of MVC Model validation but MVC 4 has built in support for it.

Built-in MVC 4 client validation

In order to enable client side validation with the built-in MVC 4 jQuery validation do the following.

  • Enable MVC client validation support

    Ensure that both "Unobtrusive JavaScript" (which will output the required HTML attributes) and "Client side validation" (which will wire up the javascript).

    In web.config (for entire site)

    <appSettings>
        <add key="ClientValidationEnabled" value="true"/>
        <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
    </appSettings>
    

    Or in code - global.asax.cs, Views or Controllers depending on the scope you want

    HtmlHelper.ClientValidationEnabled = true;
    HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
    
  • Include the required scripts in the Views

    MVC 4 has prepared a bundle including jQuery validation and the unobtrusive validation scripts. This must be added after the jQuery bundle.

    @Scripts.Render("~/bundles/jqueryval")
    

Always check ModelState.IsValid

Even if you have client-side validation enabled you have to perform server-side validation of posted data before you perform any actions. Whether the data that a user submits is valid or invalid your ActionMethod will still execute... So you need to check IsValid before you take action on the data that has been submitted.

      [HttpPost]
      public ActionResult ActionMethodName(ModelClass modelParameterName)
      {
       //exit directly on invalid data
       if(!ModelState.IsValid) return View(modelParameterName);
       //okay to save data etc...
       ...
       ...
       //return whatever makes sense for your scenario
       return View(modelParameterName); 
      }

Other options

You could also use jQuery validation manually or another third party javascript validation library.

Also custom javascript form validation is an option - return false from a function which is called from the form's submit event.

HTML5 has support for client-side validation attributes ('required' in your case) although not all browsers implement it.

If you perform validation yourself without using the MVC 4 unobtrusive scripts you should turn of the built-in feature to avoid conflicts and reduce the size of the returned HTML.

    <appSettings>
        <add key="ClientValidationEnabled" value="false"/>
        <add key="UnobtrusiveJavaScriptEnabled" value="false"/>
    </appSettings>

Or in code where you can control the scope you want it to apply to.

    HtmlHelper.ClientValidationEnabled = false;
    HtmlHelper.UnobtrusiveJavaScriptEnabled = false;