DisplayNameFor () da lista <Objeto> no modelo

87

Acredito que isso seja muito simples, mas não consigo encontrar a maneira certa de mostrar o nome de exibição de um item em uma lista dentro do meu modelo.

Meu modelo simplificado:

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; } }      
}

e nomes:

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; }
}

Agora, gostaria de fazer uma tabela para mostrar todos os nomes de uma pessoa e obter o DisplayNameFor FullName. Obviamente,

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

não funcionaria, e

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

será interrompido se não houver nomes. Existe uma 'melhor maneira' de obter o nome de exibição aqui?

Jonesopolis
fonte

Respostas:

143

Isso realmente funciona, mesmo sem itens na lista:

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

Funciona porque o MVC analisa a expressão em vez de realmente executá-la. Isso permite que ele encontre a propriedade e o atributo corretos sem a necessidade de haver um elemento na lista.

É importante notar que o parâmetro ( modelacima) nem precisa ser usado. Isso também funciona:

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

Assim como isso:

@{ Namespace.Of.PersonNameViewModel dummyModel = null; }
@Html.DisplayNameFor(dummyParam => dummyModel.FullName)
Tim S.
fonte
Estou assumindo que, se em vez de List<PersonNameViewModel>você tiver um IEnumerable<PersonNameViewModel>, também é seguro usar a expressão model => model.Names.First().FullName. Isso está correto? Dito isso, acho que gosto do dummyModelmelhor exemplo dos três. Caso contrário, você pode ter várias propriedades nas quais precisa digitar ou colar model.Names[0].. Então, novamente, talvez você deva refatorar a seção para uma visualização parcial que aceite o Listou IEnumerablecomo seu modelo.
aaaantoine
6
Eu testei e FirstOrDefault()funciona com uma lista vazia.
Josh K
Infelizmente, isso não funciona na propriedade IEnumerable.
Willy David Jr
7

Há outra maneira de fazer isso, e acho que é mais clara:

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(); }
    }
}

E então, na visualização:

@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>
T-moty
fonte
1

Gosto da solução de T-moty. Eu precisava de uma solução usando genéricos, então minha solução é essencialmente esta:

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; }
    }
}

Usar isso na visualização é o mesmo que seu exemplo. Eu crio uma única instância de um objeto vazio, então não estou criando uma nova instância toda vez que faço referência a Default .

Observe que a restrição new () é necessária para chamar new T () . Se sua classe de modelo não tem um construtor padrão, ou você precisa adicionar argumentos ao construtor, você pode usar isto:

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

A visualização terá uma linha @model como:

@model CustomList<Namespace.Of.PersonNameViewModel.Model>
shox
fonte
Observe que DisplayNameFornunca realmente chama o getfor para a propriedade, portanto, não importa se você criar uma instância.
NetMage
1

Como solução alternativa, você pode tentar:

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

Funcionará mesmo se a lista estiver vazia!

Fábio Fabian
fonte
por que devo usar esta solução em vez da solução de Tim S?
EKanadily