Alan B
Alan B

Reputation: 2309

ASP.NET MVC Binding Nested List

I am using the solution suggested in Mel's space (https://mleeb.wordpress.com/2013/11/23/editing-nested-lists-in-asp-mvc-4/) for editing nested list

Basically I have my model as below

ProductEditModel

 --> ProductAudioEditModel

    --> ProductAssetResourceEditModel

I got this working for the below

 @Html.EditorFor(c => resource.TrackTitle, null, Html.GetHtmlName("TrackTitle"))

which gives me the correct value when it's edited.

However, I couldnt get this working for DropDownList or I am not able to pick the edited value in the dropdownlist . It always give me the original value in my controller.

 @using (Html.BeginCollectionItem("ProductAssetAudios", out parentIdentifier))
  {
       .....
        @foreach (var resource in Model.ProductAssetResources.OrderBy(a => a.ResourceNumber))
          {
            string childIdentifier = null; 
             @using (Html.BeginChildCollectionItem("ProductAssetResources", parentIdentifier, out childIdentifier))
              {
                  @Html.HiddenFor(model => resource.AssetResourceStatusId, new { Name = Html.GetHtmlName(childIdentifier, "AssetResourceStatusId") })
                  @Html.DropDownListFor(model => resource.AssetResourceStatusId, new SelectList(visibleResourceStatuses, "AssetResourceStatusId", "Name", resource.AssetResourceStatusId),  new { @class = "inherit-title" }) @Html.ValidationMessageFor(model => resource.AssetResourceStatusId)

              }
          }
  }

The AssetResourceStatusId always holding the original value even though the drop down list is selected for a different value.

I was hoping that the EditorFor and DropDownListFor should be work in the same manner when editing nested list.

Edited

Generated HTML

DropDownListFor

    <select class="inherit-title valid" id="ProductAssetAudios_0df86a5c-0a32-4b0f-97ee-3b3254f743d9__ProductAssetResources_c58ba43c-6081-41d4-88fd-d59799c7374e__resource_AssetResourceStatusId" name="ProductAssetAudios[0df86a5c-0a32-4b0f-97ee-3b3254f743d9].ProductAssetResources[c58ba43c-6081-41d4-88fd-d59799c7374e].resource.AssetResourceStatusId" aria-invalid="false"><option value="3">Extra</option>
    <option selected="selected" value="2">Found</option>
    <option value="8">Ignore</option>
    </select>

HiddenFor

<input name="ProductAssetAudios[b5670a6a-7a1d-4c76-86bc-85a05cd144c1].ProductAssetResources[aa378d38-0fb7-4304-9f24-79d0efcb36b9].AssetResourceStatusId" data-val="true" data-val-number="The field AssetResourceStatusId must be a number." data-val-required="The AssetResourceStatusId field is required." id="ProductAssetAudios_b5670a6a-7a1d-4c76-86bc-85a05cd144c1__ProductAssetResources_aa378d38-0fb7-4304-9f24-79d0efcb36b9__resource_AssetResourceStatusId" type="hidden" value="2">

-Alan-

Upvotes: 2

Views: 4892

Answers (1)

user3559349
user3559349

Reputation:

You model contains a collection property named ProductAssetAudios (typeof ProductAudioEditModel) and each object in that collection contains a collection property named ProductAssetResources (typeof ProductAssetResourceEditModel) and each of those objects contains a property named AssetResourceStatusId.

In C# code, if you were to get the AssetResourceStatusId value of the 1st ProductAssetResourceEditModel in the 1st ProductAudioEditModel, your code would be

var id = model.ProductAssetAudios[0].ProductAssetResources[0].AssetResourceStatusId;

Drop the model prefix and that is exactly how the name attribute of the control must be. What the BeginCollectionItem() and BeginChildCollectionItem() methods do is to modify the collection indexers to a Guid and adds a hidden input for the indexer to allow you to dynamically add and remove items from the collection. By default, the DefaultModelBinder will bind collections with zero-based consecutive indexers, unless a value for the indexers is also posted (i.e the reason why the hidden input is added).

In your case, the name attribute for the hidden input is correct, i.e. using

@Html.HiddenFor(model => resource.AssetResourceStatusId, new { Name = Html.GetHtmlName(childIdentifier, "AssetResourceStatusId") })

because your overriding the default name attribute generated by HiddenFor(). You just need to do the same for the DropDownListFor() method, i.e. set the name attribute using new { Name = Html.GetHtmlName(childIdentifier, "AssetResourceStatusId") }. But then you also need to then delete the hidden input because the DefaultModelBinder will only bind the first value that is posted for a property. Note also that you will need to change the ValidationMessageFor() also.


Side note. From the comments it appears that you are not wanting to add and remove items in the view, in which case, do not use the BeginCollectionItem() and BeginChildCollectionItem() methods. Instead, just use nested for loops or custom EditorTemplates for typeof ProductAudioEditModel and ProductAssetResourceEditModel. An example of using for loops would be

for(int i = 0; i < Model.ProductAssetAudios.Count; i++)
{
    @Html.TextBoxFor(m => m.ProductAssetAudios[i].SomeProperty)
    ....
    for (int j = 0; j < Model.ProductAssetAudios[i].ProductAssetResources.Count; j++)
    {
        @Html.DropDownListFor(m => m.ProductAssetAudios[i].ProductAssetResources[j].AssetResourceStatusId, new SelectList(.....)

Refer also this answer for an example of using a nested EditorTemplate.

Upvotes: 1

Related Questions