I'm having a problem I can't seem to figure out here and I've done a fair amount of searching:
In my model (a list of objects) I have the following output:
<input data-val="true" data-val-number="Please enter a number." data-val-required="The ModelID field is required." id="Students_19__ModelID" name="Students[19].ModelID" type="hidden" value="62">
If you look closely here this value is 62 (I've bound a second object just to check myself and I see that it is also 62:
<input data-val="true" data-val-number="Please enter a number." id="Students_19__storage_ID" name="Students[19].storage.ID" type="hidden" value="62">
In the same view I'm seeing this:
@Html.LabelFor(_ => Model.Students[i].Show, "ID: " + Model.Students[i].storage.ID)
producing: ID: 63
In summary:
@Html.HiddenFor(_ => Model.Students[i].storage.ID)
@Html.LabelFor(_ => Model.Students[i].Show, "ID: " + Model.Students[i].storage.ID)
OR
@Html.HiddenFor(m => m.Students[i].storage.ID)
@Html.LabelFor(m => m.Students[i].Show, "ID: " + Model.Students[i].storage.ID)
produces->
<input data-val="true" data-val-number="Please enter a number." id="Students_19__storage_ID" name="Students[19].storage.ID" type="hidden" value="62">
<label for="Students_19__Show">ID: 63</label>
Obviously 62 != 63 and this is throwing off the model in the controllers. Does anyone have an idea of what could be causing this? I feel like something is being converted poorly but I'm out of ideas at this point. I've seen this behavior before and shuffling around the loading order of the hidden fields has fixed it but this obviously isn't a good solution.
EDIT: Per comments: The structure of the object is pretty tame: (this is the "storage" variable)
public class EducationGoalModel
{
public int? ID { get; set; }
public decimal Amount { get; set; }
public string Name { get; set; }
public string Category { get; set; }
//Education
public int StudentAge { get; set; }
public int StudentBegin { get; set; }
public int StudentYears { get; set; }
[DisplayFormat(DataFormatString = "{0:P2}", ApplyFormatInEditMode = true)]
[Percentage] //Custom annotation, nothing silly here
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources.Errors))] //Error is not triggered otherwise the ID wouldn't work in the "workaround"
public decimal Inflation { get; set; }
// This is just used by another module that needs annotations waaaay down the line, these values are unused.
public decimal TargetAmount { get { return Calculations.FutureValue(this.YearOfCompletion - DateTime.Now.Year, this.Inflation, this.Amount); } }
public byte RiskToleranceLevel { get; private set; }
public int YearOfCompletion { get; set; }
public bool Hide { get; set; }
public EducationGoalModel()
{
this.Category = FinanceGoalTargetCategory.EDUCATIONAL;
}
public EducationGoalModel(EducationGoal goal)
{
//The goal is just the entity I'm using, this is a light wrapper around it.
ID = goal.ID;
Amount = goal.Amount;
Name = goal.Name;
Category = goal.Category;
StudentAge = goal.StudentAge;
StudentBegin = goal.StudentBegin;
StudentYears = goal.StudentYears;
Inflation = goal.Inflation;
Hide = goal.Hide;
}
public void CopyTo(EducationGoal goal)
{ //SNIP.... we don't really care about this method, it isn't called
}
Here's the view Model:
public class EducationGoalInputModel : InputModelBase
{
public CurrencySliderModel AmountSlider { get; set; }
public EducationGoalModel storage { get; set; }
public int ModelID { get; set; }
public int StudentAge { get; set; }
public int StudentBegin { get; set; }
public int StudentYears { get; set; }
[MaxLength(20, ErrorMessageResourceName = "FinancialGoalsNameLength", ErrorMessageResourceType = typeof(Resources.Errors))]
public string Name { get; set; }
[DisplayFormat(DataFormatString = "{0:P2}", ApplyFormatInEditMode = true)]
[Percentage]
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources.Errors))]
public decimal Inflation { get; set; }
... plain constructor (no args), a second to construct and establish the "storage" object which is the one used for the above output.
Here's the base class, nothing interesting here really:
public abstract class InputModelBase { //a simple class that has
public abstract void UpdateAmounts(int age);
//and a couple other utility methods for the user interface (we support IE8 so a lot of UI information is stored)
}
EDIT: Per Hacks: because this is MVC (this works fine, but is terrible for maintenance and indicates a bigger problem)
<input type="hidden" name="@String.Format("Students[{0}].storage.ID", i)" id="@String.Format("Students_{0}__storage_ID", i)" value="@Model.Students[i].storage.ID" />
This outputs the correct values of value="63"
using the example above.
EDIT #2 Based on feedback from the comments below *(thank you!) it looks like the ModelState doesn't agree with the Model. There's nothing obvious modifying the ModelState in the View that I can find but I've inherited this project and there's probably some extension method doing something that I need to track down. The values in this particular test 5. The ModelState shows the wrong value and thus the binding is wrong. This has not fixed the issue but is basically an accepted answer as I now know where to look.
Thanks for your help.