【问题标题】:Model Not Submitting / Binding Correctly on POST模型未在 POST 上正确提交/绑定
【发布时间】:2017-01-04 19:15:59
【问题描述】:

场景: 我有一个表,其中包含从 ViewModel 填充的行。每行都有复选框,允许用户检查 1 行或多行,然后从下拉菜单中的操作中进行选择,以对所选行的属性进行编辑。

到目前为止一切正常,我可以让 ViewModel 正确传递,然后在 POST Action 方法中使用它以及它的所有属性。我可以根据用户选择的选项进行更改。

但是,由于下拉列表中的某些选项会产生相当大且不可逆的更改,因此我正在使用 GET 调用新视图并仅使用选定的行填充新表,并要求用户确认他们想要做出改变。到目前为止,一切都还不错。新视图会按预期填充之前视图中选择的行。

问题: 在用户确认他们的意图后,使用 POST 调用 Action 方法。正确填充当前视图的 ViewModel 正在正确地进入控制器。我得到了 ViewModel,但与填充 View 的属性不同。

视图模型

public class ProjectIndexViewModel
{        
    public List<ProjectDetailsViewModel> Projects { get; set; }
    public string FlagFormEditProjects { get; set; }
    public string FlagFormNewProjectStatus { get; set; }
}

List&lt;ProjectDetailsViewModel&gt; Projects 用于填充表中的行,而 Projects 是在控制器的 POST Action 方法中未正确绑定的。

选中复选框的初始视图。注意选择下拉选项之一时调用的 JavaScript 函数之一的示例,这是提交表单的内容。

@using (Html.BeginForm("EditProjectsTable", "Project", FormMethod.Get, new { name = "formEditProjects", id = "formEditProjects" }))
{         
    @Html.HiddenFor(item => item.FlagFormEditProjects)
    @Html.HiddenFor(item => item.FlagFormNewProjectStatus) 
    ....
    <table>
        <thead>
            ....
        </thead>
        <tbody>
            @for (int i = 0; i < Model.Projects.Count; i++)
            {
                <tr>
                    <td>@Html.DisplayFor(x => x.Projects[i].ProjectNumber)</td>
                    <td>@Html.DisplayFor(x => x.Projects[i].ProjectWorkType)</td>
                    .... // more display properties
                    <td>
                        @Html.CheckBoxFor(x => x.Projects[i].Selected, new { @class = "big-checkbox" })
                        @Html.HiddenFor(x => x.Projects[i].ProjectModelId)
                    </td>
                </tr>
            }                         
        </tbody>
    </table>
}

function submitFormRemoveProjects() {
    $("#FlagFormEditProjects").attr({
        "value": "RemoveProjects"
    });
    $('#formEditProjects').submit();
}

返回“确认”视图的操作方法(工作正常)

[HttpGet]
[Authorize(Roles = "Sys Admin, Account Admin, User")]
public async Task<ActionResult> EditProjectsTable([Bind(Include = "Projects,FlagFormEditProjects,FlagformNewProjectStatus")]ProjectIndexViewModel projectIndexViewModel)
{
    // Repopulate the Projects collection of ProjectIndexViewModel to
    // include only those that have been selected
    return View(projectIndexViewModel);
}

从上面的 Action 方法返回的视图(工作正常)请注意,被调用的 Action 方法是使用 Html.BeginForm 调用中的 actionName 变量动态设置的。

@using (Html.BeginForm(actionName, "Project", FormMethod.Post))
{
    @Html.AntiForgeryToken()
    @Html.HiddenFor(model => model.FlagFormNewProjectStatus)
    ....
    <table>
        <thead>
            ....
        </thead>
        <tbody>
            @for (int i = 0; i < Model.Projects.Count; i++)
            {
                <tr>
                    <td>@Html.HiddenFor(x => x.Projects[i].ProjectModelId)</td>
                    <td>@Html.DisplayFor(x => x.Projects[i].ProjectNumber)</td>
                    <td>@Html.DisplayFor(x => x.Projects[i].ProjectWorkType)</td>
                    .... // more display properties
                </tr>
             }
         </tbody>
     </table>
     <input type="submit" value="Delete Permanently" />
}

从该视图调用的控制器操作方法之一的示例,它与视图中的项目不同。不知何故,它与最初选择的项目数量相同,但如果只选择一个,则它具有模型 ID 最低的项目。我不确定如何描述正在发生的事情。但总而言之,正确的 ViewModel 并没有进入下面显示的 POST 方法示例。

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Sys Admin, Account Admin")]
public async Task<ActionResult> DeleteConfirmedMultipleProjects([Bind(Include = "Projects")] ProjectIndexViewModel projectIndexViewModel)
{
    if (ModelState.IsValid)
    {
        // Remove Projects from db and save changes
        return RedirectToAction("../Project/Index");
    }
    return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}

请帮忙!

【问题讨论】:

  • 代码墙!你能只发布Minimum Viable Code吗?
  • 对此感到抱歉....我清理了一下。
  • “我获得了 ViewModel,但与填充 View 的属性不同”是什么意思。您在“DeleteConfirmedMultipleProjects”方法中的预期数据是什么?
  • 从 Action 方法 EditProjectsTable 构建并传递给 View 的 ViewModel 在从该方法返回的 View 中按预期工作。但是,当这个View中的表单提交给DeleteConfirmedMultipleProjects方法时,同样的Projects不在模型中。但最令人困惑的部分是,正如我在上面试图解释的那样,在初始视图中选择的相同数量的Projects 被发送到DeleteConfirmedMultipleProjects,但是例如,如果Projects 中只有一个是最初选择,然后发布到...
  • ...DeleteConfirmedMultipleProjects,那么它是具有不同模型 ID 的 Project

标签: asp.net-mvc


【解决方案1】:

问题是当您从第一个视图提交到EditProjectsTable() 方法时,所有表单控件的值都添加到ModelState

重新填充您的 ProjectDetailsViewModel 集合不会更新 ModelState,当您返回视图时,DisplayFor() 方法将显示正确的值,因为 DisplayFor() 使用模型的值,但是您的

@Html.HiddenFor(x => x.Projects[i].ProjectModelId)

将使用来自ModelState 的值,就像生成表单控件的所有HtmlHelper 方法一样(PasswordFor() 除外)。

解决这个问题的一种方法是在EditProjectsTable() 方法中返回视图之前调用ModelState.Clear()HiddenFor() 方法现在将使用模型的值,因为没有 ModelState 值。

[HttpGet]
[Authorize(Roles = "Sys Admin, Account Admin, User")]
public async Task<ActionResult> EditProjectsTable(ProjectIndexViewModel projectIndexViewModel)
{
    // Repopulate the Projects collection of ProjectIndexViewModel to
    // include only those that have been selected
    ModelState.Clear(); // add this
    return View(projectIndexViewModel);
}

有关为什么这是默认行为的解释,请参阅this answer 的第二部分。

旁注:您使用的是视图模型,因此在您的方法中包含 [Bind] 属性是没有意义的。

【讨论】:

  • @stephenmueke ,感谢您的好评。这解释了我遇到的问题。您对使用这种类型的场景而不是 TempData 的优点/缺点有任何见解吗?
  • TempData 可以使用,但它只持续一个请求,所以如果您使用该解决方案,那么如果用户按 F5,一切都会丢失(并且用户不知道发生了什么在)。通常,仅将其用于非关键数据(例如在重定向时从 POST 方法传递友好的成功消息)
  • 顺便说一句,您最初的 POST 方法正在执行大量数据库调用来重新填充您的数据(我删除了大部分代码,因为它与问题无关),它可能会影响性能,所以我会考虑缓存该数据(比如将其添加到Session),然后在EditProjectsTable 中再次获取它(然后在返回视图之前将其从Session 中删除
  • 按照您的描述使用ModelState.Clear(); 解决了问题。谢谢你,也感谢你的额外参考!
【解决方案2】:

我认为你的问题来自这部分:

@Html.CheckBoxFor(x => x.Projects[i].Selected, new { @class = "big-checkbox" })
@Html.HiddenFor(x => x.Projects[i].ProjectModelId)

我之前遇到过这个错误,我所做的是向ProjectDetailsViewModel 添加一个布尔属性,例如IsSelected。 那么你应该有:

@Html.CheckBoxFor(x => x.Projects[i].IsSelected, new { @class = "big-checkbox" })

然后你应该添加的方法:

foreach (var project in ProjectIndexViewModel.Projects  )
        {
            if (project.IsSelected==true)
                "put your logic here"
        }

【讨论】:

  • 我相信Selected 属性已经在初始视图中完成了这项工作
猜你喜欢
  • 2012-09-27
  • 2014-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-28
  • 1970-01-01
  • 1970-01-01
  • 2016-05-07
相关资源
最近更新 更多