4

I'm experiencing following issue: if I submit a form that contains checked value in checkbox to the api controller via AJAX, ModelState object says it's invalid.

Prerequisites:

  1. Visual Studio 2012
  2. ASP.NET MVC 4 Final
  3. Latest jQuery and jQuery unobtrusive validation stuff (versions 1.8.2 and 1.9.0.1)

Steps to reproduce:

  1. Created a ViewModel for form, containing Boolean field:
    public class CheckboxViewModel
    {
    [Display(Name="Test checkbox")]
    public bool TestCheckbox { get; set; }
    }
    
  2. Created controller action that simply prepares ViewModel and renders form:
    public ActionResult Index()
    {
        return View(new CheckboxViewModel());
    }
    
  3. Created a view with form
    @using (Ajax.BeginForm("Post", "api/Values", null, new AjaxOptions { HttpMethod = "POST" }, new { id = "form" }))
    {
        @Html.ValidationSummary(true)
        @Html.LabelFor(model => model.TestCheckbox)
        @Html.EditorFor(model => model.TestCheckbox)
        @Html.ValidationMessageFor(model => model.TestCheckbox)
        <input type="submit" value="Submit" />
    }
    
  4. Created Web API action that checks if modelstate is valid and return status code 200,otherwise 400:
    public HttpResponseMessage Post(CheckboxViewModel model)
    {
        if (!ModelState.IsValid)
            return new HttpResponseMessage(HttpStatusCode.BadRequest);
        return new HttpResponseMessage(HttpStatusCode.OK);
    }
    

I've done some googling and I know that editor for checkbox renders additional hidden field, but it seems to be ok and is needed for model binding. So, when I uncheck checkbox and submit form everything works fine, server responds with status 200, thanks to hidden field, I suppose. However, when I make checkbox checked and submit form, server responds with status 400, meaning that ModelState is in invalid state. ModelState in this case contains error with empty ErrorMessage and following Exception message:

{System.InvalidOperationException: The parameter conversion from type 'System.Collections.Generic.List`1[System.String]' to type 'System.Boolean' failed because no type converter can convert between these types.
   в System.Web.Http.ValueProviders.ValueProviderResult.ConvertSimpleType(CultureInfo culture, Object value, Type destinationType)
   в System.Web.Http.ValueProviders.ValueProviderResult.UnwrapPossibleArrayType(CultureInfo culture, Object value, Type destinationType)
   в System.Web.Http.ValueProviders.ValueProviderResult.ConvertTo(Type type, CultureInfo culture)
   в System.Web.Http.ValueProviders.ValueProviderResult.ConvertTo(Type type)
   в System.Web.Http.ModelBinding.Binders.TypeConverterModelBinder.BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)}

I have no idea how to handle this properly. Should I create custom model binder? Or am I missing something? Any help would be appreciated. Thanks.

I've created solution that reproduces my issue.

UPDATE: I've figured out that form actually contains values TestCheckbox: true and TestCheckbox:false, so I think this maybe some kind affects binder and it throws an exception. Still have no options how to workaround this.

sigurd
  • 3,071
  • 4
  • 29
  • 37

2 Answers2

2

I ran into the same problem sending a form to a Web API with JavaScript. Here is a basic fix you can use to work around the problem. I know it's kind of simplistic or even ugly - and not very practical if you have lots of checkboxes. However, the idea is simple and could easily be extended to meet your requirements.

Add this before you post the form (using jQuery):

if ($('[name="MyCheckbox"]:checked').length > 0)
    $('[name="MyCheckbox"]:hidden').detach();
else if ($('[name="MyCheckbox"]:hidden').length < 1)
    $('#myForm').append('<input type="hidden" name="MyCheckbox" value="false" />');

// Etc...
$.ajax({ });

So you remove the hidden field if the checkbox is checked, and you add it if the checkbox isn't checked and the hidden field has already been deleted.

See this Fiddle: http://jsfiddle.net/Hsfdg/

Knelis
  • 6,782
  • 2
  • 34
  • 54
  • Thanks for you reply. Yeah, that works, though extra jquery stuff doesn't help to write clean code. :) I'll accept your answer, but I ended up with throwing `EditorFor` away and use raw html, because I decided to use knockoutjs and clean javascript/jquery. – sigurd Oct 17 '12 at 04:30
1

I tried to implement this

if ($('[name="MyCheckbox"]:checked').length > 0)
    $('[name="MyCheckbox"]:hidden').detach();
else if ($('[name="MyCheckbox"]:hidden').length < 1)
    $('#myForm').append('<input type="hidden" name="MyCheckbox" value="false" />');

But ended up the model property being removed when hidden check box is detached.

Instad set the hidden chechbox value to true results in setting the model property to true the value expected else false by default.

if ($('[name="MyCheckbox"]:checked').length > 0)
    $('[name="MyCheckbox"]:hidden').val(true);
Desmond
  • 1,308
  • 1
  • 19
  • 27