简体   繁体   English

具有动态集合的MVC模型绑定

[英]MVC Model Binding with Dynamic Collection

According to the seminal Scott Hanselman article on the complexities of the ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries : 根据具有开创性的Scott Hanselman文章,关于将模型绑定到数组,列表,集合,字典ASP.NET Wire格式的复杂性:

We read in the properties by looking for parameterName[index].PropertyName 我们通过查找parameterName[index].PropertyName读取属性。
The index must be zero-based and unbroke 索引必须从零开始,并且是连续的

So this HTML: 所以这个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" />

Which will post like this: 它将像这样发布:

发布数据-People%5B0%5D.FirstName = George&People%5B1%5D.FirstName = Abraham&People%5B2%5D.FirstName = Thomas

However, if I load a new person into my model over AJAX, I lose the context for building that person into the model and get the following output: 但是,如果我通过AJAX将新人员加载到模型中,则会丢失将该人员构建到模型中的上下文,并获得以下输出:

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

发布数据-People%5B0%5D.FirstName = George&People%5B1%5D.FirstName = Abraham&People%5B2%5D.FirstName = Thomas&FirstName = John

Which won't get picked up by the model binder. 模型绑定器不会将其拾取。

Q : How can I preserve the expression tree when dynamically adding new elements over AJAX? 在AJAX上动态添加新元素时,如何保留表达式树?

Here's an MVCE 这是MVCE

Model: /Model/Person.cs 型号: /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; }
}

Controller: Controllers/PersonController.cs 控制器: 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);
}

View: Views/Person/Index.cshtml 查看: 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>

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

@model Person

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

NOTE : There are other complexities when deleting an item that I'm not looking to address here per se. 注意 :删除我本身不打算解决的项目时,还有其他复杂性。 I'd just like to add an element and know that it belongs in a nested context alongside other properties. 我只想添加一个元素,并知道它与其他属性一起属于嵌套上下文。

You can install the Html.BeginCollectionItem utility like this: 您可以像这样安装 Html.BeginCollectionItem实用程序:

PM> Install-Package BeginCollectionItem

Then wrap your collection item partial view like this: 然后像这样包装您的收藏品局部视图:

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

Which will generate a GUID-driven collection like this: 它将生成一个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>

Now we get posted form data that look like this: 现在,我们获得了如下所示的表单数据:

表单数据-带GUID

This leverages the DefaultModelBinder which allows for Non-Sequential Indices as explained by Phil Haack : 这利用了DefaultModelBinder ,它允许非连续索引,Phil Haack解释

The good news is that by introducing an extra hidden input, you can allow for arbitrary indices. 好消息是,通过引入额外的隐藏输入,您可以允许任意索引。 Just provide a hidden input with the .Index suffix for each item we need to bind to the list. 只需为需要绑定到列表的每个项目提供一个带有.Index后缀的隐藏输入。 The name of each of these hidden inputs is the same, which will give the model binder a nice collection of indices to look for when binding to the list. 这些隐藏输入的每个名称都是相同的,这将为模型绑定器提供一个不错的索引集合,以便在绑定到列表时查找。

Right away, your model should build just fine, but you'll also be able to add and remove items as well. 立即,您的模型应该构建良好,但是您也可以添加和删除项目。

Further Reading 进一步阅读

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM