Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you access the DisplayNameFor in a nested model

How do you access the DisplayNameFor in a nested model - ie.:

public class Invoice
{
    public int InvoiceId { get; set; }
    public string CustomerName { get; set; }
    public string Contact { get; set; }
    public IList<InvoiceItem> InvoiceItems { get; set; }
}

public class InvoiceItem
{
    public int InvoiceItemId { get; set; }
    public int InvoiceId { get; set; }
    [Display(Name = "Item Heading")]
    public string Item { get; set; }
    [Display(Name = "Item Description")]
    public string Description { get; set; }
    public virtual Invoice Invoice { get; set; }
}

I can loop through the nested items as follows:

     @foreach (var item in Model.InvoiceItems)
     {
          @Html.DisplayFor(modelItem => item.Item)
          <br />
          @Html.DisplayFor(modelItem => item.Description)
          <br />
     }  

But I how do I get to: [Display(Name = "Item Heading")]

@Html.DisplayNameFor(modelItem => modelItem.Item)

I get the error: 'inv5.Models.Invoice' does not contain a definition for 'Item' and no extension method 'Item' accepting a first argument of type 'inv5.Models.Invoice' could be found

Thanks, Mark

like image 923
Mark Avatar asked May 22 '13 13:05

Mark


3 Answers

Here you're accessing the item in your loop:

@Html.DisplayFor(modelItem => item.Item)

But here you're not:

@Html.DisplayNameFor(modelItem => modelItem.Item)

Change the latter to this and it should work:

@Html.DisplayNameFor(modelItem => item.Item)

modelItem is of type Invoice (the model passed to the view), but in the loop you want to use item (from the loop iterator) which is of type InvoiceItem.

Side note: Debugging something like this might be a lot more straightforward if everything wasn't called "item." Clear naming is important :)

Edit: If you're accessing it outside of the loop, then you need to drill down into the model manually. Something like this:

@Html.DisplayNameFor(modelItem => modelItem.InvoiceItems.First().Item)

It feels like this would be dangerous, since .First() can throw an exception if there are no items. However, as comments have indicated and some cursory Googling seems to confirm, .First() isn't actually executed at runtime in this case. This is just a bit of trickery to allow .DisplayNameFor() to reflect down into the property.

like image 56
David Avatar answered Oct 19 '22 17:10

David


You can create a DisplayTemplate for your InvoiceItems, say "InvoiceItemHeader":

@model IEnumerable<InvoiceItem>

@Html.DisplayNameFor(m => m.Item)
<br />
@Html.DisplayNameFor(m => m.Description)

The type of the model has to be IEnumerable<T>, as the DisplayNameFor Method has an overload version for this type.

Then in your view you can display the template:

 @Html.DisplayFor(m => m.InvoiceItems, "InvoiceItemHeader")
 @foreach (var item in Model.InvoiceItems)
 {
      @Html.DisplayFor(modelItem => item.Item)
      <br />
      @Html.DisplayFor(modelItem => item.Description)
      <br />
 } 
like image 3
Zabavsky Avatar answered Oct 19 '22 16:10

Zabavsky


I have not tried this, but it should work.

@Html.DisplayNameFor(modelItem => modelItem.First().InvoiceItems().First().Item)

DisplayNameFor extension method is nothing else than a method expecting a Memberexpression with this signature: Expression<Func<TModel, TValue>> expression

like image 1
Jürgen Steinblock Avatar answered Oct 19 '22 17:10

Jürgen Steinblock