Steve Sanderson's BeginCollectionItem helper won't bind correctly

后端 未结 1 1912
误落风尘
误落风尘 2020-12-03 16:01

I am using Steve Sanderson\'s BeginCollectionItem helper and ran into a problem. I have a form that has an option to add unlimited reward fields. I am using his helper since

相关标签:
1条回答
  • 2020-12-03 16:44

    Ok I think I see what is going on here.

    In the second sample, where you did the foreach, it looks like your cshtml was something like this (@ symbols may be incorrect):

    foreach (var war in Model.WarrantyFeaturesVm) {
        using (Html.BeginCollectionItem("WarrantyFeaturesVm")) {
            Html.HiddenFor(m => war.FeatureId)
            <span>@Html.DisplayFor(m => war.Name)</span>
            Html.HiddenFor(m => war.HasFeature)
        }
    }
    

    Because BeginCollectionItem uses its context to derive the HTML names and id's, this is why you end up with "war" in the id's and names. The model binder is looking for a collection property named "WarrantyFeaturesVm", which it finds. However it is then looking for a property named "war" on the WarrantyFeaturesVm viewmodel, which it cannot find, and thus does not bind.

    <input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191" 
        name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].war.FeatureId" 
        id="WarrantyFeaturesVm_68ba9241-c409-4f4b-96da-cce13b127c1e__war_FeatureId" .../>
    

    In the 3rd scenario, it is similar. It is looking for the WarranyFeaturesVm collection property, which it finds. It however looks for another collection item.

    <input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191" 
        name="WarrantyFeaturesVm[fe3fbc82-a2df-476d-a15a-dacd841df97e].WarrantyFeaturesVm[0].FeatureId" 
        id="WarrantyFeaturesVm_fe3fbc82-a2df-476d-a15a-dacd841df97e__WarrantyFeaturesVm_0__FeatureId" .../>
    

    In order to bind correctly, your HTML has to look similar to your first HTML example:

    <input type="hidden" value="68ba9241-c409-4f4b-96da-cce13b127c1e" 
        name="WarrantyFeaturesVm.index" .../>
    <input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191" 
        name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].FeatureId" 
        id="WarrantyFeaturesVm_68ba9241-c409-4f4b-96da-cce13b127c1e__FeatureId" .../>
    

    Like I hinted in my comment, you can achieve this by putting the BeginCollectionItem and everything it wraps into a partial view. The partial view will then receive its own context, since your helpers will use the view's @Model property with the stongly-typed helpers like so: @Html.WidgetFor(m => m.PropertyName).

    On the other hand, if you really need the collection to be rendered in the outer view, I don't see any problem using normal indexing (integer-based) with a for loop and without BeginCollectionItem.

    Update

    I dug up this old post from Phil Haack. An excerpt:

    ...by introducing an extra hidden input, you can allow for arbitrary indices. In the example below, we provide a hidden input with the .Index suffix for each item we need to bind to the list. The name of each of these hidden inputs are the same, so as described earlier, this will give the model binder a nice collection of indices to look for when binding to the list.

    <form method="post" action="/Home/Create">
    
        <input type="hidden" name="products.Index" value="cold" />
        <input type="text" name="products[cold].Name" value="Beer" />
        <input type="text" name="products[cold].Price" value="7.32" />
    
        <input type="hidden" name="products.Index" value="123" />
        <input type="text" name="products[123].Name" value="Chips" />
        <input type="text" name="products[123].Price" value="2.23" />
    
        <input type="hidden" name="products.Index" value="caliente" />
        <input type="text" name="products[caliente].Name" value="Salsa" />
        <input type="text" name="products[caliente].Price" value="1.23" />
    
        <input type="submit" />
    </form>
    

    BeginCollectionItem uses this indexing method to make sure the model binding happens. The only difference is it uses Guids instead of ints as the indexer. But you could manually set any indexer like in Phil's example above.

    0 讨论(0)
提交回复
热议问题