/// overload using a template prefix. Used to display for the value type using a custom prefix so one object type can have different templates. Will fall back to base types. /// /// /// Works similar to regular /// where the template type is searched recursively using . If no template can be found, it will fallback to the default System.Web.Mvc.Html.DisplayExtensions.DisplayFor method /// using no template at all. /// /// The model type /// The value type /// The html helper /// The value expression /// The template prefix, ex using "MyStringList" for string would, using default , look for the display template named MyStringList.string.cshtml /// How to format the view template name, format arguments: {0} - , {1} - model type name. / can also be used to make the prefix be a directory /// If set, will be used if no display template can be found, otherwise DisplayFor without template will be called as fallback /// If true, the display template search prefers interfaces before base classes, otherwise all base classes will be checked before any interface. Also, only interfaces defined directly on the current type being searched is considered each iteration /// The rendered html public static MvcHtmlString DisplayForWithPrefix(this HtmlHelper html, Expression> expression, string templatePrefix, string viewFormat = DEFAULT_PREFIX_VIEW_FORMAT, string fallbackTemplate = null, bool preferInterfaceBeforeBaseClass = false) { var metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData); var modelType = metaData.Model.GetType(); // this is the most derived type of the model Func renderer = templateName => System.Web.Mvc.Html.DisplayExtensions.DisplayFor(html, expression, templateName); // first, check all concrete implementations while(modelType != null) { var viewName = string.Format(viewFormat, templatePrefix, modelType.Name); if(DisplayTemplateExist(html.ViewContext, viewName)) { return renderer(viewName); } if(preferInterfaceBeforeBaseClass) { // check interfaces defined on the model type directly var exceptions = modelType.BaseType != null ? modelType.BaseType.GetInterfaces() : Enumerable.Empty(); foreach(var @interface in metaData.Model.GetType().GetInterfaces().Except(exceptions)) { viewName = string.Format(viewFormat, templatePrefix, @interface.Name); if(DisplayTemplateExist(html.ViewContext, viewName)) { return renderer(viewName); } } } modelType = modelType.BaseType; } // check all interfaces, if not already checked per implementation if(!preferInterfaceBeforeBaseClass) { foreach(var @interface in metaData.Model.GetType().GetInterfaces()) { var viewName = string.Format(viewFormat, templatePrefix, @interface.Name); if(DisplayTemplateExist(html.ViewContext, viewName)) { return renderer(viewName); } } } // fallback return renderer(fallbackTemplate); } private static bool DisplayTemplateExist(ViewContext context, params string[] templateNames) { foreach(var templateName in templateNames) { // the FindPartialView will not automatically search the displaytemplates folder, but the DisplayFor method will var viewResult = ViewEngines.Engines.FindPartialView(context, "DisplayTemplates/" + templateName); return viewResult.View != null; } return false; }}The html helper is quite simple, it works like DisplayFor, infact it uses DisplayFor to do t">
When working with MVC and display templates I have time and again experienced a shortcoming of the DisplayFor method of rendering objects, or maybe I shouldn't call it a shortcoming, lets call it a design decision for simplicity: The inability to easily define and use multiple display templates for one model type.I work with a CMS, lets call it EPiServer, where a common scenario is that I have a number of different page models, all inheriting a common ancestor. The most basic properties of a page would be a name and its url, a more advanced page could be an article where there is also a image and a preamble. These pages are mixed in the tree structure of my site, and are used in a number of different ways, like in the support navigation at the top of the page, a page listing, or perhaps a sidebar teaser list.Above I have already defined three different display templates I want to use when showing information about my pages to the visitor while not visiting the acutal page in where the page controller index method will be used. But MVC does only support one, with a possible local overload in the controller folder: /Views/Shared/DisplayTemplates/MyPageTypeClassName.cshtml.First of allLets recap some ground rules for the display template system used in MVC. Assume you have a data model:public class MyItem { public string Name { get; set; } public string Image { get; set; } public string Preamble { get; set; } public string Url { get; set; }}And in a view model of some sort:public class MyViewModel { public MyItem Teaser { get; set; }}Now to render this in a view, you would call:@model MyViewModel@Html.DisplayFor(m => m.Teaser)This would just simply render out each individual property from the MyItem object. To enhance it, you would do something like so:@Html.DisplayFor(m => m.Teaser, "MyTeaserView")And the view /Views/Shared/DisplayTemplates/MyTeaserView.cshtml would be used.There is also another possibility to just add a display template with the same name as the class to the /views/shared/displaytemplates/ folder and that will be used if I call:@Html.DisplayFor(m => m.Teaser)Another option is to use a UIHint:public class MyViewModel { [UIHint("MyDisplayTemplate")] public MyItem Teaser { get; set; }}and when calling DisplayFor using @Html.DisplayFor(m => m.Teaser), the display template /views/shared/displaytemplates/MyDisplayTemplate.cshtml would be used.Another powerful convention using DisplayFor is when you use it on objects with the same ancestry or interfaces, consider the following items:public abstract class ItemBase { public string Name { get; set; } public string Url { get; set; }}public class MyItem : ItemBase { public string Image { get; set; } public string Preamble { get; set; }}public class MySimpleItem : ItemBase {}public class MyItemListViewModel { IEnumerable<itembase> Items { get; set; }}Using conventions, I can add some display templates to tweak the display of my items: MyItem.cshtml:@model MyItem@Html.DisplayFor(m => m.Name)@Html.DisplayFor(m => m.Image)@Html.DisplayFor(m => m.Preamble)@Html.DisplayFor(m => m.Url)MySimpleItem.cshtml:@model MySimpleItem@Html.DisplayFor(m => m.Name)@Html.DisplayFor(m => m.Url)I can even add a template for the base class, so if I later add another implementation of ItemBase, it will display like the base item.ItemBase.cshtml:@model ItemBase@Html.DisplayFor(m => m.Name)@Html.DisplayFor(m => m.Url)The problemRemember when I talked about my pages in the beginning of this post, where I commonly had different page types that need to be rendered differently in the same list?Now, combine the display templates of MVC and my pages, and I could create a page list using a foreach loop and Html.DisplayFor(m => page). For each page I create a display template and the page will be rendered in its own unique way in my page listing depending on what properties I have available, like so:@foreach (var page in Model.PagesToList) { @Html.DisplayFor(m => page)}Next I want to list other pages as teasers in the sidebar, using different html for each type ofcourse:@foreach (var page in Model.SideBarTeasers) { @Html.DisplayFor(m => page)}But I can't! Because the display template for each page is already occupied by my page listing, what do I do?There are a number of solutions:I can create a whole new controller for just the side bar listing, as controllers can have their own display templates. I can create a new view model for each page type, combine them with a new base type, and use those in my listing instead, even though there will be no new data so I am effectively duplicating my models just to gain access to a new display template. I can use a specific display template, using @Html.DisplayFor(m => item, "MySideBarPageModel") , but this would limit me to just one view for all different page types, which would fail my requirement of having different html per model. I could use something like item.GetType().Name as template argument, but that could easily break if I later add another page type to the list as there is no fallback to base. There are probably more clever ways to achieve this, but all ways I tried have revolving around duplicated code (view model bonanza!), or just been a case of killing ants with cannons to stay within the conventions of MVC and DisplayFor.The solutionCue the DisplayForWithPrefix html helper:public static class DisplayExtensions { public const string DEFAULT_PREFIX_VIEW_FORMAT = "{0}.{1}"; public const string DIRECTORY_PREFIX_VIEW_FORMAT = "{0}/{1}"; /// <summary> /// <see cref="System.Web.Mvc.Html.DisplayExtensions.DisplayFor{TModel,TValue}(System.Web.Mvc.HtmlHelper{TModel},System.Linq.Expressions.Expression{System.Func{TModel,TValue}})"/> /// overload using a template prefix. Used to display for the value type using a custom prefix so one object type can have different templates. Will fall back to base types. /// </summary> /// <remarks> /// Works similar to regular <see cref="System.Web.Mvc.Html.DisplayExtensions.DisplayFor{TModel,TValue}(System.Web.Mvc.HtmlHelper{TModel},System.Linq.Expressions.Expression{System.Func{TModel,TValue}})"/> /// where the template type is searched recursively using <see cref="Type.BaseType"/>. If no template can be found, it will fallback to the default System.Web.Mvc.Html.DisplayExtensions.DisplayFor method /// using no template at all. /// </remarks> /// <typeparam name="TModel">The model type</typeparam> /// <typeparam name="TValue">The value type</typeparam> /// <param name="html">The html helper</param> /// <param name="expression">The value expression</param> /// <param name="templatePrefix">The template prefix, ex using "MyStringList" for <typeparamref name="TValue"/> string would, using default <paramref name="viewFormat" />, look for the display template named MyStringList.string.cshtml</param> /// <param name="viewFormat">How to format the view template name, format arguments: {0} - <paramref name="templatePrefix"/>, {1} - model type name. / can also be used to make the prefix be a directory</param> /// <param name="fallbackTemplate">If set, will be used if no display template can be found, otherwise DisplayFor without template will be called as fallback</param> /// <param name="preferInterfaceBeforeBaseClass">If true, the display template search prefers interfaces before base classes, otherwise all base classes will be checked before any interface. Also, only interfaces defined directly on the current type being searched is considered each <see cref="Type.BaseType"/> iteration</param> /// <returns>The rendered html</returns> public static MvcHtmlString DisplayForWithPrefix<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templatePrefix, string viewFormat = DEFAULT_PREFIX_VIEW_FORMAT, string fallbackTemplate = null, bool preferInterfaceBeforeBaseClass = false) { var metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData); var modelType = metaData.Model.GetType(); // this is the most derived type of the model Func<string, MvcHtmlString> renderer = templateName => System.Web.Mvc.Html.DisplayExtensions.DisplayFor(html, expression, templateName); // first, check all concrete implementations while(modelType != null) { var viewName = string.Format(viewFormat, templatePrefix, modelType.Name); if(DisplayTemplateExist(html.ViewContext, viewName)) { return renderer(viewName); } if(preferInterfaceBeforeBaseClass) { // check interfaces defined on the model type directly var exceptions = modelType.BaseType != null ? modelType.BaseType.GetInterfaces() : Enumerable.Empty<Type>(); foreach(var @interface in metaData.Model.GetType().GetInterfaces().Except(exceptions)) { viewName = string.Format(viewFormat, templatePrefix, @interface.Name); if(DisplayTemplateExist(html.ViewContext, viewName)) { return renderer(viewName); } } } modelType = modelType.BaseType; } // check all interfaces, if not already checked per implementation if(!preferInterfaceBeforeBaseClass) { foreach(var @interface in metaData.Model.GetType().GetInterfaces()) { var viewName = string.Format(viewFormat, templatePrefix, @interface.Name); if(DisplayTemplateExist(html.ViewContext, viewName)) { return renderer(viewName); } } } // fallback return renderer(fallbackTemplate); } private static bool DisplayTemplateExist(ViewContext context, params string[] templateNames) { foreach(var templateName in templateNames) { // the FindPartialView will not automatically search the displaytemplates folder, but the DisplayFor method will var viewResult = ViewEngines.Engines.FindPartialView(context, "DisplayTemplates/" + templateName); return viewResult.View != null; } return false; }}The html helper is quite simple, it works like DisplayFor, infact it uses DisplayFor to do t