【问题标题】:MVC Model Binding with Dynamic CollectionMVC 模型绑定与动态集合
【发布时间】:2017-04-02 13:51:54
【问题描述】:

根据 Scott Hanselman 关于ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries 的复杂性的开创性文章:

我们通过查找 parameterName[index].PropertyName
来读取属性 索引必须从零开始且完整

所以这个 HTML:

<input type="text" name="People[0].FirstName" value="George" />
<input type="text" name="People[1].FirstName" value="Abraham" />
<input type="text" name="People[2].FirstName" value="Thomas" />

会像这样发布:

但是,如果我通过 AJAX 将一个新人加载到我的模型中,我会丢失将该人构建到模型中的上下文并获得以下输出:

<input type="text" name="FirstName" value="New" />

模型绑定器不会拾取它。

问:如何在通过 AJAX 动态添加新元素时保留表达式树?

这是一个 MVCE

型号:/Model/Person.cs

public class PersonViewModel
{
    public List<Person> People { get; set; }
}
public class Person
{
    public String FirstName { get; set; }
    public String LastName { get; set; }
}

控制器:Controllers/PersonController.cs

[HttpGet]
public ActionResult Index()
{
    List<Person> people = new List<Person> {
        new Person { FirstName = "George" , LastName = "Washington"},
        new Person { FirstName = "Abraham" , LastName = "Lincoln"},
        new Person { FirstName = "Thomas" , LastName = "Jefferson"},
    };
    PersonViewModel model = new PersonViewModel() {People = people};
    return View(model);
}

[HttpPost]
public ActionResult Index(PersonViewModel model)
{
    return View(model);
}

public ActionResult AddPerson(String first, String last)
{
    Person newPerson = new Person { FirstName = first, LastName = last };
    return PartialView("~/Views/Person/EditorTemplates/Person.cshtml", newPerson);
}

查看:Views/Person/Index.cshtml

@model PersonViewModel

@using (Html.BeginForm()) {
    <table id="table">
        <thead>
            <tr>
                <th>@Html.DisplayNameFor(model => model.People.First().FirstName)</th>
                <th>@Html.DisplayNameFor(model => model.People.First().LastName)</th>
            </tr>
        </thead>
        <tbody>
            @for (int i = 0; i < Model.People.Count; i++)
            {
                @Html.EditorFor(model => model.People[i])
            }
        </tbody>
    </table>

    <input type="button" value="Add Person" id="add"/>
    <input type="submit" value="Save" />
}

<script type="text/javascript">

    $("#add").click(function() {
        var url = "@Url.Action("AddPerson")?" + $.param({ first: "", last: "" });
        $.ajax({
            type: "GET",
            url: url,
            success: function(data) {
                $("#table tbody").append(data);
            }
        });
    });

</script>

查看:Views/Person/EditorTemplates/Person.cshtml

@model Person

<tr>
    <td>@Html.EditorFor(model => model.FirstName)</td>
    <td>@Html.EditorFor(model => model.LastName)</td>
</tr>

注意:删除我不希望在此处解决的项目时还有其他复杂性。我只想添加一个元素并知道它与其他属性一起属于嵌套上下文。

【问题讨论】:

  • MVC 的助手非常棒,直到您开始使用集合做更高级的事情 - 一旦达到这一点,您最好自己控制标记生成。在 DOM 中的某处粘贴一个计数并在您的 JS 中获取它,然后您可以在添加新项目时手动构建(模型绑定)标记。
  • 如果您想在视图中动态添加(和删除)集合项,请参阅this answer 了解一些选项。旁注-在这种情况下您不能使用EditorFor(),但正确的用法应该只是@Html.EditorFor(m =&gt; model.People)-不在循环内-该方法接受IEnumerable&lt;T&gt;,并将为集合中的每个项目生成正确的html跨度>
  • 对于使用客户端模板方法的实现,请参阅this DotNetFiddle。有关BeginCollectionItem() 实现的示例,请参阅this answer

标签: c# asp.net asp.net-mvc collections model-binding


【解决方案1】:

您可以像这样install Html.BeginCollectionItem 实用程序:

PM> Install-Package BeginCollectionItem

然后像这样包装你的收藏项部分视图:

@model Person
<tr>
    @using (Html.BeginCollectionItem("people"))
    {
        <td>@Html.EditorFor(model => model.FirstName)</td>
        <td>@Html.EditorFor(model => model.LastName)</td>
    }
</tr>

这将生成一个 GUID 驱动的集合,如下所示:

<tr>
    <input type="hidden" name="people.index" autocomplete="off" 
                     value="132bfe2c-75e2-4f17-b54b-07e011971d78">
    <td><input class="text-box single-line" type="text" value="Abraham"
                 id="people_132bfe2c-75e2-4f17-b54b-07e011971d78__FirstName" 
               name="people[132bfe2c-75e2-4f17-b54b-07e011971d78].FirstName"></td>
    <td><input class="text-box single-line"  type="text" value="Lincoln"
                 id="people_132bfe2c-75e2-4f17-b54b-07e011971d78__LastName"  
               name="people[132bfe2c-75e2-4f17-b54b-07e011971d78].LastName"></td>            
</tr>

现在我们得到如下所示的已发布表单数据:

这利用了 DefaultModelBinder,它允许 Non-Sequential Indices 作为 explained by Phil Haack:

好消息是,通过引入额外的隐藏输入,您可以允许任意索引。只需为我们需要绑定到列表的每个项目提供一个带有 .Index 后缀的隐藏输入。这些隐藏输入中的每一个的名称都是相同的,这将为模型绑定器提供一个很好的索引集合,以便在绑定到列表时查找。

马上,您的模型应该构建得很好,但您也可以添加和删除项目。

进一步阅读

【讨论】:

    猜你喜欢
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 2013-12-08
    • 2012-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多