Over the weekend, I posted part 1 of a discussion of this thing I created that I’m calling “conditional pages”. I promised to elaborate on the technical details, so here I am with some code samples and instructions that hopefully will allow others to reproduce what I have done.
Before I begin, let me once again thank Sitecore CTO John West - without whose blog posts and assistance on the SDN forums, I might have never figured this stuff out. I haven’t found a better source of information on rules engine customizations. If you’re looking for more information, check out his blog here http://goo.gl/5V4Gg
This whole process begins when a request comes in to the Sitecore pipelines. By implementing a custom StartTrackingProcessor, I am able to intercept the request, inspect the item that was requested and if it is a conditional page, execute the rules defined on the page to determine what item should replace the originally requested item in the Sitecore context.
To create my custom processor, I just created a class that derives from Sitecore’s StartTrackingProcessor and implemented an override for the Process() method. The declaration looks like this:
public class ConditionalPageTrackingProcessor :
StartTrackingProcessor
{
public override void Process(StartTrackingArgs args)
{
}
}
Within my Process() method, I first check if Sitecore.Context.Item is using the Conditional Page template. I described this template in my previous post, but here’s a quick screenshot of what it looks like in Sitecore:
I’ll explain the path you see in the source for the Rules field in a little bit.
If the requested item is not a conditional page, then there is nothing for my processor to do so the code just returns. However, if it IS a conditional page – I pull the value from the Rules field like this:
string rulesXml = Context.Item["Rules"];
Note, there is no strongly-typed Field-derived class for fields of type Rules, so I’m pulling out the raw XML string. This string now needs to be parsed, and that is done by a method on the Sitecore.Rules.RuleFactory class - ParseRules<T>():
var parsed = RuleFactory.ParseRules<ConditionalPageRuleContext>
(Sitecore.Context.Database, rulesXml);
Now you may be asking – what is this ConditionalPageRuleContext class? This is my custom rules engine context. It derives from Sitecore.Rules.RulesContext and adds a property that indicates what item (if any) the rules want the user redirected to. The concept of a rules context seems a bit confusing at first, but suffice it to say that this class is what allows the rules engine to communicate information back to the code that executes it. Here’s what the ConditionalPageRuleContext class looks like in code:
public class ConditionalPageRuleContext : Sitecore.Rules.RuleContext
{
public Item NewItem { get; set; }
}
In order to proceed, I need to construct an object of this class and tell it what item it is acting on:
ConditionalPageRuleContext ruleContext =
new ConditionalPageRuleContext();
ruleContext.Item = Context.Item;
And then, I run the rules that were parsed – passing in my rule context:
RuleList<ConditionalPageRuleContext> rules =
new RuleList<ConditionalPageRuleContext>();
rules.Name = Context.Item.Paths.Path;
rules.AddRange(parsed.Rules);
rules.Run(ruleContext);
When this returns, I check if ruleContext.NewItem was set and if it was, I set Sitecore.Context.Item = ruleContext.NewItem.
So wait – how does the NewItem property on my context get set by the rules engine? Let’s take a closer look at an example rule that redirects some users to a page “Test 1” and others to “Test 2” instead:
The actions here are “set current item to …”. This is a custom rules engine action that only runs within a ConditionalPageRuleContext and sets the NewItem property of that context to the specified value. Here’s the code:
public class SwitchToItemAction<T> :
RuleAction<T> where T :
Rules.ConditionalPageRuleContext
{
public string ItemId { get; set; }
public override void Apply(T ruleContext)
{
ruleContext.NewItem =
ruleContext.Item.Database.GetItem(new ID(ItemId));
}
}
If you haven’t seen a custom action before, the public property gets set by Sitecore to the value selected in the rule editor dialog (the ID of “Test 1” or “Test 2” in the above example), and the Apply() method gets called only for the actions whose conditions pass. Making this action available in the rules editor requires something in Sitecore. Remember that source path on the Rules field for the Conditional Page template?
Inside the /sitecore/System/Settings/Rules/ folder, I created a folder named “Conditional Page”. Within that folder, I created a folder named “Actions”. The rules editor looks at the field’s source and restricts the actions that can be chosen to those within the “Actions” sub-folder. So, to configure my action, I created an item in this Actions folder named “Switch to Item” using the /sitecore/templates/System/Rules/Action template.
There are two fields to fill in on this item – Text and Type. Type is simply a pointer to the above SwitchToItemAction class (in the usual Namespace.Class, Assembly format). Text is special. Mine looks like this:
The text outside of the square brackets is what the user will see in the rules editor. Inside the square brackets are four comma-separated values:
ItemId – The name of the property on the class to be set to the selected value.
Tree – The type of UI element to present to the user for choosing a value.
root=… – An argument passed to the UI element. In this case, the root item for the tree.
an item – The text to show in the rules editor before an item has been chosen.
Note – I haven’t found a documented list of available UI elements and what their arguments are. I just saw another example using “Tree”.
There’s one more thing I found it necessary to do to make everything work… My StartTrackingProcessor was never called if the conditional page item didn’t have a layout set on it. Instead, Sitecore would abort in an earlier pipeline and display the “layout not found” error. I just fixed this by setting the standard values for the Conditional Page template to use a blank layout.
If you read Part 1 of this series, I talked quite a bit about a Page Design condition… Reading and writing data from a site visitor’s profile is pretty simple, but I’ll cover that and how to create a custom rules engine condition in my next blog post. :)
I hope this helps somebody. Please, let me know if you use this idea or think of any ways I could improve it. And if you have any questions, just send them to me via direct message on Twitter - @williamsk000.
A few days ago, I posed a question here (and on Twitter and on the SDN forum and some other places) regarding accessing visitor profile data from the HttpRequestBegin pipeline. I promised to post about why I needed to do that once I had everything working, so here’s the story. Sorry – it’s a bit of a long one. :)
Introduction
Recently, an NTT Data client asked me if I could do something with Sitecore that I had never imagined before… Essentially, they wanted to A/B test multiple pages across a visit to their site. If a visitor saw homepage A, they would also see the “A” versions of landing pages, etc. The changes between versions were not minor – every rendering on a page was likely to change between the “A” and “B” version, some renderings would use completely different datasource templates, etc.
First, let me say - I completed OMS training and certification a year and a half ago, but this was my first real marketing suite request. The client’s site is being built on current Sitecore 6.5, meaning the latest DMS features were available - but I had little familiarity with them. I’ve developed a working solution, but I imagine there may be a better way to accomplish it. If you know of one, I would love to hear about it!
Anyhow - I could not figure out a way to do this with stock DMS features (based on my limited experience), but eventually I thought of a simple customization that would make it possible… A custom rules engine condition that would check a visitor’s profile for a “Page Design” key with a value of “A” or “B”. If they did not yet have a value, randomly choose one, save it to their profile, and then make a comparison.
The Page Design Condition
With this in place, I was able to set personalization rules on multiple renderings on a page, and have them each evaluate my custom condition (e.g. except where the visitor’s page design is “A”, hide component – see above screenshot). This worked, but it made a crazy mess for content authors within the page editor… Both versions of the page were visible at the same time and it was terribly confusing to try and determine which renderings were meant to appear on which page version, much less edit any content.
Back to the drawing board, then. What I needed was a way to create two separate items in the content tree so they could be edited separately, but allow them to share the same URL or have an alias that redirected to them without changing the URL seen by visitors. I went down many dead end roads trying to realize this idea, but the concept I ended up with is what I’m calling a “conditional page” – an item with rules applied (when the visitor’s page design is “A”, change the current item to “Homepage A”. When the visitor’s page design is “B”, change the current item to “Homepage B”) that changes Sitecore’s context item to allow the page that was requested (the conditional page) to appear as the alternate page.
I present for you here the working solution I eventually landed upon.
Conditional Pages
A conditional page defines a set of rules under which the site visitor may be “redirected” to another item which will then appear to the visitor in place of the originally requested page.
The above screenshot is of an item using the Conditional Page data template. It is named “demo” and is a child of the site’s homepage. Here, I am using the page design condition discussed above along with a custom action that causes the Sitecore context item to change (I’ll discuss how momentarily). The effect is that when a visitor goes to the /demo URL, they will see either Landing Page A or Landing Page B depending on which page design has been chosen for them during their visit.
The Conditional Page data template has just one field – Rules – of type “Rules”. The Source for this field is set to a folder I created under /sitecore/system/Settings/Rules named “Conditional Page”. This folder has sub-folders named Actions and Conditions containing my custom actions and condition respectively. For more information about this folder structure, you can read John West’s blog post about using the rules engine here http://goo.gl/aadSf
The key to making conditional pages work was to intercept the initial item request, execute the rules stored in the Rules field, and change Sitecore.Context.Item early enough in the process that everything else would treat it like that was the page requested all along… It took some trial and error (see previous question about the HttpRequestBegin pipeline) and a little help from John West on the SDN forum, but I eventually found just the spot – the StartTracking pipeline just before the ProcessItem processor.
The technical details of how all of this is done will fill up another blog post, so I’ll save that for later (I promise to post in the next day or two). But if you’re really interested now – take a look at John West’s post I linked above where he is doing something similar using the rules engine to change the context device for certain requests.
By the way – conditional pages also work with other DMS conditions, not just my custom “page design” one. So you can have the page that comes up be based on GeoIP rules or if a visitor has achieved a DMS goal or search engine keywords, etc, etc. It’s kind of like a rules-powered alias.
I hope this concept will be useful to someone else. :) If you have any questions, comments, suggestions, etc – please reach out to me on Twitter @williamsk000. Thanks for reading!
Sorry that this isn't a tutorial-style post today, but I promise if I get all of this working that I will post the full story in a few days. For now, I'm having a Sitecore problem...
I have an HttpRequestBegin pipeline processor that runs right after the stock ItemResolver. I need for it to read a value from the current visitor's profile (or if a value isn't there, write one). Here's what my code looks like:
var currentRow = Sitecore.Analytics.Tracker.CurrentVisit.Profile.FirstOrDefault(row => (row.ProfileName == "foo" ));
if ( currentRow == null )
{
currentRow = Sitecore.Analytics.Tracker.CurrentVisit.CreateProfile("foo");
currentRow.PatternValues = "bar";
currentRow.AcceptChanges();
Sitecore.Analytics.Tracker.CurrentVisit.AcceptChanges();
}
This code when placed in a sublayout works as expected - the first time, the profile value is created and subsequent times it is read back out. However, doing this in the HttpRequestBegin pipeline seems to have no effect at all. Anyone know - is there no way to interact with the current visitor's profile in the pipeline?
There's actually a lot more going on here involving rules engine, etc... As I said, I'll post the full story if I can get it working 'cause it's pretty neat. But for now, I have to get past this particular hurdle...
If you want to contact me regarding this post, it's probably best to use Twitter. You can DM me at @williamsk000