94

I believe this is pretty simple, I just can't seem to find the right way to show the display name for an item within a list within my model.

My simplified model:

public class PersonViewModel
{
    public long ID { get; set; }
    
    private List<PersonNameViewModel> names = new List<PersonNameViewModel>();

    [Display(Name = "Names")]
    public List<PersonNameViewModel> Names { get { return names; } set { names = value; } }      
}

and Names:

public class PersonNameViewModel
{
    public long ID { get; set; }

    [Display(Name = "Set Primary")]
    public bool IsPrimary { get; set; }

    [Display(Name = "Full Name")]
    public string FullName { get; set; }
}

Now I'd like to make a table to show all the names for a person, and get the DisplayNameFor FullName. Obviously,

@Html.DisplayNameFor(model => model.Names.FullName);

wouldn't work, and

@Html.DisplayNameFor(model => model.Names[0].FullName);  

will break if there are no names. Is there a 'best way' to obtain the display name here?

1
  • Just a note for noobs, if the class attribute is not declared with { get; set; } the DisplayNameFor will not work at all! This rookie mistake happens to pros as well sometimes :D.
    – Honza P.
    Dec 22, 2021 at 13:35

5 Answers 5

150

This actually works, even without items in the list:

@Html.DisplayNameFor(model => model.Names[0].FullName)

It works because MVC parses the expression instead of actually executing it. This lets it find that right property and attribute without needing there to be an element in the list.

It's worth noting that the parameter (model above) doesn't even need to be used. This works, too:

@Html.DisplayNameFor(dummy => Model.Names[0].FullName)

As does this:

@{ Namespace.Of.PersonNameViewModel dummyModel = null; }
@Html.DisplayNameFor(dummyParam => dummyModel.FullName)
3
  • I'm assuming that if instead of a List<PersonNameViewModel> you have an IEnumerable<PersonNameViewModel>, then it's also safe to use the expression model => model.Names.First().FullName. Is this correct? That said, I think I like the dummyModel example best of the three. Otherwise, you could have several properties where you need to type or paste in model.Names[0].. Then again, maybe you should refactor the section to a partial view which accepts the List or IEnumerable as its model.
    – aaaantoine
    Mar 24, 2016 at 13:12
  • 6
    I tested and FirstOrDefault() does work with an empty list.
    – Josh K
    Jul 21, 2016 at 6:02
  • Unfortunately this doesn't work on IEnumerable property. Mar 28, 2020 at 1:48
9

There is another way for do it, and i guess that is more clear:

public class Model
{
    [Display(Name = "Some Name for A")]
    public int PropA { get; set; }

    [Display(Name = "Some Name for B")]
    public string PropB { get; set; }
}

public class ModelCollection
{
    public List<Model> Models { get; set; }

    public Model Default
    {
        get { return new Model(); }
    }
}

And then, in the view:

@model ModelCollection

<div class="list">
    @foreach (var m in Model.Models)
    {
        <div class="item">
            @Html.DisplayNameFor(model => model.Default.PropA): @m.PropA
            <br />
            @Html.DisplayNameFor(model => model.Default.PropB): @m.PropB
        </div>
    }
</div>
4

As an alternate solution you could try:

@Html.DisplayNameFor(x => x.GetEnumerator().Current.ItemName)

It will work even if the list is empty!

1
  • why should i use this solution instead of Tim S's solution?
    – EKanadily
    Aug 8, 2020 at 15:16
3

In ASP.NET Core, Html.DisplayNameForInnerType() solves this. It can be used this way:

Html.DisplayNameForInnerType((PersonNameViewModel person) => person.FullName)

Note that the type PersonNameViewModel has to be explicitly specified in the lambda expression's parameter.

This is what the API documentation has to say:

Returns the display name for the specified expression if the current model represents a collection.

It works when the current Model itself is a collection and also when one of its members is a collection.

1

I like T-moty's solution. I needed a solution using generics so my solution is essentially this:

public class CustomList<T> : List<T> where T : new()
{       
    public static async Task<CustomList<T>> CreateAsync(IQueryable<T> source)
    {           
        return new CustomList<T>(List<T> list); //do whatever you need in the contructor, constructor omitted
    }

    private T defaultInstance = new T();

    public T Default
    {
        get { return defaultInstance; }
    }
}

Using this in the view is the same as his example. I create a single instance of an empty object so I'm not creating a new instance every time I reference Default.

Note, the new() constraint is needed in order to call new T(). If your model class doesn't have a default contructor, or you need to add arguments to the constructor you can use this:

private T defaultInstance = (T)Activator.CreateInstance(typeof(T), new object[] { "args" });

The view will have an @model line like:

@model CustomList<Namespace.Of.PersonNameViewModel.Model>
1
  • 1
    Note that DisplayNameFor never actually calls the get for the property, so it doesn't matter if you create an instance.
    – NetMage
    Nov 13, 2019 at 0:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.