DisplayNameFor() From List<Object> in Model

C#asp.net MvcRazorModel

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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)

Solution 2 - C#

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>

Solution 3 - C#

As an alternate solution you could try:

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

It will work even if the list is empty!

Solution 4 - C#

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.

Solution 5 - C#

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>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJonesopolisView Question on Stackoverflow
Solution 1 - C#Tim S.View Answer on Stackoverflow
Solution 2 - C#T-motyView Answer on Stackoverflow
Solution 3 - C#Fábio FabianView Answer on Stackoverflow
Solution 4 - C#Amal KView Answer on Stackoverflow
Solution 5 - C#shoxView Answer on Stackoverflow