我今天遇到一个问题,其中一个表单中的嵌套对象的 Code 值被更改为不正确的值。经过一番挖掘,我发现只有在 POST 后,并且只有当我尝试使用 Html.TextBoxFor 的第二个对象参数显式设置 Name 属性时,才会为它分配 Parent 对象 Code 的值。
我设置了一个简单的 MVC(版本 5.2.2.0)项目来隔离问题。这是相应的代码。
型号
public class Parent
{
public string Code { get; set; }
public Child Child { get; set; }
}
public class Child
{
public string Code { get; set; }
}
Controller
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);
}
}
View /父级/显示
@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>
}
View /子/显示
@model TextBoxForBugTest.Models.Child
@Html.TextBoxFor(o => o.Code, new { Name = "Child.Code" })
当我第一次加载/Parent/Show 时,我在输入中看到正确的值:1(代码)和 999(子代码)。
但是,提交表单后从更新操作方法返回后,Child.Code 已被分配值“1” - 父代码。
我发现可以通过设置 HtmlFieldPrefix 来解决该问题。
@model TextBoxForBugTest.Models.Child
@{ Html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "Child"; }
@Html.TextBoxFor(o => o.Code)
或者使用局部变量
@model TextBoxForBugTest.Models.Child
@{ var theCode = Model.Code; }
@Html.TextBoxFor(o => theCode, new { Name = "Child.Code" })
但我想了解为什么。这里发生了什么?为什么 POST 后 Child.Code 会被赋予 Parent.Code 的值?
我还发现了一些与使用扩展程序相关的问题,但它们似乎回答了不同的问题
ASP.NET MVC partial views: input name prefixes
ASP.MVC 3 Razor Add Model Prefix in the Html.PartialView extension
***编辑 - 从答案中可以清楚地看出,我在陈述实际问题方面做得很差,因此我将尝试在这里进行更多澄清。
我发现导致最终用户发现错误的问题是
@Html.TextBoxFor(o => o.Code, new { Name = "Child.Code" })
第二次调用时(POST 之后)生成具有不同“值”的 html。
我能够通过设置 Html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix 解决该问题。 Stephen Muecke 还在编辑器模板中指出了该问题的另一个(可能更好)解决方案。
我想问的是:
为什么
@Html.TextBoxFor(o => o.Code, new { Name = "Child.Code" })
生成
<input name="Child.Code" id="Code" type="text" value="999">
第一次(/Parent/Show),但随后生成
<input name="Child.Code" id="Code" type="text" value="1">
第二次(发布到/Parent/Update 之后)?
发布的表单数据是
以及
中的绑定(bind)模型public ActionResult Update(Parent parent)
{
return View("Show", parent);
}
预期值为 Parent.Code == 1 和 Child.Code == 999。
我认为 Stephen Muecke 可能接近我在他的评论中寻找的答案
Note also that the new { Name = "Child.Code" } hack does not change the id attribute and you have invalid html. – Stephen Muecke
确实,使用
@Html.TextBoxFor(o => o.Code, new { Name = "Child.Code" })
,我最终得到了 2 个 id="Code"的输入,根据 the spec 这是无效的.
即使知道这一点,我仍然不明白为什么 TextBoxFor 生成的 value 属性根据我是 GET/Parent/Show 还是 POST 到/Parent/Update 而不同。
最佳答案
您使用 @Html.Partial("~/Views/Child/Show.cshtml", Model.Child)
正在生成一个输入
<input name="Code" ... />
虽然需要
<input = name="Child.Code" ... />
不要使用部分来生成表单控件。相反,使用 EditorTemplate
它将生成带有前缀的正确 name
属性。
将您的部分重命名为 Child.cshtml
并将其放置在 /Views/Shared/EditorTemplates
文件夹中,并在主视图中使用
@Html.EditorFor(m => m.Child)
编辑(基于修改后的问题)
解释正在发生的事情。当您将 View 传递给模型并使用 HtmlHelpers 为模型的属性生成表单控件时,帮助器首先计算表达式并获取该属性的 ModelMetadata
。 ModelMetadata
包括模型本身的值以及用于确定需要如何生成 html 以及如何显示模型值的附加属性。该帮助器还添加 TextBoxFor()
方法的第二个参数中定义的 htmlAttributes。
现在假设您已将第二个文本框的值编辑为 999
,当您提交表单时,它会发回 Code=1&Child.Code=999
因为您已经为输入元素指定了一个属性 name="Child.Code"
。 DefaultModelBinder
读取表单数据,在模型中查找 Code
和 Child.Code
的匹配项,并将它们的值设置为 1分别为
和 999
现在,当您返回 View 时,您的第二个 TextBoxFor()
方法将绑定(bind)到属性 Code
(不是 Child.Code
),该属性具有值为 1
(不是 999
)。仅添加 name
属性不会更改您绑定(bind)到的属性(但当您将数据发送回 Controller 时,它确实会搞砸模型绑定(bind))。
关于c# - 为什么在部分 View 输入上显式设置名称属性会导致值在 POST 后发生变化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34143821/