I'm trying to make a custom template for a basket item list. I need a few different templates, as I have different ways of displaying the item, depending on if it's on the webpage or in a mail. Now my problem is, that when I use the default name it works flawlessly.
@Html.DisplayFor(b => b.Items)
But when I try to add a template name, I get an expection that my templates needs to be of a list type IEnumerable and not BasketItem.
@Html.DisplayFor(i => basket.Items, "CustomerItemBaseList")
Any ideas where my mistake is, or why it's not possible are appreciated. Thanks.
Unfortunately that's a limitation of templated helpers. If you specify a template name for a collection property the template no longer applies automatically for each item of the collection. Possible workaround:
@for (int i = 0; i < Model.Items.Length; i++)
{
@Html.DisplayFor(x => x.Items[i], "CustomerItemBaseList")
}
That's a good idea, Darin. I'm lazy though, so I'd like to take it one step further and make an actual helper that wraps this. I also took out the lambda expression to simplify it for my case, but you can easily add that functionality back in.
public static class DisplayTextListExtension
{
public static MvcHtmlString DisplayForList<TModel>(this HtmlHelper<TModel> html, IEnumerable<string> model, string templateName)
{
var tempResult = new StringBuilder();
foreach (var item in model)
{
tempResult.Append(html.DisplayFor(m => item, templateName));
}
return MvcHtmlString.Create(tempResult.ToString());
}
}
Then the actual usage looks like:
@Html.DisplayForList(Model.Organizations, "infoBtn")
I liked Dan's answer but just adjusted slightly as it can work for any IEnumerable:
using System.Collections;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace YourProject.Whatever
{
public static class DisplayExtensions
{
public static MvcHtmlString DisplayForIEnumerable<TModel>(this HtmlHelper<TModel> html, IEnumerable model, string templateName)
{
var tempResult = new StringBuilder();
foreach (var item in model)
{
var item1 = item;
tempResult.Append(html.DisplayFor(m => item1, templateName));
}
return MvcHtmlString.Create(tempResult.ToString());
}
}
}
And of course:
@Html.DisplayForIEnumerable(Model.Organizations, "NameOfYourDisplayTemplate")
I suggest another solution useful even more with lists of heterogeneous objects (i.e. BasketItem subclasses), using the additionalViewData parameter of the DisplayFor
method, like:
@DisplayFor(b=>b.Items, new { layout="row" })
in this way the helper works fine with IEnumerable<T>
, calling for each item (subclass of T) the relative DisplayTemplate, passing it the additionalViewData values in the ViewData
dictionary.
The template could so output different code for different layout values.
In the example above the template named View\Shared\DisplayTemplates\BasketItem (or the name of the subclass) should be like this:
@model MyProject.BasketItem // or BasketItem subclass
@{
string layout = ViewData["layout"] as string ?? "default";
switch(layout)
{
case "row":
<div class="row">
...
</div>
break;
// other layouts
...
default: // strongly recommended a default case
<div class="default-view>
...
</div>
break;
}
}
It is strongly recommended to provide always a default code.
I hope this suggestion could help.