I have a nested class such as :
public class Jar
{
public IEnumerable<NailClippings> Nails { set; get; }
public IEnumerable<People> Donors { set; get; }
}
My controller passes a single Jar
to my view, string typed as
@model Test.ViewModels.Jar
I can loop through the contents of Nails
and Donors
easily with something like this
@foreach(var item in Model.Nails)
My issue is with using HTML helpers to generate Display names for me
@Html.DisplayNameFor(model => model.Nails.Length)
I have come up with a solution by changing the type of the inner nested classes to List
, appending .ToList()
at the end of my queries and changing the line to
@Html.DisplayNameFor(model => model.Nails[0].Length)
Being forced to use List
and have an index [0]
to display a name is goofy to me. Is there an alternative method for referencing inner nested class attributes?
Edit: View
<table>
<tr>
<th>@Html.DisplayNameFor(model => model.Nails[0].Length)</th>
.
.
</tr>
@foreach(var item in Model.Nails){
<tr>
<td>@Html.DisplayFor(modelItem => item.Length</td>
.
.
</tr>
}
</table>
Instead of
model.Nails[0].Length
You could use
model.Nails.First().Length
Without having to convert to a List
.
You don't need to be writing any loops in your views. It makes them so ugly.
Simply define a corresponding display template for the collection type. So for example you would create ~/Views/Shared/DisplayTemplates/NailClippings.cshtml
partial view that will be strongly typed to a single element of the collection:
@model NailClippings
<tr>
<td>
@Html.DisplayFor(x => x.Length)
</td>
.
.
</tr>
and now inside your main view it's as simple as:
@model Jar
<table>
<thead>
<tr>
<th>Length</th>
.
.
</tr>
</thead>
<tbody>
@Html.DisplayFor(x => x.Nails)
</tbody>
</table>
Display/editor templates work by convention. So here when inside your main view you use @Html.DisplayFor(x => x.Nails)
, ASP.NET MVC will analyze the type of the Nails
property and will see that it is an IEnumerable<NailClippings>
collection. So it will start looking for a corresponding display template first inside ~/Views/CurrentController/DisplayTemplates/NailClippings.cshtml
, then inside ~/Views/Shared/DisplayTemplates/NailClippings.cshtml
and finally it will fallback to the default display template
. When a template is selected this template will automatically be executed for you by ASP.NET MVC for each element of the collection property so that you never have to worry about things like writing some loops in your views, worry about indexes, ...
This also works with editor templates: @Html.EditorFor(x => x.Nails)
will look for ~/Views/Shared/EditorTemplates/NailClippings.cshtml
. The bonus you get here is that the generated input fields will have correct names and you when you submit the form, the default model binder will automatically rehydrate your view model that the HttpPost action is taking as argument from the request.
Basically if you respect the conventions that the framework is following you will make your life much easier.
I think you can just use item
variable of loop
@Html.DisplayNameFor(model => item.Length)
UPDATE another option for you is creating partial view for Nails displaying. It will be strongly typed to IEnumerable<NailClippings>
and all properties will be available:
@Html.Partial("_Nails", Model.Nails)
Nails partial view:
@model IEnumerable<NailClippings>
<table>
<tr>
<th>@Html.DisplayNameFor(model => model.Length)</th>
.
.
</tr>
@foreach(var item in Model){
<tr>
<td>@Html.DisplayFor(modelItem => item.Length)</td>
.
.
</tr>
}
</table>
I think this sample code is useful for nested model class
@model ViewModels.MyViewModels.Theme
@Html.LabelFor(Model.Theme.name)
@foreach (var category in Model.Theme)
{
@Html.LabelFor(category.name)
@foreach(var product in theme.Products)
{
@Html.LabelFor(product.name)
@foreach(var order in product.Orders)
{
@Html.TextBoxFor(order.Quantity)
@Html.TextAreaFor(order.Note)
@Html.EditorFor(order.DateRequestedDeliveryFor)
}
}
}