ASP.net MVC - Display Template for a collection

2019-01-10 22:23发布

I have the following model in MVC:

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

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

When I want to display all of the children for the parent model I can do:

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

I can then create a ChildModel.cshtml display template and the DisplayFor will automatically iterate over the list.

What if I want to create a custom template for IEnumerable?

@model IEnumerable<ChildModel>

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

How can I create a Display Template that has a model type of IEnumerable<ChildModel> and then call @Html.DisplayFor(m => m.Children) without it complaining about the model type being wrong?

4条回答
男人必须洒脱
2楼-- · 2019-01-10 22:47

In my question about not getting output from views, I actually have an example of how to template a model with a collection of child models and have them all render.

ASP.NET Display Templates - No output

Essentially, you need to create a model that subclasses List<T> or Collection<T> and use this:

@model ChildModelCollection 

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

In your template for the collection model to iterate and render the children. Each child needs to strongly-typed, so you may want to create your own model types for the items, too, and have templates for those.

So for the OP question:

public class ChildModelCollection : Collection<ChildModel> { }

Will make a strongly-typed model that's a collection that can be resolved to a template like any other.

查看更多
欢心
3楼-- · 2019-01-10 22:58

This is in reply to Maslow's comment. This is my first ever contribution to SO, so I don't have enough reputation to comment - hence the reply as an answer.

You can set the 'TemplateHint' property in the ModelMetadataProvider. This would auto hookup any IEnumerable to a template you specify. I just tried it in my project. Code below -

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

You basically override the 'CreateMetadataFromPrototype' method of the 'CachedDataAnnotationsModelMetadataProvider' and register your derived type as the preferred ModelMetadataProvider.

In your template, you cannot directly access the ModelMetadata of the elements in your collection. I used the following code to access the ModelMetadata for the elements in my 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 });
}

In my view, I simply call @Html.DisplayForModel() and the template gets loaded. There is no need to specify 'UIHint' on models.

I hope this was of some value.

查看更多
叛逆
4楼-- · 2019-01-10 23:03

Like this:

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

or like this:

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

where obviously you would have ~/Views/Shared/DisplayTemplates/YourTemplateName.cshtml:

@model IEnumerable<ChildModel>

<table>
    <tr>
        <th>Property 1</th>
        <th>Property 2</th>
    </tr>
    ...
</table>
查看更多
Anthone
5楼-- · 2019-01-10 23:08

The actual "valid answer" is -IMHO- not correctly answering the question. I think the OP is searching for a way to have a list template that triggers without specifying the UIHint.

Magic stuff almost does the job

Some magic loads the correct view for a specified type.
Some more magic loads the same view for a collection of a specified type.
There should be some magic that iterates the same view for a collection of a specified type.

Change the actual behavior?

Open your favorite disassembler. The magic occurs in System.Web.Mvc.Html.TemplateHelpers.ExecuteTemplate. As you can see, there are no extensibility points to change the behavior. Maybe a pull request to MVC can help...

Go with the actual magic

I came up with something that works. Create a display template ~/Views/Shared/DisplayTemplates/MyModel.cshtml.

Declare the model as type object.

If the object is a collection, iterate and render the template again. If it's not a collection, then show the object.

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

Now DisplayFor works without UIHint.

查看更多
登录 后发表回答