Martin Hansen Lennox February 2016

Can I create a HtmlHelper specifically for IEnumerable properties?

I would like to create a HtmlHelper that can be used on IEnumerable properties.

The aim is to use it like this:

@Html.DisplayForEnumerable(m => m.EnumerableItemsProperty, "ViewTemplateName");

If possible I would like to use the m => m.Items lambda syntax (as opposed to passing through Model.Items).

This is my best effort so far. But I'm not sure how to get the items variable from the expression parameter.

I suspect I may have to use something like IEnumerable<TValue> as the return type of the expression, but I'm quite new to generics and I've no idea how to implement this.

public static MvcHtmlString DisplayForEnumerable<TModel>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable>> expression, string templateName, object additonalViewData = null)
{
    var sb = new StringBuilder();

    // how to get items variable?

    foreach (var item in items)
    {
        var item1 = item;
        sb.Append(html.DisplayFor(m => item1, templateName, additonalViewData));
    }

    return MvcHtmlString.Create(sb.ToString());
}

Update

To clarify - I am taking this approach because I would like to be able so specify differnt templates for the same model. And the normal DisplayFor() enumeration does not occur if you specify a particular template.

I know I could just enumerate through manually, but I'd rather use this method unless someone more knowledgable advises otherwise.

Answers


Stephen Muecke February 2016

You helper will need to be

public static MvcHtmlString DisplayForEnumerable<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string templateName, object additionalViewData = null)
{
    ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
    IEnumerable collection = metaData.Model as IEnumerable;
    if (collection == null)
    {
        return helper.DisplayFor(expression, templateName, additionalViewData );
    }
    StringBuilder html = new StringBuilder();
    foreach (var item in collection)
    {
        html.Append(helper.DisplayFor(m => item, templateName, additionalViewData).ToString());
    }
    return MvcHtmlString.Create(html.ToString());
}

Note the code allows you to pass either a single T or IEnumerable<T> (although the method name now does not really make sense). If you wanted to limit it to only IEnumerable<T> you could throw an InvalidCastException if collection == null

Note that this approach will not work if you wanted to generate form controls for a collection (for example a EditorForEnumerable() method) because the required collection indexers will not be added to the generate name attributes. A better approach is to use the built-in DisplayFor() and EditorFor() methods which will generate the correct html for both T and IEnemerable<T>

Assuming you have a Person.cs class, create a partial view in /Views/Shared/DisplayTemplates/Person.cshtml (note the name of the file must match the name of the class) and in the view simply use

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

You can also create specific display and editor templates for each controller, for exam

Post Status

Asked in February 2016
Viewed 1,069 times
Voted 11
Answered 1 times

Search




Leave an answer