Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EditorFor IEnumerable<T> with TemplateName

Suppose I have a simple model to explain the purpose:

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

View:

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

EditorTemplate:

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

Note that I don't have to define the EditorTemplate for IEnumerable<Product>, I can only create it for the Product model and MVC framework is smart enough to use its own template for IEnumerable. It iterates through my collection and calls my EditorTemplate.

The output html will be something like this

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

which I can post to my controller after all.

But why doesn't the MVC do the trick when I define EditorTemplate with a template name?

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

In that case I have to change the type of the property to IList<Product>, iterate through the collection by myself and call the EditorTemplate

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

which seems kind of dirty workaround to me. Is it any other, cleaner solution to do this?

like image 891
Zabavsky Avatar asked Dec 26 '12 09:12

Zabavsky


People also ask

How do I add an EditorFor style?

EditorFor does not allow for styling as there are no parameters for additional attributes. The reason for this is because the EditorFor doesn't always generate a single element as it can be overridden. To style a specific type of element you need to use the specific editor you want to use.

What is the use of EditorFor?

EditorFor: This control is bit smart. It renders HTML markup based on the datatype of the property. E.g. suppose there is a boolean property in model. To render this property in the view as a checkbox either we can use CheckBoxFor or EditorFor.

What is EditorFor?

EditorFor<TModel,TValue>(HtmlHelper<TModel>, Expression<Func<TModel,TValue>>, String, String, Object) Returns an HTML input element for each property in the object that is represented by the expression, using the specified template, HTML field name, and additional view data.

What is MVC editor template?

An EditorTemplate is a Razor file placed in the EditorTemplates folder: For Razor Pages apps, in the Pages/Shared/EditorTemplates folder. For MVC apps, in the Views/Shared/EditorTemplates folder or the Views/ControllerName/EditorTemplates folder.


2 Answers

There, now I only owe Darin 9999 beers.

    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());
    }
like image 143
DaveMorganTexas Avatar answered Oct 19 '22 10:10

DaveMorganTexas


Is it any other, cleaner solution to do this?

The simple answer is no, it sucks badly, I completely agree with you, but that's how the designers of the framework decided to implement this feature.

So what I do is I stick to the conventions. Since I have specific view models for each views and partials it's not a big deal to have a corresponding editor template, named the same way as the type of the collection.

like image 22
Darin Dimitrov Avatar answered Oct 19 '22 10:10

Darin Dimitrov