31

In my model I have an Entity

public class Carrier
{
public Guid CarrierId { get; set; }
public string Name { get; set; }
}

I also have a ViewModel

public class CarrierIndexViewModel
{
public IEnumerable<Carrier> Carriers { get; set; }
public PagingInfo PagingInfo { get; set; }
}

I have a strongly-typed (CarrierIndexViewModel) Index View, which is suppose to display a table of Carrier using PagingInfo.

I'm trying to use Html helper to display Carrier.Name in my table's header When I used IEnumerable as a model for this view I had @Html.DisplayFor(model => model.Name)

How can I get same result using my new CarrierIndexViewModel to display title for Carrier.Name?

tereško
  • 58,060
  • 25
  • 98
  • 150
Alex
  • 389
  • 1
  • 3
  • 8

5 Answers5

43

I found there was no need for helper methods, extra code or looping through the collection. I just used the following:

@Html.DisplayNameFor(model => model.Carriers.FirstOrDefault().Name)

This still works even if FirstOrDefault() would return null because it's only looking for meta data, the Name property itself is not accessed.

Many thanks to @Kurian who inspired this answer.

Community
  • 1
  • 1
Red Taz
  • 4,159
  • 4
  • 38
  • 60
  • Thanks. Just what I've been searching for. If Carriers is a list<>, you can use Carriers[0] or Carriers.FirstOrDefault() . – Greg Little Jul 30 '14 at 20:02
17

I've got a similar problem, because i don't want to type my column names by hand so i wrote a helper:

public static IHtmlString DisplayColumnNameFor<TModel, TClass, TProperty>
    (this HtmlHelper<TModel> helper, IEnumerable<TClass> model,
    Expression<Func<TClass, TProperty>> expression)
{ 
    var name = ExpressionHelper.GetExpressionText(expression);
    name = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
    var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(
        () => Activator.CreateInstance<TClass>(), typeof(TClass), name);

    return new MvcHtmlString(metadata.DisplayName);
}

Then in the view you'll call it

@Html.DisplayColumnNameFor(Model.Carriers, m => m.Name)

Please note that you can use Display attribute which accepts Name and many other customization like Resource file etc.

public class Carrier
{
  [Display(Name="Carrier ID")]
  public Guid CarrierId { get; set; }
  [Display(Name="Carrier Name")]
  public string Name { get; set; }
}
ahsteele
  • 26,243
  • 28
  • 134
  • 248
Matija Grcic
  • 12,963
  • 6
  • 62
  • 90
  • 1
    I haven't tried this yet but I think you can just say "m => m.Carriers[0].Name". Since it uses metadata from lambda expressions internally it shouldn't matter if "Carriers" is null or empty. – Monstieur Mar 28 '13 at 09:29
  • 1
    Can I suggest a slight tweak? If the Display Name attribute has not been applied, you'll want to return the property name like so: "return new MvcHtmlString( metadata.DisplayName ?? name )" – Darryl Apr 05 '13 at 17:44
  • I like the idea behind this solution, but I feel like I hear little alarms going off when I think about it (maybe I shouldn't think about it :) ). If I was going to take the time to write the display value for a property in my code, why not just take the time and write the display value in the view? It seems like more code was written just to prevent inserting values into a view, when that's really where it should be anyway. – jaryd Dec 06 '13 at 16:52
  • 2
    @phreak3eb If you want to hardcore the value do it. I've used this approach so i can localize the [Display(Name="Some name")] and to avoid hard-coding. You can take a look here for the *.resx solution http://stackoverflow.com/a/20383378/1241400 – Matija Grcic Dec 06 '13 at 23:52
  • @plurby Ok that is a much better approach. Even better it supports localization! :) – jaryd Dec 08 '13 at 21:42
  • If `TClass` doesn't have a public parameterless constructor, `Activator.CreateInstance` will fail. Instead, use `default(TClass)`. – Aly Elhaddad Apr 25 '17 at 10:09
10

You can add a @using statement to your View to specify the Carrier type, then you will need to loop through your Model's Carriers property.

Your View will look something like..

@model CarrierIndexViewModel
@using Carrier

@foreach(Carrier c in Model.Carriers)
{
 @Html.DisplayFor(model => c.Name)
}
Jed
  • 10,649
  • 19
  • 81
  • 125
4

I had a similar requirement, but I needed to get display names for properties from a different non-enumerable class so the solution from @plurby wouldn't have worked. I ended up solving this by creating a fluent call that can be used to get a new HtmlHelper for any arbitrary type. The code for the extension and adapter is:

public static class HtmlHelpers
{
    public static HtmlHelperAdapter<TModel> Adapt<TModel>(this HtmlHelper<TModel> helper)
    {
        return new HtmlHelperAdapter<TModel>(helper);
    }

    public class HtmlHelperAdapter<TModel>
    {
        private readonly HtmlHelper<TModel> _helper;

        public HtmlHelperAdapter(HtmlHelper<TModel> helper)
        {
            _helper = helper;
        }

        public HtmlHelper<TNewModel> For<TNewModel>()
        {
            return new HtmlHelper<TNewModel>(_helper.ViewContext, new ViewPage(), _helper.RouteCollection);
        }
    }
}

You can then use this in the view like this:

<td>@(Html.Adapt().For<MyOtherModel>().DisplayNameFor(m => m.SomeProperty))</td>

One of the advantages is that this approach will work for any HtmlHelper extension, not just DisplayNameFor (but keep in mind that the adapted HtmlHelper does not have any view data since that's all typed based on the model being used in the view and thus won't work in the adapted HtmlHelper).

daveaglick
  • 3,600
  • 31
  • 45
4

Here is an approach, which is based on the original DisplayNameFor extension method and works consistent.

An extension method for HtmlHelper:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;

public static class HtmlHelperExtensions
{
    public static MvcHtmlString DisplayNameFor<TModel, TEnumerable, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TEnumerable>>> enumerableExpression, Expression<Func<TEnumerable, TValue>> valueExpression)
    {
        var metadata = ModelMetadata.FromLambdaExpression(valueExpression, new ViewDataDictionary<TEnumerable>());
        string displayName = metadata.DisplayName ?? metadata.PropertyName ?? ExpressionHelper.GetExpressionText(valueExpression).Split('.').Last();
        return new MvcHtmlString(HttpUtility.HtmlEncode(displayName));
    }
}

And that's how it could be used in a view:

@Html.DisplayNameFor(m => m.Carriers, c => c.Name)
Deilan
  • 4,740
  • 3
  • 39
  • 52