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.
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 example /Views/yourControllerName/DisplayTemplates/Person.cshtml
. This allows you to use one template in /Persons/Index
and another template in /Organisation/Details/1
which might display a list of Person
associated with an Organisation
And finally, if you do need 2 different templates for Person
in the same controller, you can use view models, for example class PersonVM
and class AssociatedPersonVM
and create an EditorTemplate
for each