问题描述:

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

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

相关阅读:
Top