How to properly bind list<object> in the cre

2019-09-08 13:06发布

问题:

I have a problem similar to the hypothetical below. I have added partial views to another view multiple times but I'm not sure how to get them to bind properly to the view model etc. Also, the validation seems to trigger for every single fooName if a single fooName is wrong. I've added the index to the viewbag as you can see but I'm not really sure how to use it yet.

Note:Using MVC5.2

View Models

public class Thing
{
    public String thingName { get; set; }
    public List<Foo> Foos { get; set; }
}
public class Foo
{
    public String fooName { get; set; }
}

Foo View

@model Project.Models.Foo

<div class="form-horizontal">
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })

    <div class="form-group">
        <div class="col-md-12">
            @Html.LabelFor(model => model.fooName, htmlAttributes: new { @class = "control-label" })
            @Html.EditorFor(model => model.fooName, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.fooName, "", new { @class = "text-danger" })
        </div>
    </div>
</div>

Thing View

@model Project.Models.Thing
@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    <div class="form-horizontal">
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })

        <div class="form-group">
            <div class="col-md-12">
                @Html.LabelFor(model => model.thingName, htmlAttributes: new { @class = "control-label" })
                @Html.EditorFor(model => model.thingName, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.thingName, "", new { @class = "text-danger" })
            </div>
        </div>
    </div>
    <div class="add-foo">
        @* at some point add more foos with ajax, for now k.i.s.s. *@
        @Html.Partial("/Views/Foo/Create.cshtml", new ViewDataDictionary { { "id", 1 } })
        @Html.Partial("/Views/Foo/Create.cshtml", new ViewDataDictionary { { "id", 2 } })
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Create" class="btn btn-default" />
        </div>
    </div>
}

回答1:

The problem here is that the Foo partial is rendering without context of the larger model at play. As a result you're getting a bunch of inputs with all the same names and ids, i.e.:

<input type="text" name="fooName" id="fooName" class="form-control">
...
<input type="text" name="fooName" id="fooName" class="form-control">
...

That's why the validation is triggering on all of them, because they are all the same. In order to have the partial behave correctly, you would need to pass some context into it. For example, if you were iterating over existing instances of something you could do:

@for (var i = 0; i < Model.Foos.Count; i++)
{
    @Html.Partial("_Foo", Model.Foos[i])
}

Based on the Model.Foos[i] bit, Razor would then generate proper input names like Foos[0].fooName, Foos[1].fooName, etc.

Or, you can override the HtmlFieldPrefix:

@Html.Partial("_Foo", foo1, new ViewDataDictionary { TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Foos[0]" } })
@Html.Partial("_Foo", foo2, new ViewDataDictionary { TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Foos[1]" } })
...

Since you're planning on being able to add additional ones dynamically via JavaScript, your best bet though is to rely on something like Knockout or Angular to render the fields for you based on a JavaScript array. Then, when you add new Foo instances to that array, the library will automatically add additional fields to the page with an indexed name attribute.