2

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).

Before POST

However, after returning from the Update Action Method after submitting the form, Child.Code has been assigned the Value "1" - the Parent Code.

After POST

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

enter image description here

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.

7
  • Have you observed the form values as the browser is submitting them (using Firebug or your browser's built-in dev tools)? Sounds like you've caused a duplicate name="" value on one of the generated inputs. Commented Dec 7, 2015 at 21:49
  • @TiesonT. looks like that's not the case here. The Form Data that gets posted - according to Chrome - is Code: 1, Child.Code:999. I can also confirm that the model has the correct property values as it leaves the Update method. Good idea though, I hadn't thought to examine the form data in the browser. Commented Dec 8, 2015 at 14:25
  • Your problem is rooted in the fact you are doing the wrong thing, as Stephen pointed out. You should NEVER override the name attribute unless there is a very specific reason (and just "getting it to work" is not a good reason). MVC provides mechanisms to do what you need to do without fighting against the framework. Basically, you're saying "Doctor, it hurts when I put my hand in this running blender", and the doctor will rightfully say "Why the HECK are you putting your hand in a running blender? Don't do that!" Commented Dec 10, 2015 at 16:27
  • @ErikFunkenbusch I don't doubt that is the root of my problem. I also don't doubt that overriding the name attribute is a pretty stupid thing to do in this context. Looking back on my past code, it seems that I do stupid things often. I guess I'm asking though, "Why does the running blender hurt me sometimes(when POSTing), and not others(when GETing)?" Commented Dec 10, 2015 at 16:41
  • Getting doesn't require model binding. Commented Dec 10, 2015 at 16:42

1 Answer 1

4

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).

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks for the suggestion. This gives me another solution to the problem, though I don't think it really answers the question of why I can't set the Name explicitly like I was trying to do. Please correct me if I'm wrong.
Sorry I don't understand. Your using the wrong tool for the job. Its not a 'suggestion' - its the correct approach. And in any case what you were claiming is not even correct. If you omit new { Name = "Child.Code" } then the value of Child will be null when you submit and an exception will be thrown when you return the view. If you do include the hack, then the value will be 999 when you submit. If that is not what you are seeing, then you have other code modifying the default MVC behavior.
Note also that the new { Name = "Child.Code" } hack does not change the id attribute and you have invalid html.
Sorry, it's probably my fault for not asking a clear enough question. I was already able to solve the end user problem by setting the HtmlFieldPrefix. Your answer was helpful to me in that it gave me another - probably better - solution to the end user problem, and for that reason I still upvoted your answer. I've edited the question to hopefully make my actual question more clear.
Also - just to be clear - this is a new MVC project with no custom code other than the code posted in the original question.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.