21

假设我有一个简单的模型来解释目的:

public class Category
{
    ...
    public IEnumerable<Product> Products { get; set; }
}

看法:

@model Category
...
<ul>
    @Html.EditorFor(m => m.Products)
</ul>

编辑器模板:

@model Product
...
<li>
    @Html.EditorFor(m => m.Name)
</li>

请注意,我不必为 定义 EditorTemplate IEnumerable<Product>,我只能为Product模型创建它,并且 MVC 框架足够聪明,可以为 IEnumerable 使用它自己的模板。它遍历我的收藏并调用我的 EditorTemplate。

输出 html 将是这样的

...
<li>
    <input id="Products_i_Name" name="Products[i].Name" type="text" value="SomeName">
</li>

毕竟我可以发布到我的控制器。

但是,当我使用模板名称定义 EditorTemplate 时,为什么 MVC 不这样做呢?

@Html.EditorFor(m => m.Products, "ProductTemplate")

在这种情况下,我必须将属性的类型更改为IList<Product>,自己遍历集合并调用 EditorTemplate

@for (int i = 0; i < Model.Products.Count; i++)
{
    @Html.EditorFor(m => m.Products[i], "ProductTemplate")
}

这对我来说似乎是一种肮脏的解决方法。是否有其他更清洁的解决方案可以做到这一点?

4

3 回答 3

16

在那里,现在我只欠达林 9999 瓶啤酒。

    public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string templateName = null) where TModel : class
    {
        StringBuilder sb = new StringBuilder();

        // Get the items from ViewData
        var items = expression.Compile()(html.ViewData.Model);
        var fieldName = ExpressionHelper.GetExpressionText(expression);
        var htmlFieldPrefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
        var fullHtmlFieldPrefix = String.IsNullOrEmpty(htmlFieldPrefix) ? fieldName : String.Format("{0}.{1}", htmlFieldPrefix, fieldName);
        int index = 0;

        foreach (TValue item in items)
        {
            // Much gratitude to Matt Hidinger for getting the singleItemExpression.
            // Current html.DisplayFor() throws exception if the expression is anything that isn't a "MemberAccessExpression"
            // So we have to trick it and place the item into a dummy wrapper and access the item through a Property
            var dummy = new { Item = item };

            // Get the actual item by accessing the "Item" property from our dummy class
            var memberExpression = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));

            // Create a lambda expression passing the MemberExpression to access the "Item" property and the expression params
            var singleItemExpression = Expression.Lambda<Func<TModel, TValue>>(memberExpression,
                                                                               expression.Parameters);

            // Now when the form collection is submitted, the default model binder will be able to bind it exactly as it was.
            var itemFieldName = String.Format("{0}[{1}]", fullHtmlFieldPrefix, index++);
            string singleItemHtml = html.EditorFor(singleItemExpression, templateName, itemFieldName).ToString();
            sb.AppendFormat(singleItemHtml);
        }

        return new MvcHtmlString(sb.ToString());
    }
于 2013-09-24T06:56:28.730 回答
14

是否有其他更清洁的解决方案可以做到这一点?

简单的答案是否定的,它很糟糕,我完全同意你的看法,但这就是框架的设计者决定实现这个特性的方式。

所以我所做的就是遵守约定。由于我为每个视图和局部视图都有特定的视图模型,因此拥有一个相应的编辑器模板并不是什么大问题,它的命名方式与集合的类型相同。

于 2012-12-26T09:27:39.357 回答
0

我决定编写一个库来提供一种更简洁的方法来解决这个问题。使用它,您可以为列表的每个部分指定单独的模板,它适用于显示和编辑:

@Html.DisplayListFor(x => x.Books,
    itemTemplate: "BookViewModel",
    itemContainerTemplate: "ContainerForMyItem"
    listTemplate:  "MyList"
    listContainerTemplate: "ContainerForMyList")

请注意,自定义是可选的,您可以简单地编写

@Html.DisplayListFor(x => x.Books)

或者

@Html.ListEditorFor(x => x.Books)

它应该可以正常工作。

该库称为动态视图模型列表,可在 NuGet 上使用。

于 2020-05-13T06:55:30.050 回答