7

How get a property display name using DisplayNameFor()to build a table header. for instance:

   @model IEnumerable<Item>
   <table class="table">
        <thead>
            <tr>
                <td>@Html.DisplayNameFor(? => ?.prop1)</td>
                <td>@Html.DisplayNameFor(? => ?.prop2)</td>
                <td>@Html.DisplayNameFor(? => ?.prop3)</td>
            </tr>
        </thead>
        <tbody>
            @foreach (Item item in Model) {
                <tr>
                    <td>@Html.DisplayFor(i => item.prop1)</td>
                    <td>@Html.DisplayFor(i => item.prop2)</td>
                    <td>@Html.DisplayFor(i => item.prop3)</td>
                </tr>
            }    
        </tbody>
    </table>

what should I write in the question marks?

Daniel Santos
  • 14,328
  • 21
  • 91
  • 174
  • 1
    Using `LabelFor()` is not appropriate (it generates a label that is intended to associated with a form control, but you have many). Use `@Html.DisplayNameFor(m => m.prop1)` –  Aug 19 '16 at 01:52
  • So what problem are you now having? (see the code in my previous comment) –  Aug 19 '16 at 02:01
  • Model is a IEnumerable not the Item class.. – Daniel Santos Aug 19 '16 at 02:10
  • 2
    Yes I know. Try it :) (`DisplayNameFor()` has an [overload](https://msdn.microsoft.com/en-us/library/system.web.mvc.html.displaynameextensions.displaynamefor(v=vs.118).aspx) for `IEnumerable`) –  Aug 19 '16 at 02:12
  • @StephenMuecke I've never noticed this overload! I hope you'll have time to post this as an answer so others can find it. There's a lot more of [this](http://stackoverflow.com/a/20808067/4270650) around. – Will Ray Aug 19 '16 at 02:25
  • 1
    @WillRay, The answer in that link is correct and necessary because the `model` is not `IEnumerable` (its a model that contains a property that is `IEnumerable`) - I'm trying to find a suitable dupe, but if not I'll add an answer –  Aug 19 '16 at 02:30

3 Answers3

10

DisplayNameFor() has an overload that accepts IEnumerable<T> so it simply needs to be

<td>@Html.DisplayNameFor(m => m.prop1)</td>

Note that this only works where the the model is Enumerable<T> (in you case @model IEnumerable<Item>).

But will not work if the model was an object containing a proeprty which was IEnumerable<T>.

For example, the following will not work

<td>@Html.DisplayNameFor(m => m.MyCollection.prop1)</td>

and it would need to be

<td>@Html.DisplayNameFor(m => m.MyCollection.FirstOrDefault().prop1)</td>

which will work even if the collection contains no items.

Side note: Under some circumstances, you may initially get a razor error, but you can ignore it. Once you run the app, that error will disappear.

Daniel Santos
  • 14,328
  • 21
  • 91
  • 174
  • I actually liked your answer more than mine, I didn't know about that overload. If `MyCollection` is an IEnumerable, then you'd need to use something like `m.MyCollection.First().prop1`, wouldn't you? Because since it wouldn't be an array, your couldn't use `MyCollection[0]`. Is that right? – Alisson Reinaldo Silva Aug 19 '16 at 02:46
  • @Alisson, Your right (the indexer would be for a property which is `IList`) - have updated. –  Aug 19 '16 at 02:50
3

You could do like this:

   @model IEnumerable<Item>
   <table class="table">
        <thead>
            <tr>
                <th>@Html.DisplayNameFor(i => i.First().prop1)</th>
                <th>@Html.DisplayNameFor(i => i.First().prop2)</th>
                <th>@Html.DisplayNameFor(i => i.First().prop3)</th>
            </tr>
        </thead>
        <tbody>
            @foreach (Item item in Model) {
                <tr>
                    <td>@Html.DisplayFor(i => item.prop1)</td>
                    <td>@Html.DisplayFor(i => item.prop2)</td>
                    <td>@Html.DisplayFor(i => item.prop3)</td>
                </tr>
            }    
        </tbody>
    </table>

It may look like you are actually getting the first item of the IEnumerable, but you are not.

Since the parameter you are passing to DisplayFor() is just an Expression Tree, it won't execute IEnumerable's First() method at all, internally DisplayFor() will only check for the passed type (the parameter's type) to use reflection and build a display for it.

Alisson Reinaldo Silva
  • 10,009
  • 5
  • 65
  • 83
1

If you change the model to an IList<Item> or Item[] you can do this:

   @model IList<Item>
   <table class="table">
        <thead>
            <tr>
                <td>@Html.DisplayNameFor(x => x[0].prop1)</td>
                <td>@Html.DisplayNameFor(x => x[0].prop2)</td>
                <td>@Html.DisplayNameFor(x => x[0].prop3)</td>
            </tr>
        </thead>
        ...
    </table>
Max Toro
  • 28,282
  • 11
  • 76
  • 114
  • This would fail if the item list is empty. Better add condition before creating the table. – Ankit Vijay Aug 19 '16 at 02:16
  • 2
    @AnkitVijay the lambda expression is not executed, only analyzed. – Max Toro Aug 19 '16 at 02:19
  • I could be wrong about the list, however, I feel we would still execute all these lines of code when it is not needed. If condition can save us on that? – Ankit Vijay Aug 19 '16 at 02:24
  • 1
    @MaxToro that's why I think there is no need to use `IList` or `Item[]`. The code will just analyze the expression to get metadata and build a display. It would work even if the `IEnumerable` was null. Actually I think this should work even with a null model, since the code won't get an object instance from the expression tree at all. – Alisson Reinaldo Silva Aug 19 '16 at 02:25