web-dev-qa-db-fra.com

ASP.net MVC - Modèle d'affichage pour une collection

J'ai le modèle suivant dans MVC:

public class ParentModel
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }

    public IEnumerable<ChildModel> Children { get; set; }
}

Lorsque je veux afficher tous les enfants du modèle parent, je peux le faire:

@Html.DisplayFor(m => m.Children)

Je peux ensuite créer un modèle d'affichage ChildModel.cshtml et DisplayFor parcourra automatiquement la liste.

Que faire si je souhaite créer un modèle personnalisé pour IEnumerable?

@model IEnumerable<ChildModel>

<table>
    <tr>
        <th>Property 1</th>
        <th>Property 2</th>
    </tr>
    ...
</table>

Comment puis-je créer un modèle d'affichage dont le type de modèle est IEnumerable<ChildModel>, Puis appeler @Html.DisplayFor(m => m.Children) sans qu'il se plaint que le type de modèle est incorrect?

43
Dismissile

Comme ça:

@Html.DisplayFor(m => m.Children, "YourTemplateName")

ou comme ça:

[UIHint("YourTemplateName")]
public IEnumerable<ChildModel> Children { get; set; }

où vous auriez évidemment ~/Views/Shared/DisplayTemplates/YourTemplateName.cshtml:

@model IEnumerable<ChildModel>

<table>
    <tr>
        <th>Property 1</th>
        <th>Property 2</th>
    </tr>
    ...
</table>
64
Darin Dimitrov

Ceci est en réponse au commentaire de Maslow. C'est ma première contribution à SO, donc je n'ai pas assez de réputation pour commenter - d'où la réponse comme réponse.

Vous pouvez définir la propriété 'TemplateHint' dans ModelMetadataProvider. Cela raccorderait automatiquement tout IEnumerable à un modèle que vous spécifiez. Je viens de l'essayer dans mon projet. Code ci-dessous -

protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor)
    {
        var metaData = base.CreateMetadataFromPrototype(prototype, modelAccessor);
        var type = metaData.ModelType;

        if (type.IsEnum)
        {
            metaData.TemplateHint = "Enum";
        }
        else if (type.IsAssignableFrom(typeof(IEnumerable<object>)))
        {
            metaData.TemplateHint = "Collection";
        }

        return metaData;
    }

Vous remplacez essentiellement la méthode "CreateMetadataFromPrototype" du "CachedDataAnnotationsModelMetadataProvider" et enregistrez votre type dérivé comme ModelMetadataProvider préféré.

Dans votre modèle, vous ne pouvez pas accéder directement aux ModelMetadata des éléments de votre collection. J'ai utilisé le code suivant pour accéder à ModelMetadata pour les éléments de ma collection -

@model IEnumerable<object>
@{ 
var modelType = Model.GetType().GenericTypeArguments[0];
var modelMetaData = ModelMetadataProviders.Current.GetMetadataForType(null, modelType.UnderlyingSystemType);

var propertiesToShow = modelMetaData.Properties.Where(p => p.ShowForDisplay);
var propertiesOfModel = modelType.GetProperties();

var tableData = propertiesOfModel.Zip(propertiesToShow, (columnName, columnValue) => new { columnName.Name, columnValue.PropertyName });
}

À mon avis, j'appelle simplement @ Html.DisplayForModel () et le modèle est chargé. Il n'est pas nécessaire de spécifier 'UIHint' sur les modèles.

J'espère que cela avait une certaine valeur.

8
swazza85

Dans ma question sur la non-sortie des vues, j'ai en fait un exemple de la façon de créer un modèle avec une collection de modèles enfants et de les rendre tous.

Modèles d'affichage ASP.NET - Aucune sortie

Essentiellement, vous devez créer un modèle qui sous-classe List<T> ou Collection<T> et utilisez ceci:

@model ChildModelCollection 

@foreach (var child in Model)
{
    Html.DisplayFor(m => child);
}

Dans votre modèle pour le modèle de collection pour itérer et rendre les enfants. Chaque enfant doit être fortement typé, vous pouvez donc également créer vos propres types de modèle pour les éléments et avoir des modèles pour ceux-ci.

Donc, pour la question OP:

public class ChildModelCollection : Collection<ChildModel> { }

Fera un modèle fortement typé qui est une collection qui peut être résolue en un modèle comme les autres.

8
Luke Puplett

La "réponse valide" réelle est -IMHO- ne répond pas correctement à la question. Je pense que l'OP cherche un moyen d'avoir un modèle de liste qui se déclenche sans spécifier l'UIHint.

Les trucs magiques font presque l'affaire

Un peu de magie charge la vue correcte pour n type spécifié.
Quelques charges magiques supplémentaires la même vue pour ne collection de un type spécifié.
Il devrait être un peu de magie qui itère la même vue pour une collection d'un type spécifié.

Changer le comportement réel?

Ouvrez votre démonteur préféré. La magie se produit dans System.Web.Mvc.Html.TemplateHelpers.ExecuteTemplate. Comme vous pouvez le voir, il n'y a aucun point d'extensibilité pour changer le comportement. Peut-être qu'une demande de pull à MVC peut aider ...

Allez avec la magie réelle

J'ai trouvé quelque chose qui fonctionne. Créez un modèle d'affichage ~/Views/Shared/DisplayTemplates/MyModel.cshtml.

Déclarez le modèle comme type object.

Si l'objet est une collection, itérez et restituez le modèle. Si ce n'est pas une collection, montrez l'objet.

@model object

@if (Model is IList<MyModel>)
{
    var models = (IList<MyModel>)Model;
<ul>
    @foreach (var item in models)
    {
@Html.Partial("DisplayTemplates/MyModel", item)
    }
</ul>
} else {
    var item = (MyModel)Model;
    <li>@item.Name</li>
    }
}

Maintenant, DisplayFor fonctionne sans UIHint.

2
SandRock