-1

I'm having a problem using the HiddenFor and Hidden helpers to store data to be POSTed back to a Controller. I've got a Controller with two methods:

Function Index() As ActionResult
    Dim model As New FormPOSTViewModel With {.Name = "Test", .Description = "Test description goes here", .Value = 12}
    Return View(model)
End Function

<HttpPost>
Function Update(model As FormPOSTViewModel) As ActionResult
    Dim updated As New FormPOSTViewModel
    updated.Name = model.Name & "_x"
    updated.Description = model.Description & "_x"
    updated.Value = model.Value * 2
    Return View("Index", updated)
End Function

and a view with three forms, one using HTML, one using HiddenFor and one using Hidden:

@ModelType MVCAjaxWorkbench.FormPOSTViewModel

@Using Html.BeginForm("Update", "FormPOST")
    @:<table>
        @:<tr><td>@Html.DisplayFor(Function(m) Model.Name)</td></tr>
        @:<tr><td><input type="hidden" id="Name" name="Name" value="@Model.Name" /></td></tr>
        @:<tr><td><input type="hidden" id="Description" name="Description" value="@Model.Description" /></td></tr>
        @:<tr><td><input type="hidden" id="Value" name="Value" value="@Model.Value" /></td></tr>
    @:</table>
    @:<input type="submit" />
End Using

@Using Html.BeginForm("Update", "FormPOST")
    @:<table>
        @:<tr><td>@Html.DisplayFor(Function(m) Model.Name)</td></tr>
        @:<tr><td>@Html.Hidden("Name", Model.Name)</td></tr>
        @:<tr><td>@Html.Hidden("Description", Model.Description)</td></tr>
        @:<tr><td>@Html.Hidden("Value", Model.Value)</td></tr>
    @:</table>
    @:<input type="submit" />
End Using

@Using Html.BeginForm("Update", "FormPOST")
    @:<table>
        @:<tr><td>@Html.DisplayFor(Function(m) Model.Name)</td></tr>
        @:<tr><td>@Html.HiddenFor(Function(m) Model.Name)</td></tr>
        @:<tr><td>@Html.HiddenFor(Function(m) Model.Description)</td></tr>
        @:<tr><td>@Html.HiddenFor(Function(m) Model.Value)</td></tr>
    @:</table>
    @:<input type="submit" />
End Using

and a ViewModel:

Public Class FormPOSTViewModel
    Public Property Name As String
    Public Property Description As String
    Public Property Value As Integer
End Class

What I want to happen is that the model is updated each time the Submit button is pressed appending '_x' to the end multiple times so that it ends up something like 'Test_x_x_x_x_x_x'. If I use the Submit button in the form where I've created the HTML by hand then everything works okay.

However what actually happens when I fire this up and press one of the other Submit buttons is that only a single '_x' is ever appended. This seems to be because the model that the Update method receives on the second and subsequent times around is the model sent to the page by the original Index method.

Is this bug with Hidden/HiddenFor or am I using them incorrectly?

Carl Onager
  • 4,112
  • 2
  • 38
  • 66

2 Answers2

4

No its not a bug. When you post back the model, the value of the properties is added to ModelState. When you return the view, it's the value in ModelState that takes precedence over any value you might try to override in the controller. Your first need to clear ModelState values using

ModelState.Clear();

Now when you set the value of a property, the Html helpers will use property value.

  • 2
    Personally I think that I'd classify it as a bug even if MS think it's 'working as intended' as the object you're explicitly passing to the view is not the one that the view actually uses. Too many abstractions. – Carl Onager Dec 03 '14 at 12:54
  • It is not a bug. The reason why it works this way is explained in [this answer](http://stackoverflow.com/questions/26654862/textboxfor-displaying-initial-value-not-the-value-updated-from-code/26664111#26664111) –  Dec 03 '14 at 20:46
  • 2
    It is at best a 'feature'. A lot of people would define it as a bug because you have to add a workaround (ModelState.Clear) in order to get it working as intended. The design flaw is that the various helper extensions resort to reading from data that the user has not explicitly set. Explicit should always trump implicit but in this case it does not - that is a bug and rather symptomatic of the over reliance on implicit that the MVC framework uses – Carl Onager Dec 05 '14 at 08:50
2

From Simon Ince's Blog which contains a description of the issue and why using the ModelState.Clear call is required:

ASP.NET MVC assumes that if you’re rendering a View in response to an HTTP POST, and you’re using the Html Helpers, then you are most likely to be redisplaying a form that has failed validation. Therefore, the Html Helpers actually check in ModelState for the value to display in a field before they look in the Model. This enables them to redisplay erroneous data that was entered by the user, and a matching error message if needed.

Since our [HttpPost] overload of Index relies on Model Binding to parse the POST data, ModelState has automatically been populated with the values of the fields. In our action we change the Model data (not the ModelState), but the Html Helpers (i.e. Html.Hidden and Html.TextBox) check ModelState first… and so display the values that were received by the action, not those we modified.

Thus to resolve the problem we must add a line to the Controller's Update method:


Function Update(model As FormPOSTViewModel) As ActionResult
    Dim updated As New FormPOSTViewModel
    updated.Name = model.Name & "_x"
    updated.Description = model.Description & "_x"
    updated.Value = model.Value * 2
    ModelState.Clear()
    Return View("Index", updated)
End Function
Carl Onager
  • 4,112
  • 2
  • 38
  • 66