0

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>
}
Preston
  • 1,300
  • 1
  • 17
  • 32
  • 1
    Possibly related: http://stackoverflow.com/questions/29486946/asp-net-mvc-parent-child-view-with-parent-view-updating-children – stephen.vakil Sep 08 '16 at 18:46
  • 1
    If your wanting to dynamically add new items to the collection, refer [this answer](http://stackoverflow.com/questions/28019793/submit-same-partial-view-called-multiple-times-data-to-controller/28081308#28081308) for some options. –  Sep 08 '16 at 22:08

1 Answers1

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.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • At this point in the process Foos do not exist yet as this is the page for creating them. So when I tell razor Foos[i] I get nullReferenceException. I feel like I should be using a lambda expression there to pass in a template. How is that supposed to work? – Preston Sep 08 '16 at 21:36
  • That was just an example to illustrate that Razor needs context in order to generate the right names. If you're creating these dynamically, then it's JavaScript, not Razor that will need to generate the inputs. You just need to make sure that the input names align with what the model binder will expect on POST. – Chris Pratt Sep 09 '16 at 12:46