【问题标题】:ASP.net MVC - Display Template for a collectionASP.net MVC - 集合的显示模板
【发布时间】:2011-12-21 13:41:49
【问题描述】:

我在 MVC 中有以下模型:

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

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

当我想显示父模型的所有子模型时,我可以这样做:

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

然后我可以创建一个 ChildModel.cshtml 显示模板,DisplayFor 将自动遍历列表。

如果我想为 IEnumerable 创建自定义模板怎么办?

@model IEnumerable<ChildModel>

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

如何创建模型类型为 IEnumerable&lt;ChildModel&gt; 的显示模板,然后调用 @Html.DisplayFor(m =&gt; m.Children) 而不会抱怨模型类型错误?

【问题讨论】:

    标签: asp.net-mvc asp.net-mvc-3 display-templates


    【解决方案1】:

    实际的“有效答案”是-恕我直言-没有正确回答问题。我认为 OP 正在寻找一种无需指定 UIHint 即可触发的列表模板的方法。

    神奇的东西几乎可以完成这项工作

    一些魔法会为指定类型加载正确的视图。
    更多的魔法会为一个集合指定类型加载相同的视图
    应该有一些魔法可以为指定类型的集合迭代相同的视图。

    改变实际行为?

    打开你最喜欢的反汇编程序。魔法发生在System.Web.Mvc.Html.TemplateHelpers.ExecuteTemplate。如您所见,没有可扩展点来更改行为。也许对 MVC 的拉取请求可以帮助...

    发挥真正的魔力

    我想出了一些可行的方法。创建显示模板~/Views/Shared/DisplayTemplates/MyModel.cshtml

    将模型声明为类型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>
        }
    }
    

    现在DisplayFor 可以在没有UIHint 的情况下工作。

    【讨论】:

    • 您可以在 foreach 中移动 &lt;li&gt; 元素标签,使其与 ul 配对。
    【解决方案2】:

    在关于不从视图中获取输出的问题中,我实际上有一个示例,说明如何使用一组子模型对模型进行模板化并让它们全部呈现。

    ASP.NET Display Templates - No output

    本质上,您需要创建一个子类List&lt;T&gt;Collection&lt;T&gt; 的模型并使用它:

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

    在集合模型的模板中迭代和呈现子项。每个孩子都需要强类型,因此您可能还想为这些项目创建自己的模型类型,并为这些项目创建模板。

    所以对于 OP 问题:

    public class ChildModelCollection : Collection<ChildModel> { }
    

    将创建一个强类型模型,它是一个可以像其他任何模板一样解析为模板的集合。

    【讨论】:

    • +1:为列表制作模板意味着您的项目正在成长,那么您应该强烈键入所有集合。对我来说,这就是答案。 [UIHint] 不是类型安全的,只需搜索最合适的模板即可。
    【解决方案3】:

    这是对马斯洛评论的回复。这是我对 SO 的第一次贡献,因此我没有足够的声誉来发表评论 - 因此将回复作为答案。

    您可以在 ModelMetadataProvider 中设置“TemplateHint”属性。这会将任何 IEnumerable 自动连接到您指定的模板。我刚刚在我的项目中尝试过。下面的代码 -

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

    您基本上覆盖了“CachedDataAnnotationsModelMetadataProvider”的“CreateMetadataFromPrototype”方法,并将您的派生类型注册为首选的 ModelMetadataProvider。

    在您的模板中,您无法直接访问集合中元素的 ModelMetadata。我使用以下代码访问集合中元素的 ModelMetadata -

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

    在我看来,我只需调用 @Html.DisplayForModel() 并加载模板。无需在模型上指定“UIHint”。

    我希望这有点价值。

    【讨论】:

    • 我相信你的type.IsAssignableFrom(typeof(IEnumerable&lt;object&gt;))是倒退的,应该是typeof(IEnumerable&lt;object&gt;).IsAssignableFrom(type)
    • 这不是所选答案是否有原因?这比在任何地方创建自定义列表类或在每个可枚举属性上都需要 UiHint 更好。
    【解决方案4】:

    像这样:

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

    或者像这样:

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

    显然你会有~/Views/Shared/DisplayTemplates/YourTemplateName.cshtml:

    @model IEnumerable<ChildModel>
    
    <table>
        <tr>
            <th>Property 1</th>
            <th>Property 2</th>
        </tr>
        ...
    </table>
    

    【讨论】:

    • 真的没有办法声明一个模板会自动连接为任何 IEnumerable 的默认值吗?
    • 我不相信这个答案是 100% 正确的.. 1) 如果 DisplayFor 用模板名称调用,它不会自动迭代集合; 2) 在显示模板中指定@model ICollection 将不允许在不从集合中检索项目的情况下访问 ChildModel 属性,即需要使用 foreach
    • @Jack0fshad0ws,你是绝对正确的。这就是为什么在我的回答中我在模板中使用@model IEnumerable&lt;ChildModel&gt; 的原因。因为当你指定模板名称时,传递给模板的模型就是集合属性。
    • 我是否正确理解没有办法(a)避免 foreach 和(b)指定模板的名称?在上面的示例中,我仍然需要 @foreach 遍历表格行。
    • 更正...您将 IEnumerable 的集合传递给您的模板,但您的模板的模型将是单数的。 MVC 为集合中的每个项目重新创建一次模板。
    猜你喜欢
    • 2011-07-14
    • 1970-01-01
    • 2011-05-30
    • 2011-07-26
    • 2011-06-08
    • 2011-06-29
    • 1970-01-01
    • 2012-08-14
    • 1970-01-01
    相关资源
    最近更新 更多