Global Conditional Rendering rules with Sitecore MVC

So I was playing around the other day with Sitecore MVC and some View Renderings. As I’m more familiar with Web Forms than MVC I started asking my self how I translate some thing I do in Web Forms to MVC like for example how would I go about hiding a view rendering. In Web Forms with Sub Layouts it’s easily done by changing visiblity like:

this.Visible = false;

So for example if the Item that the datasource would point to, didn’t have any published version in the current language, I could just hide it using that small piece of code.

Now how would I go about doing this with MVC. The only solution I found what to used jquery to hide the rendering which wasn’t good enough. So I started looking at the rules engine and conditional renderings and found that you can define global conditional rendering rules. However only with Web Forms and not MVC. According to documentation global conditional rendering rules does not work with MVC, no reason why, it just don’t… or does it?

Weeeeeell, regular conditional rendering works with MVC. So wouldn’t it be possible to extend this to include the global rules as well? After some fiddling with dotPeak on Sitecore assemblies it was quite easy to implement.

Looking at how it was done for Web Forms in Sitecore.Pipelines.InsertRenderings.Processors.EvaluateConditions which is run for every rendering when using Web Forms. And also investigate where and when Sitecore handled conditional renderings for MVC. It was quite straight forward to implement a processor processor that would evaluate the global conditional rendering rules for the MVC renderings.

Once I was done with the processor I implemented a condition that used the ConditionalRenderingsRuleContext and check if the datasource of the rendering had any versions published in the current language. Just to see of I could hide all renderings with a datasource pointing at items with no version. Guess what… worked like a charm.

You can find the source code here

Please do comment other ways to hide renderings in MVC 🙂

Follow up on: Hacking the layout rendering

In my previous post I wrote about fetching the layout definitions of sub pages and add them to a top page and making Sitecore think they belong to the top page. This was done on Sitecore.NET 6.5.0 (rev. 111123).

This week we tried implementing the same function on a Sitecore 6.6.0 (rev. 130404). While the base function worked, with it displaying the sub pages layouts on the top page, the DMS didn’t handle if there were any conditional renderings. After some looking around in the pipelines I found out that the evaluations of the conditions were done in the insertRenderings pipeline which in turn was run before the renderLayout pipeline where my pipeline to fetch the layout definitions were. So I was adding the layout definitions after Sitecore had evaluated the conditions.

<insertRenderings>
        <processor type="Sitecore.Pipelines.InsertRenderings.Processors.GetItem, Sitecore.Kernel"/>
        <processor type="Sitecore.Pipelines.InsertRenderings.Processors.AddPageDesignerRenderings, Sitecore.Kernel, Version=6, Culture=neutral"/>
        <processor type="Sitecore.Pipelines.InsertRenderings.Processors.AddRenderings, Sitecore.Kernel"/>        
        <processor type="Sitecore.Pipelines.InsertRenderings.Processors.EvaluateConditions, Sitecore.Kernel"/>
</insertRenderings>

The insertRenderings pipeline

To solve this problem I had to add the layout definitions before the conditions were evaluated which was done in the EvaluateConditions processor. What we did was to create a pipeline which basically did the same as before and add it just after the AddRenderings processor, looking like this.

public class AddSubPageRenderings : InsertRenderingsProcessor
{
    public override void Process(InsertRenderingsArgs args)
    {
        Assert.ArgumentNotNull((object)args, "args");
        if (args.ContextItem == null)
            return;
        DeviceItem device = Context.Device;
        if (device == null)return;
        if (!args.ContextItem.TemplateID.Equals(new ID("{D42E1DBF-D9F5-4DC8-A8B7-0F8B5BC5758C}"))) return;
        if (!Sitecore.Context.PageMode.IsNormal) return;
        var referenses = new List();
        foreach (var child in args.ContextItem.Children.Where(i => i.IsPublished()))
        {
            var renderings = child.Visualization.GetRenderings(device, true);
            foreach (var renderingReference in renderings)
            {
                if (!renderingReference.Placeholder.Equals("content", StringComparison.InvariantCultureIgnoreCase) && !renderingReference.Placeholder.StartsWith("/main/content", StringComparison.InvariantCultureIgnoreCase)) continue;
                renderingReference.Settings.DataSource = string.IsNullOrEmpty(renderingReference.Settings.DataSource) ? child.Paths.FullPath : renderingReference.Settings.DataSource;
                referenses.Add(renderingReference);
            }
         }
        args.Renderings.AddRange(referenses);
        args.HasRenderings = args.Renderings.Count > 0;
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <insertRenderings>
        <processor patch:after="*[@type='Sitecore.Pipelines.InsertRenderings.Processors.AddRenderings, Sitecore.Kernel']" type="Test.Pipelines.AddSubPageRenderings, Test"/>
      </insertRenderings>
    </pipelines>    
  </sitecore>
</configuration>

Now all the conditional renderings from the sub pages are evaluated accordingly.

Hacking the layout rendering

I recently worked on our own company web page where the criteria is that all Sub Pages to a page in the top navigations (Top Page) should be rendered as sections on that page. The Sub Pages should be individually browsable. You should be able to edit them using the Page Editor. And as a personal goal it should support nested placeholders. Extra if the DMS personalisation could work.

Previously I had done a similar solution. But as the developer I am, I wanted to try doing it differently. The previous solution was to have a rendering go through all child items and extract all layout definitions in a specific placeholder and load the control using Page.LoadControl(“~/path”) and add the control to a regular asp placeholder on Page_Load. This didn’t allow nested placeholders and off course not the DMS features.

The plan

I knew that Sitecore at some point had to load all the renderings for the item and my plan was to add the renderings of the child items at the same time so that Sitecore would think that the layouts of the child items actually belonged to the Top Page.

So using .NET Reflector we set out to find the best place to do our magic. And after a short search it was quite obvious that we should look at the renderLayout pipeline and in the InsertRenderings processor. This processor takes all the Items layout definitions and adds them to the Sitecore.Context.Page which is what will be rendered for the current request.

Creating the Pipeline

We created our own processor and added it right after the InsertRenderings processor which looks like this.

public class FetchChildLayoutDefinition : RenderLayoutProcessor
    {
        public override void Process(RenderLayoutArgs args)
        {

            var item = Sitecore.Context.Item;
            if(item==null) return;
            if (!item.TemplateID.Equals(new ID("{99F41F1F-47B2-422A-82A9-0650B102989D}"))) return;
            if (!Sitecore.Context.PageMode.IsNormal) return;

            using (new ProfileSection("Insert child renderings into page."))
            {
                DeviceItem device = Sitecore.Context.Device;

                foreach (var child in item.Children.Where(ItemTools.IsPublished))
                {
                    var renderings = child.Visualization.GetRenderings(device, true);
                    foreach (var renderingReference in renderings)
                    {
                        if (!renderingReference.Placeholder.Equals("content", StringComparison.InvariantCultureIgnoreCase) && !renderingReference.Placeholder.StartsWith("/main/content", StringComparison.InvariantCultureIgnoreCase)) continue;
                        renderingReference.Settings.DataSource = string.IsNullOrEmpty(renderingReference.Settings.DataSource) ? child.Paths.FullPath : renderingReference.Settings.DataSource;
                        Sitecore.Context.Page.AddRendering(renderingReference);
                    }

                }
            }
        }
    }

 

A quick explanations of the processor. We only wanted to do this for a specific page on the site which has its own Templates. So we check if the current item is based on that Template. Since we actually add all the renderings so that Sitecore think they belong to the current page, this also applied for the Page Editor, so when we saved the Top Page with all the renderings gathered from the child items it ended up saving all these renderings as well which wasn’t desirable. So we hade to check that we weren’t in any other mode than normal. After that we iterate all published children and extract all renderings using the current device. Since the sub pages should be browsable it contains elements such as a header, footer navigations and so on, these renderings were already added to the Top Page, so we wanted to ignore these common renderings. To solve this we assigned a placeholder “content” that we used as a base and only used renderings that were added inside this placeholder or nested within. There are two ways you can add a rendering to a placeholder, you can either write the placeholders name like “content” or you can write the nested path of the placeholder like “/main/content” which the Page Editor does when adding a rendering, so we had to look for both of them. And when we found a rendering that fulfilled the placeholder criteria, we add that to Sitecore.Context.Page. And that’s it. Right…?

Multiple placeholder problem

Well this actually worked, somewhat… when we only had renderings in the content placeholder. When we started nesting placeholders and renderings within the “content” placeholder the problem started. It was kind of an obvious problem that I was counting on. If a rendering with the placeholder “subpageprecontent” would be added to the “content” placeholder on multiple sub pages and this rendering would be collected and added to the Top Page, all these “subpageprecontent” placeholder would have the same adress “/main/content/subpageprecontent”. And if something would have been added to that placeholder, it all would end up in the first occurrence of the “subpageprecontent” placeholder since there isn’t anything unique about them. So to solve this, we would have to make the placeholders unique. This got me thinking of a prototype module I’ve previously used called DynamicKeyPlaceholder which basically adds something to the key of the placeholder to make it unique. When adding a rendering to an item a new Guid is generated for that rendering and added as the attribute UniqueID in the layout definition xml. We ended up using that guid and concatenated it with the key of the placeholders in the renderings added to the “content” placeholder resulting in a unique placeholder path looking something like this “/main/content/subpageprecontent{108127af-7f86-44e6-9fce-14baf564faed}”. Below you can see a screen shot of how it looks like in Sitecore.

Now we had unique placeholders even if it was the same placeholder in the same rendering because of the UniqueID generated for the rendering.

Did it work with the DMS?

As a bonus, since we add the entire rendering to with all conditions for the rendering to the Top Page so that Sitecore thinks it belongs to that page. It actually worked with the DMS out of the box. We didn’t have to do anything else to get this working. Pretty cool huh 😀

 

Integrate an external media library into Sitecore – Part 3

Finally the last post in this serie is done.

In the previous post we covered how we did to store the information we needed in the Image field in Sitecore.

In this post I will show you what we did to get the information stored in the Image field out on the website. I’ll start with a short warning, this will get complicated, so I will not go into details on all parts. At some point in this project we took some shortcuts which will explain some of the solutions we did that could have been better. This post does not contain much code examples since this post would be way to long, so at the end is a zipped file with all classes used in this solution.

When requesting any media from Sitecore, all file ends with .ashx, this ending is mapped to a requesthandler that steams the media to your browser.
Our plan was to use this in such a way that we request an predefined media-item inside Sitecore based on one of our extended media templates with the external media id and media extension as querystrings.
So we created an item /sitecore/media library/EmptyMedia/EmptyImage based on the template Externa Image that we created in one of the earlier posts without any data.
This media-item would get the url:
~/media/EmptyMedia/EmptyImage.ashx
We were going to request this item with both the id from the external media library and the extension of the image like this:
~/media/EmptyMedia/EmptyImage.ashx?extid=30779&extension=jpg

This was going to be our base for requests, now we needed a requesthandler that would take care of our media items and not mess up the existing Sitecore media functionality.
We started by creating a complete copy of the existing MediaRequestHandler that Sitecore uses for handling all .ashx requests. And started modifying it.

Our custom requesthandler would have to check if the request was done with extid as querystring and based on this either handle it as an external media item or as a Sitecore media item.

In Sitecores MediaRequestHandler, the ProcessRequest method wich is triggered on request, runs a method bool DoProcessRequest(HttpContext context)  which validates the request and creates an important object Sitecore.Resources.Media.Media and passes this to the method bool DoProcessRequest(HttpContext context, MediaRequest request, Sitecore.Resources.Media.Media media) which then loads the stream and passes it to the response output.

Simple right? Well we thought so, and we wanted to use these methods since they were already there. We started by checking if the request was done with extid as querystring and if that was the case a ran our custom method:

bool DoProcessExternalRequest(HttpContext context, MediaRequest request, string id, string extension)

This method was responsible for creating an object of the Sitecore.Resources.Media.Media. So we did some research on the Media class and found out that to create this we needed to create a MediaData object and pass it to the constructor of Media. And to create the MediaData object we needed a MediaItem that was based on the EmptyImage item inside Sitecore.
We now knew which objects we needed, so it was time to find out where the stream of data was loaded. After some poking around we found that the MediaRequestHandler calls the method GetStream in Sitecore.Resources.Media.Media wich in turn runs the pipeline getMediaStream with the MediaData object as parameter. All of the processors in the getMediaStream pipeline triggers a chain of methods that at one point ends up running the method GetBlobStream of MediaData which would retrieve the data from the Field by calling its GetBlobStream method. This was our entry point. We were going to check if the stream delivered from the Field objects was null and if so check if so validate if the request was for an external media file and deliver its stream instead.

So we had to be able to make the external id and extension available for this method. Since we were going to use this in some different cases to we decided to make an extension of the complete chain of classes and and mimic the behavior. We already had an extension of the MediaItem called ExtMediaItem created in the previous post which could hold the external id, extension and the width and height. So over to the MediaData class, we needed it to be able to hold and pass the external id and extension and to handle the ExtMediaItem. So the class ExtendedMediaData was created and we did the following alterations in this class:

  • Added attribute ExtID to hold and pass the external media id
  • Altered MediaId to deliver a unique id for external media. The MediaId is, what I think, used for caching.
  • GetBlobStream – this method was altered to try to load the data from the external media library if the stream that was delivered by the field is null.

With that altered we tried to get this working, so we created a ExtMediaItem based on the EmptyImage in Sitecore.
We created a ExtendedMediaData based on the ExtMediaItem and provided it with the  the values from the querystring for extid and extension.
This was then fead into a Media object and sent to
bool DoProcessRequest(HttpContext context, MediaRequest request, Sitecore.Resources.Media.Media media)
in the RequestHandler.
Unfortunately this didn’t, we got an exception telling us that the object provided wasn’t a MediaData object, even though ExtendedMediaData inherits from MediaData.
Because the lack of time we decided to create an extension of the Media class aswell where we altered all MediaData objects handled in the class to ExtendedMediaData, which worked.

So now when I make the following request:
~/media/EmptyMedia/EmptyImage.ashx?extid=30779&extension=jpg
the following steps are made:

  1. We check if the url contains extid and calls a custom method to build a Media object.
  2. An ExtMediaItem based on the item /sitecore/media library/EmptyMedia/EmptyImage is created
  3. An ExtendedMediaData is created with the ExtMediaItem and also provided with the ExtId and Extension values.
  4. A custom Media class wich intherits Sitecores Media class i created with the ExtendedMediaData.
  5. This Media object is provided to the DoProcessRequest method in the requesthandler.
  6. The DoProcessRequest calls the method GetStream on the Media object which triggers the getMediaStream pipeline.
  7. At some point one of the processors in the getMediaStream pipeline triggers a chain of methods that calls the GetBlobStream in the ExtendedMediaData.
  8. If the stream delivered by the Field object in ExtendedMediaData, it tries to get a stream from the web service of the external media library based on the extid value.
  9. Once this stream has been fetched, it’s returned and handled by the processors in the getMediaStream pipeline, like being resized. This is where the extension is needed, if the ExtendedMediaData doen’t have an extension it won’t be handled as a image.
  10. When the getMediaStream pipeline is done processing the stream it’s return to the Media object which will add the stream to Sitecores media cache and then return it to the RequestHandler.
  11. This is where it all ends, when the stream has been delivered to the RequestHandler, it is written to the output stream of the HTTP response object.

So thats the end of the series on how we integrated an external media library. Below is a link to all files used, they are not in any order what so ever and there might be some or alot of code that isn’t in use 🙂

Here’s the zip-file 😀