模型未在POST

时间:2017-01-04 19:15:59

标签: asp.net-mvc

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

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

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

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

视图模型

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

List<ProjectDetailsViewModel> 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" />
}

从此View 调用的一个Controller Action方法的示例,它与View中没有相同的Project。不知何故,它具有与最初选择的项目数相同的项目,但如果只选择了一个项目,则项目具有最低的模型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);
}

请帮忙!

2 个答案:

答案 0 :(得分:1)

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

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

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

将使用ModelState中的值,生成表单控件的所有HtmlHelper方法也是如此(PasswordFor()除外)。

解决此问题的一种方法是在ModelState.Clear()方法中返回视图之前调用EditProjectsTable()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]属性。

答案 1 :(得分:0)

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

@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"
        }