EditorFor IEnumerable <T> с TemplateName
Предположим, что у меня есть простая модель для объяснения цели:
public class Category
{
...
public IEnumerable<Product> Products { get; set; }
}
Вид:
@model Category
...
<ul>
@Html.EditorFor(m => m.Products)
</ul>
EditorTemplate:
@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>
который я могу отправить на мой контроллер в конце концов.
Но почему MVC не делает трюк, когда я определяю EditorTemplate с именем шаблона?
@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")
}
который кажется грязным обходным путем для меня. Это другое, более чистое решение для этого?
Ответы
Ответ 1
Это другое, более чистое решение для этого?
Простой ответ - нет, он плохо сосать, я полностью согласен с вами, но то, как разработчики платформы решили реализовать эту функцию.
Так что я делаю, я придерживаюсь условностей. Поскольку у меня есть определенные модели представлений для каждого представления и частичных данных, не имеет большого значения иметь соответствующий шаблон редактора, названный так же, как и тип коллекции.
Ответ 2
Там, теперь я только должен Darin 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());
}