Reinoud van Dalen

August 17, 2014

Using Sitecore placeholders in a custom DisplayMode

In case you did not know: DisplayModes are awesome and I think we should consider to use them more. This example will show you how you can create your own displaymode and understand the potential of this feature.

Why use them?

I think everyone has found themselves in a situation where a particular component can be reused in another part of a site. However, the presentation should differ and possibly you end op copying the logic and presentation which is something you should always want to prevent as programmer. Here I present you one of the possibilities to consider: DisplayModes.

So what are DisplayModes?

DisplayModes are classes that help ViewEngines locate specific views in a specific situation. The default DisplayMode shipped with MVC enables you to create custom views for mobile devices. So where you have, for instance, a /Views/Product/Index.cshtml, you can add a /Views/Products/Index.mobile.cshtml which will be chosen if you visit the application with a mobile device. For a complete explanation you could visit: 
http://build-failed.blogspot.nl/2012/03/aspnet-mvc-4-part-2-mobile-features.html

Creating a PlaceholderDisplayMode

I aim to create a DisplayMode which will append the current placeholder to the filename. I have a controller rendering "Title" added to a col-12 and another to a col-6 placeholder. By default the /Views/Common/Title.cshtml view will be used, which renders a <h1> tag, but for the col-6 placeholder I want to render a <h2> tag. Ofcourse there are other ways to achieve this but for now this will do. To start off I created the PlaceholderDisplayMode, implementing IDisplayMode:

public class PlaceholderDisplayMode : IDisplayMode
{
    public bool CanHandleContext(System.Web.HttpContextBase httpContext)
    {
        throw new NotImplementedException();
    }
    public string DisplayModeId
    {
        get { throw new NotImplementedException(); }
    }
    public DisplayInfo GetDisplayInfo(System.Web.HttpContextBase httpContext, string virtualPath, Func<string, bool> virtualPathExists)
    {
        throw new NotImplementedException();
    }
}

So we need to:

  • Let the caller know when this DisplayMode can be used
  • Give an DisplayModeId so the view can be (uniquely) cached in the ViewLocations cache
  • Return a DisplayInfo object with the modified virtualPath

The CanHandle method will check 2 things:

  • Is it an SitecoreMvc Route?
  • Do we have a Rendering to get the placeholder from?
private RenderingContext _renderingContext;
public bool CanHandleContext(System.Web.HttpContextBase httpContext)
{
    //Is it an Sitecore MVC route?
    var isContentUrl = httpContext.Items["sc::IsContentUrl"] as string;
    if (string.IsNullOrEmpty(isContentUrl) || !isContentUrl.ToBool()) return false;
    //Is there a rendering to get the placeholder from?
    _renderingContext = RenderingContext.CurrentOrNull;
    return _renderingContext != null;
}

Next I create 2 methods that will help determine the modified virtualPath and check if the (virtual) file exists before returning a new DisplayInfo object or null:

public DisplayInfo GetDisplayInfo(HttpContextBase httpContext, string virtualPath, Func<string, bool> virtualPathExists)
{
    var filePath = TransformPath(virtualPath);
    if (filePath != null && virtualPathExists(filePath)) return new DisplayInfo(filePath, this);
    return null;
}
private string TransformPath(string virtualPath)
{
    var placeholderName = GetFriendlyPlaceholderName();
    //if we do not have a place holder, return originalPath
    if (string.IsNullOrEmpty(placeholderName)) return virtualPath;
    var extension = Path.GetExtension(virtualPath);
    //return virualpath as: {virtualPath}.{placeholder}.{extension}
    return Path.ChangeExtension(virtualPath, placeholderName + extension);
}

/// 
/// Gets the placeholdername from current RenderingContext
/// Removes any guids generated by dynamic placeholders
/// 
/// 
private string GetFriendlyPlaceholderName()
{
    var placeholderName = string.Empty;
    //get last segment from placeholder path
    if (_renderingContext != null)
    {
        placeholderName = (_renderingContext.Rendering.Placeholder + "").Split('/').Last();
        //remove guid from dynamic placeholders
        placeholderName = Regex.Replace(placeholderName, @"_[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}", string.Empty, RegexOptions.IgnoreCase);
    }
    return placeholderName;
}

Note that I remove guids because I make use of DynamicPlaceholders.

The last thing to do is return a DisplayModeId so the view location can be properly put in the location cache:

public string DisplayModeId
{
    get { GetFriendlyPlaceholderName(); }
}

The DisplayMode is finished and after we have registered the Mode on startup the placeholder specific views are used.

DisplayModeProvider.Instance.Modes.Insert(0, new PlaceholderDisplayMode());

And the result (set h2 to render in red to highlight the difference):

Some description

TAGS: sitecore mvc