0

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.

Daniel B. Chapman
  • 4,647
  • 32
  • 42
  • 1
    Could you publish the structure of `storage.ID` (getter or field declaration) ? – Orel Eraki Mar 05 '16 at 19:01
  • What is an actual value of ID in the viewModel object you are passing? – Ivan Gritsenko Mar 05 '16 at 19:02
  • @OrelEraki I've updated the code to offer a lot more information on the model. This is pretty simple. – Daniel B. Chapman Mar 05 '16 at 21:13
  • @IvanGritsenko The ID is just the primary key on the database to track the entity. It could be any integer greater than 1. In the example above the model was passing `63` (for this object, it binds correctly for most of them) but `62` was the value printed when referenced (and what is in the database) was `63` in this case. This was always off by `1`. It was always the last object in the list from my brief testing, but that could be coincidence. The debugger showed a value of `63` in the model before passing it to the view and `62` coming back in (obviously, the html shows `62`). – Daniel B. Chapman Mar 05 '16 at 21:16
  • You need to give a bit more context. The `HiddenFor()` method will take the value from `ModelState` if one exists, whereas you implementation of the `LabelFor()` method will use the model value. Are you making modifications to the model in a POST method to then returning the view? –  Mar 05 '16 at 22:50
  • No, this is a simple HIJAX application where the whole model is rebuilt on each request. The problem here is on the initial rendering on the client the value of the ID is off by one. So, yes, this uses the model state but the entire state is replaced on each request with a fresh copy. This, perhaps, sounds like it could be the source of the issue. Where is the ModelState cached? – Daniel B. Chapman Mar 06 '16 at 03:04
  • @DanielB.Chapman, Based on your last comment, your not understanding what `ModelState` is. Please show the relevant controller methods. –  Mar 06 '16 at 09:51
  • Pay attention to Stephen Muecke's comment and check what your `ModelState` contains before you call of `HiddenFor` method. – Ivan Gritsenko Mar 06 '16 at 14:26
  • I'll run it through the debugger to see if something is altering it when rendering the View. I was unaware that the `@Hhtml.XXXXFor(...)` referenced the ModelState and not the Model. – Daniel B. Chapman Mar 06 '16 at 16:31
  • @IvanGritsenko thanks! I have not fixed it but this is definitely the case. – Daniel B. Chapman Mar 07 '16 at 16:12
  • @StephenMuecke Thank you--this was definitely the cause. Hopefully I'll have a solution that isn't so hacky. – Daniel B. Chapman Mar 07 '16 at 16:13
  • `ModelState` values are only added by the `DefaultModelBinder` (when you have a model as a parameter of a GET or POST method) or by using `TryValidateModel()`. You have not shown you methods so the question cannot be answered answered, but the answers [here](http://stackoverflow.com/questions/26654862/textboxfor-displaying-initial-value-not-the-value-updated-from-code/26664111#26664111) and [here](http://stackoverflow.com/questions/35605740/incorrect-list-model-binding-indices-when-using-html-helpers/35640658#35640658) explain some possible issues. –  Mar 07 '16 at 22:54
  • @StephenMuecke the project is protected by non-disclosure on the framework I'm referencing so I just can't post those methods, but this has definitely pointed me in the right direction. I think there's an annotation (possibly display model) that's doing something to mess with the values. Your information was **incredibly useful**. If I have some time I'll isolate it and post it as an answer but bypassing the ModelState solves the issue for the moment. With any luck I can decouple it from this framework and solve the issue entirely. – Daniel B. Chapman Mar 07 '16 at 23:41

0 Answers0