How to pass IEnumerable<SelectListItem> collection to a MVC Partial View with only one database lookup per request

StackOverflow https://stackoverflow.com/questions/19735372

Question

Suppose that given a partial view for an html table, there exists a nested partial view for each table row such as the following:

Outer Partial View

<table>
<thead>
  <tr>
    <th>Item Name</th>
    <th>Cost</th>
    <th>Category</th>
  </tr>
</thead>
<tbody>
@for (int i = 0; i < Model.PurchaseItems.Count; i++)
{ 
  @Html.Partial("_PurchaseItemsDetail", Model.PurchaseItems[i])
}     
</tbody>
</table>

Inner Partial View, NOTE: this partial view may used multiple times by the outer view in a single request, however it may also be used just once if requested using AJAX

<tr id="@Model.ID">
  <td>@Html.TextBoxFor(x => x.ItemName)</td>
  <td>@Html.TextBoxFor(x => x.Cost)</td>
  <td>
    @Html.DropDownListFor(x => x.Category, CATEGORY_COLLECTION)
  </td>
</tr>

Problem: Given this inner view that is intended to be flexible, what is the best way to query the database and temporarily store the IEnumerable<SelectListItem> collection to be used with the dropdown? Ways that I can think of:

Method 1: This seems to be the easiest, however it feels like it violates MVC principles. Evaluate in the inner partial view and store in ViewData collection in such a way that the database is only hit once:

    @{
     if (ViewData["Categories"] == null)
     {
       ViewData["Categories"] = API.GetCategories();
     }
    }

then later in the view

  <td>
    @Html.DropDownListFor(x => x.Category, ViewData["Categories"])
  </td>

This is nice because the view takes care of itself as far as determining how to populate its containing dropdown. It does not rely on the view model.

Method 2: Same as above, except set the ViewData in the various controller methods that return the inner partial view. This way seems to be more in line with MVC best practices, however it seems more tedious and confusing to maintain given that each controller method needs to set the ViewData properly, as well as create the necessary view model.

Method 3: This seems the most work, the hardest to maintain, but most in accordance with MVC principles. Avoid using the ViewData entirely, but store a reference to the collection along with the other properties in the partial view model. It would be the responsibility of the controller to create the collection, and then store a reference in each of the table rows' view models.

public class PurchaseItemModel
{
  public string ItemName { get; set; }
  public decimal Cost { get; set; }
  public string Category { get; set; }

  // add this
  public IEnumberable<SelectListItem> Categories { get; set; }
}

and in each controller that server the inner view

// if dealing with multiple rows (GET)
IEnumerable<SelectListItem> collection = API.GetPurchaseItemList();
foreach (PurchaseItemModel pim in OuterModel.PurchaseItemModels)
{
  pim.Categories = collection;
}

or

// if dealing with a single row (Ajax)
purchaseItem.Categories = API.GetPurchaseItemList();

then later in the view

  <td>
    @Html.DropDownListFor(x => x.Category, x.Categories)
  </td>

Perhaps this is just subjective question, but it seems like there would be a best practice for this type of situation. Any thoughts?

Was it helpful?

Solution

Go ahead and create a ViewModel Class such as

public class PurchaseItemViewModel
{
  public string ItemName { get; set; }
  public decimal Cost { get; set; }
  public string Category { get; set; }
  public IEnumberable<Category> Categories { get; set; }

  public PurchaseItemViewModel(PurchaseItemModel item, List<Category> categories)
  {
       //initialize item and categories
  }
}

In the controller get all the items then get all the categories then set the Purchase items to the ViewModel and set the categories. So you will pass the ViewModel Class to the view.

OTHER TIPS

Method 3 is the way to go - but don't make it so complicated. Have two properties - a PurchaseItem property and a Categories property. Then instantiate per iteration and set the properties.

Loose example:

<table>
<thead>
  <tr>
    <th>Item Name</th>
    <th>Cost</th>
    <th>Category</th>
  </tr>
</thead>
<tbody>
@for (int i = 0; i < Model.PurchaseItems.Count; i++)
{ 
    var vm = new PurchaseItemsDetailViewModel()
        {
            PurchaseItem = Model.PurchaseItems[i],
            Categories = Model.CategoriesSelectList // or whatever holds the categories select list
        };
  @Html.Partial("_PurchaseItemsDetail", vm)
}     
</tbody>
</table>


<tr id="@Model.PurchaseItem.ID">
  <td>@Html.TextBoxFor(x => x.PurchaseItem.ItemName)</td>
  <td>@Html.TextBoxFor(x => x.PurchaseItem.Cost)</td>
  <td>
    @Html.DropDownListFor(x => x.PurchaseItem.Category, x.Categories)
  </td>
</tr>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top