This one is going to be a long one, so my apologies in advance.
Here’s the scenario: You have a folder in Sitecore which contains a list of items to render. Some examples would be a header navigation folder, a footer, or any type of repeater data. (We’ll use a Navigation for our example.) One way to accomplish this is (and we’re using Glass here) using the .NET “is” statement to load in MVC partials. I’ve actually built this, a while ago:
@foreach (var navItem in Model.MegaMenuLinks) { if (navItem is ProductBrowserNavigation) { @Html.Partial("~/Views/Components/MegaMenu/ProductSelector.cshtml", navItem) } if (navItem is GeneralNavigation) { @Html.Partial("~/Views/Components/MegaMenu/GeneralContent.cshtml", navItem) } if (navItem is ProductCategoryNavigation) { @Html.Partial("~/Views/Components/MegaMenu/ProductCategory.cshtml", navItem) } }
That’s pretty forward. Iterate through all items and load in the partial. The drawback here is that if you add a new type of item in your navigation, you need to go touch code. That’s… not ideal!
One way to fix this is with using the Sitecore Renderings field. This feels a little bit like a hack, since we’re going to have to use the actual ID of the rendering, but it’s not all that bad.
After you define your rendering in Sitecore, you navigate to the item’s template and put the Rendering ID in the __Renderers field of the item like so:
Now our code can look like this:
@foreach (var navItem in Model.MegaMenuLinks) { @Html.Sitecore().Rendering(navItem["__Renderers"]) }
Did that really cut out 90% of the code and give us the flexibility to add new types of navigation items all in one? Yep.
But wait, there’s more! HTML cache is a huge benefit in Sitecore. Unfortunately, this doesn’t make use of it. You can cache the whole rendering, but what if it changes per page? That’s not good. We need to set some cache settings in there. The overload for Rendering actually takes an object, which is prime for an anonymous type. How about this now?
@foreach (var navItem in Model.MegaMenuLinks) { @Html.Sitecore().Rendering(navItem["__Renderers"], new{ Cachable = true, Cache_VaryByData = true }); }
That’s going to send in the cache settings to allow you to cache each rendering and vary it by associated content item. That’s also a step in the right direction.
But wait, there’s more! We really, really want to let the rendering’s cache settings dictate this. I hate hard coding these values. There’s gotta be a better way! Well, with some inspiration from a blog I read, I decided to make an extension method to actually help with this. Here’s the contents of that guy:
public static class DynamicRendering { public static HtmlString RenderWithCacheSettings(this SitecoreHelper helper, Item item) { var renderingItemIds = item["__Renderers"]; var output = new StringBuilder(); foreach (var renderingItemId in renderingItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { var renderingItem = Sitecore.Context.Database.GetItem(new ID(renderingItemId)); var rendering = new RenderingItem(renderingItem); var renderingCachingSettings = new RenderingCachingSettings(rendering); var renderedHtml = helper.Rendering(renderingItemId, new { DataSource = item.ID, renderingCachingSettings }); output.Append(renderedHtml); } return new HtmlString(output.ToString()); } public class RenderingCachingSettings { public RenderingCachingSettings(RenderingItem item) { Cacheable = item.Caching.Cacheable; Cache_VaryByData = item.Caching.VaryByData; Cache_VaryByDevice = item.Caching.VaryByDevice; Cache_VaryByLogin = item.Caching.VaryByLogin; Cache_VaryByParameters = item.Caching.VaryByParm; Cache_VaryByQueryString = item.Caching.VaryByQueryString; Cache_VaryByUser = item.Caching.VaryByUser; } public bool? Cacheable { get; set; } public TimeSpan? Cache_Timeout { get; set; } public bool? Cache_VaryByData { get; set; } public bool? Cache_VaryByDevice { get; set; } public bool? Cache_VaryByLogin { get; set; } public bool? Cache_VaryByParameters { get; set; } public bool? Cache_VaryByQueryString { get; set; } public bool? Cache_VaryByUser { get; set; } } }
So let’s walk through that. We’re essentially grabbing the list of rendering ids (we’re separating them with a comma) and then rendering the HTML using Sitecore’s extension method. We’re setting the cache settings based off the actual rendering item in Sitecore.
**You see I’m setting the DataSource as a parameter. This is because the Sitecore Rendering extension doesn’t really like Controller Renderings. It loses some context.
Shout out to Mark Ursino for starting me off in this direction of the Sitecore Rendering Method.
PS: To invoke this from your view, you use the simple three-liner below:
@foreach (var navItem in Model.MegaMenuLinks) { @Html.Sitecore().RenderWithCacheSettings(navItem) }
If you run into any issues with this, drop me comment below. Happy Friday!