Forked from ChesterCampbellAM/ANestedContentUsePattern.txt
Created
June 19, 2018 21:28
-
-
Save dillorscroft/bf04b12954eb3b9c87c66caa63fc5c75 to your computer and use it in GitHub Desktop.
Pattern for binding Nested Content content to strongly-typed models (via Models Builder) in Umbraco CMS v7.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This pattern has a couple of assumptions built in: | |
1) All nested content partial views will be saved in the directory: /Views/Partials/NestedContent | |
2) All nested content partials views will be named using the alias of the Document Type the | |
nested content is based on, but starting with an underscore. For example: Document Type | |
"Full Width Text Component" (alias = fullWidthTextComponent) should have a partial view | |
named "_FullWidthTextComponent.cshtml". | |
3) All Models Builder generated data models are namespaced to "AM.Models". | |
Here's how this works: | |
Take a look at Home.cshtml. Here we have a template view that is bound to the HomePageModel | |
data model. That model has a property named ContentComponents which is an | |
IEnumerable<IPublishedContent> of content (created with a Nested Content Data Type). | |
To render model.ContentComponents we call an HTML helper and pass it the property: | |
@Html.RenderNestedContentPartials(Model.ContentComponents) | |
HtmlHelperExtensions.cs is where we define the RenderNestedContentPartials() helpers. | |
Here we attempt to find a data model class for each of the Nested Content items. If we're | |
successful we pass the item's Document Type alias, the data model object, and an optional | |
ViewDataDictionary to a Partial renderer helper and return what we get back. If we're not | |
successful in mapping the item to a data model we pass the item as an IPublishedContent | |
object to the Partial helper. | |
TypeHelper.cs is a helper that will take an IPublishedContent item and attempt to map | |
it to a data model class in the AM.Models namespace (of course, this namespace would be | |
whatever is appropriate to your project). The helper is a singleton that contains a | |
dictionary of found types (so we don't have to do the lookup and instantiation on every | |
request). If we find a match then it instantiates an instance of the data model and | |
returns it. | |
Finally, NestedContentViewEngine.cs registers the location of our partial view files to | |
the RazorViewEngine. Of course, you could change this to be a different location or | |
omit it entirely and put your cshtml files in the /Views/Partials directory (where | |
Umbraco expects them to be). The reason for modifying the Razor View Engine rather than | |
just hardcoding the partial path into the RenderNestedContentPartials() helper is | |
to support *.Mobile.cshtml switching. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@inherits Umbraco.Web.Mvc.UmbracoViewPage<HomePageModel> | |
@using AM.Helpers.Extensions | |
@using AM.Models | |
@{ | |
Layout = "Master.cshtml"; | |
} | |
<div class='container components'> | |
<div class='row'> | |
<div class='col-md-24'> | |
@Html.RenderNestedContentPartials(Model.ContentComponents) | |
</div> | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Text; | |
using System.Web; | |
using System.Web.Mvc; | |
using System.Web.Mvc.Html; | |
using Umbraco.Core.Logging; | |
using Umbraco.Core.Models; | |
namespace AM.Helpers.Extensions | |
{ | |
public static class HtmlHelperExtensions | |
{ | |
#region Nested Content Extensions | |
/// <summary> | |
/// MVC Html helper extension for rendering a single Nested Content object (type is IPublishedContent). Use viewDataDictionary to pass in additional data to the view. | |
/// </summary> | |
/// <param name="htmlHelper"></param> | |
/// <param name="contentModel"></param> | |
/// <param name="viewDataDictionary"></param> | |
/// <returns></returns> | |
public static IHtmlString RenderNestedContentPartials(this HtmlHelper htmlHelper, IPublishedContent contentModel, ViewDataDictionary viewDataDictionary = null) | |
{ | |
return htmlHelper.RenderNCPartials(new IPublishedContent[] { contentModel }, viewDataDictionary); | |
} | |
/// <summary> | |
/// MVC Html helper extension for rendering a list of Nested Content objects (type is IPublishedContent). Use viewDataDictionary to pass in additional data to the view. | |
/// </summary> | |
/// <param name="htmlHelper"></param> | |
/// <param name="contentModels"></param> | |
/// <param name="viewDataDictionary"></param> | |
/// <returns></returns> | |
public static IHtmlString RenderNestedContentPartials(this HtmlHelper htmlHelper, IEnumerable<IPublishedContent> contentModels, ViewDataDictionary viewDataDictionary = null) | |
{ | |
return htmlHelper.RenderNCPartials(contentModels, viewDataDictionary); | |
} | |
private static IHtmlString RenderNCPartials(this HtmlHelper htmlHelper, IEnumerable<IPublishedContent> contentModels, ViewDataDictionary viewDataDictionary) | |
{ | |
if (contentModels == null || HttpContext.Current == null) | |
{ | |
return new HtmlString(""); | |
} | |
StringBuilder result = new StringBuilder(); | |
foreach (IPublishedContent content in contentModels) | |
{ | |
TypeHelper tHelper = new TypeHelper(); | |
object typedContent = tHelper.GetGeneratedContentModel(content); | |
if (typedContent != null) | |
{ | |
result.Append(htmlHelper.Partial(content.DocumentTypeAlias, typedContent, viewDataDictionary).ToString()); | |
} | |
else | |
{ | |
result.Append(htmlHelper.Partial(content.DocumentTypeAlias, content, viewDataDictionary).ToString()); | |
} | |
} | |
return new HtmlString(result.ToString()); | |
} | |
#endregion | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
using System.Web.Mvc; | |
using Umbraco.Core; | |
namespace AM | |
{ | |
public class NestedContentViewEngine : RazorViewEngine | |
{ | |
private static string[] NewPartialViewFormats = new[] { | |
"~/Views/Partials/NestedContent/_{0}.cshtml", | |
"~/Views/Partials/NestedContent/_{0}.vbhtml" | |
}; | |
public NestedContentViewEngine() | |
{ | |
base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(NewPartialViewFormats).ToArray(); | |
} | |
} | |
public class RegisterNestedContentViewEngine : ApplicationEventHandler | |
{ | |
protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) | |
{ | |
ViewEngines.Engines.Add(new NestedContentViewEngine()); | |
base.ApplicationStarting(umbracoApplication, applicationContext); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using Umbraco.Core.Models; | |
namespace AM.Helpers | |
{ | |
public sealed class TypeHelper | |
{ | |
private static volatile TypeHelper _instance; | |
private static object _lock = new object(); | |
private Dictionary<string, Type> _mappedTypes = new Dictionary<string, Type>(); | |
public TypeHelper() { } | |
public static TypeHelper Instance | |
{ | |
get | |
{ | |
if (_instance == null) | |
{ | |
lock (_lock) | |
{ | |
if (_instance == null) | |
{ | |
_instance = new TypeHelper(); | |
} | |
} | |
} | |
return _instance; | |
} | |
} | |
public object GetGeneratedContentModel(IPublishedContent content) | |
{ | |
if (content != null) | |
{ | |
if (this._mappedTypes.Keys.Contains(content.DocumentTypeAlias)) | |
{ | |
return Activator.CreateInstance(this._mappedTypes.Where(t => t.Key == content.DocumentTypeAlias).FirstOrDefault().Value, new object[] { content }); | |
} | |
else | |
{ | |
foreach (Type item in GetTypesInNamespace(Assembly.GetExecutingAssembly(), "AM.Models")) | |
{ | |
FieldInfo field = item.GetField("ModelTypeAlias"); | |
if (field != null) | |
{ | |
if (content.DocumentTypeAlias == field.GetValue(null).ToString()) | |
{ | |
this._mappedTypes.Add(content.DocumentTypeAlias, item); | |
return Activator.CreateInstance(item, new object[] { content }); | |
} | |
} | |
} | |
} | |
} | |
return null; | |
} | |
#region Private | |
private IEnumerable<Type> GetTypesInNamespace(Assembly assembly, string nameSpace) | |
{ | |
return assembly.GetTypes().Where(t => String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal)); | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment