I ran across a problem today where the Code value of a nested object in one of our forms was being changed to an incorrect value. After some digging, I discovered that it is being assigned the value of the Parent object Code, only after POSTing, and only when I try to set the Name attribute explicitly with Html.TextBoxFor's second object parameter.
I setup a simple MVC(Version 5.2.2.0) project to isolate the issue. Here is the code for that.
Models
public class Parent
{
public string Code { get; set; }
public Child Child { get; set; }
}
public class Child
{
public string Code { get; set; }
}
Controllers
public class ParentController : Controller
{
public ActionResult Show()
{
var child = new Child() { Code = "999"};
var parent = new Parent() { Code = "1", Child = child };
return View("Show", parent);
}
public ActionResult Update(Parent parent)
{
return View("Show", parent);
}
}
Views/Parent/Show
@model TextBoxForBugTest.Models.Parent
@using (Html.BeginForm("Update", "Parent"))
{
@Html.TextBoxFor(o => o.Code)
@Html.Partial("~/Views/Child/Show.cshtml", Model.Child)
<button type="submit">Submit</button>
}
Views/Child/Show
@model TextBoxForBugTest.Models.Child
@Html.TextBoxFor(o => o.Code, new { Name = "Child.Code" })
When I first load /Parent/Show, I see the correct values in the inputs: 1(Code), and 999(Child.Code).
However, after returning from the Update Action Method after submitting the form, Child.Code has been assigned the Value "1" - the Parent Code.
I've found that I can fix the issue by setting the HtmlFieldPrefix.
@model TextBoxForBugTest.Models.Child
@{ Html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "Child"; }
@Html.TextBoxFor(o => o.Code)
or by using a local variable
@model TextBoxForBugTest.Models.Child
@{ var theCode = Model.Code; }
@Html.TextBoxFor(o => theCode, new { Name = "Child.Code" })
but I'd like to understand why. What is going on here? Why is Child.Code being assigned the value of Parent.Code after POSTing?
I also found some related questions that get into using extensions, but they seem to be answering different questions
ASP.NET MVC partial views: input name prefixes
ASP.MVC 3 Razor Add Model Prefix in the Html.PartialView extension
***Edit - It's clear from the answers that I did a poor job of stating my actual question, so I'll attempt to clarify a bit more here.
The problem I was seeing that was leading to an end user identified bug was that
@Html.TextBoxFor(o => o.Code, new { Name = "Child.Code" })
was generating html with a different "value" the second time it was called(after POSTing).
I was able to solve that problem by setting the Html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix. Stephen Muecke also pointed out another - probably better - solution to that problem in Editor Templates.
What I was trying to ask though was this:
Why does
@Html.TextBoxFor(o => o.Code, new { Name = "Child.Code" })
generate
<input name="Child.Code" id="Code" type="text" value="999">
the first time (/Parent/Show), but then generate
<input name="Child.Code" id="Code" type="text" value="1">
the second time (after POSTing to /Parent/Update)?
The Form Data that gets POSTed is
and the binded Model in
public ActionResult Update(Parent parent)
{
return View("Show", parent);
}
has the expected values of Parent.Code == 1 and Child.Code == 999.
I think Stephen Muecke is probably close to the answer I'm looking for in his comment
Note also that the new { Name = "Child.Code" } hack does not change the id attribute and you have invalid html. – Stephen Muecke
Indeed, using
@Html.TextBoxFor(o => o.Code, new { Name = "Child.Code" })
, I end up with 2 inputs with id="Code", which is invalid according to the spec.
Even knowing that though, I still don't understand why the value attribute generated by TextBoxFor is different based on whether I'm GETing /Parent/Show or POSTing to /Parent/Update.
Your use of @Html.Partial("~/Views/Child/Show.cshtml", Model.Child)
is generating an input with
<input name="Code" ... />
whereas it need to be
<input = name="Child.Code" ... />
Do not use a partial to generate form controls. Instead use an EditorTemplate
which will generate the correct name
attributes with the prefix.
Rename you partial to Child.cshtml
and place it in the /Views/Shared/EditorTemplates
folder, and in the main view use
@Html.EditorFor(m => m.Child)
Edit (based on revised question)
To explain what is happening. When you pass a view to the model and use the HtmlHelpers to generate a form control for a property of your model, the helper first evaluates the expression and gets the ModelMetadata
for the property. The ModelMetadata
includes the value of the model itself plus addition properties used to determine how you html needs to be generated and how the value of the model is displayed. The helper also adds the htmlAttributes as defined in the 2nd parameter of your TextBoxFor()
method.
Now assuming you have edited the value of your second textbox with the value of 999
, when you submit the form, its posts back Code=1&Child.Code=999
because you have given the input element an attribute name="Child.Code"
. The DefaultModelBinder
reads the form data, finds a match for both Code
and Child.Code
in your model and sets their values to 1
and 999
respectively
Now when you return the view your second TextBoxFor()
method is binding to property Code
(not Child.Code
) which has a value of 1
(not 999
). Just adding a name
attribute does not change the property you binding to (but it does screw up model binding when your send the data back to the controller).
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments