Mark Johnson Mark Johnson - 11 days ago 5
C# Question

Not all values in MVC form being posted

A little help or advice if any of you can?
I have an MVC form (it's actually an Umbraco form/surface controller, but I don't know that that has any bearing on this), the model for which contains a list of objects being used to generate check-boxes using HTML helper methods; see the following post for how I am rendering these and receiving them in my child action: http://stackoverflow.com/a/20687405/1285676

All appears to be working well, except that although all check-boxes are being posted on submit, not all are being picked up by the model received by the ActionResult method in my controller. I have looked at the posted data using the network tools in chrome, and all appears to be ok.. (all check-boxes appear to be being posted back in the same format), so the million dollar question is, what could be preventing some values from being picked up in the model?

Here's a snipet of my form rendering the check-boxes, cut down as the rest should not be relevant (yes there is a submit button in the real thing..):

@model MemberPreferencesVM
@using (Html.BeginUmbracoForm<MemberAccountSurfaceController>("HandleDealerMemberPreferencesAccountUpdate", FormMethod.Post, new { @class = "", @id = "dealer-member-account-preferences-form" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
@for (var j = 0; j < Model.Brands.Count; j++)
{
if (Model.Brands[j].Division == Model.Divisions[i].Id.ToString())
{
var divisionSelected = Model.Divisions.Any(x => x.Id.ToString() == Model.Brands[j].Division) ? Model.Divisions.FirstOrDefault(x => x.Id.ToString() == Model.Brands[j].Division).Checked : false;
var checkboxAttr = new Dictionary<string, object> { { "class", "styled" }, { "data-division", Model.Brands[j].Division } };
var brandSelected = Model.Brands[j].Checked;

<div class="col-xs-4">
<label class="option @if (divisionSelected && brandSelected){<text>active</text>}">
<img src="@(Model.Brands[j].LogoUrl)" class="center-block" alt="@(Model.Brands[j].Name)" />
<span class="checkbox">
@Html.HiddenFor(model => model.Brands[j].Id)
@Html.HiddenFor(model => model.Brands[j].Name)
@Html.CheckBoxFor(model => model.Brands[j].Checked, checkboxAttr)
</span>
</label>
</div>
}
}
}


Slightly cut down version of the model:

public class MemberPreferencesVM: IValidatableObject
{
public MemberPreferencesVM()
{
init();
}
private void init()
{
Divisions = [Logic to fetch divisions];
Brands = [Logic to fetch brands];
}
public IList<DivisionVM> Divisions { get; set; }
public IList<BrandVM> Brands { get; set; }

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var pDivisions = new[] { "Divisions" };
var pBrands = new[] { "Brands" };
if (!Divisions.Any(x => x.Checked))
{
yield return new ValidationResult("Select at least 1 division to view by default", pDivisions);
}
if (!Brands.Any(x => x.Checked))
{
yield return new ValidationResult("Select at least 1 brand to view by default", pBrands);
}
}
}


BrandVM:

public class BrandVM
{
public int Id { get; set; }
public string Name { get; set; }
public string LogoUrl { get; set; }
public bool Checked { get; set; }
public string Division { get; set; }
}


The ActionResult:

[ValidateAntiForgeryToken]
[ValidateInput(true)]
[HttpPost]
public ActionResult HandleDealerMemberPreferencesAccountUpdate(MemberPreferencesVM model)
{
//logic removed as model doesn't arrive will all values so not relevant
return CurrentUmbracoPage();
}


The model should contain 24 Brands, it only ever seems to arrive at 'HandleDealerMemberPreferencesAccountUpdate' with 12.

Any help appreciated (am hoping it's something simple where I've slipped up)..

Mark

Answer

Ok, this turned out to be an issue with my data, that shouldn't have been possible.

Stephen Muecke's comment above was indeed correct

You have an if block inside your for loop. If that ever evaluates to false it means you skip an 'indexer' and binding will fail when you submit. by default, collection indexers must start at zero and be consecutive. While you can add an input for the indexer to make this work, you should be using a view model that contains only the objects you need in the view.

Essentially, one of my brands didn't have a division associated with it and so it broke the solution by not being posted to the controller correctly. The code does need refactoring so that this isn't possible, though I have made changes in the CMS to prevent this from happening again.

Hopefully this will help someone else, thanks all for the pointers.

Comments