This blog post describes how you can use the rules engine in the Sitecore ASP.NET CMS to invoke rules in a context that you define. Showing how to manage and invoke both global and specific rules, the example uses the rules engine to determine the context device, which controls the presentation components that Sitecore uses to render content. For more information about the rules engine, see the Rules Engine Cookbook on the Sitecore Developer Network (SDN), as well as my previous blog posts Sitecore Rules Engine and Conditional Rendering and Use the Sitecore Rules Engine to Control Item Names. For more information about the context device and presentation components, see the Presentation Component Reference and the Presentation Component API Cookbook.
By default, the DeviceResolver processor in the httpRequestBegin pipeline defined in the web.config file determines the context device:
You can use the rules engine to refine how Sitecore determines the context device. For example, you could set the context device if the host name matches a given string, if the context site is a specific managed site, if the referring URL matches a given string, if search keywords include a specific token, if the context language is a specific language, and so forth. Note that Sitecore 6.4 introduced a number of new conditions for use with the rules engine.
To summarize, a rule consists of some number of conditions and some number of actions. When evaluating a rule, if the condition(s) evaluate to true, Sitecore invokes the actions. For this example, an httpRequestBegin pipeline processor evaluates a list of global rules to determine the context device. If none of the global rules applies, then the processor invokes rules defined in each of the device definition items, and sets the context device to the first such item for which the rule evaluates to true.
There are at least two ways that you can use the rules engine to set the context device. First, you can run some number of global rules, where the rule sets the context device to some specific device if the condition is true. Second, you can run some number of rules associated with each device, and set that device as the context device if the condition evaluates to true.
Download the prototype code and compile it into your Visual Studio project, which includes code that shows how to invoke both global and item-specific rules.
When you need to extend existing functionality in a pipeline, such as device resolution logic, you can replace existing processors in the pipeline, or you can add processors to the pipeline. When possible, avoid tampering with existing functionality. Because custom logic to determine the context device may depend on properties of the Sitecore context including the context site, the context item, the context language, and even the context device as determined by the default DeviceResolver, leave the default DeviceResolver in place, and add the custom device resolver late in the pipeline. More specifically, I think it belongs between ItemResolver and LayoutResolver:
<processor type="Sitecore.Pipelines.HttpRequest.DeviceResolver, Sitecore.Kernel" />
<processor type="Sitecore.Pipelines.HttpRequest.LanguageResolver, Sitecore.Kernel" />
<processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" />
<processor type="Sitecore.Sharedsource.Pipelines.HttpRequest.RulesEngineDeviceResolver, assembly" />
<processor type="Sitecore.Pipelines.HttpRequest.LayoutResolver, Sitecore.Kernel" />
When you define a new context for rules, such as using rules to determine the context device, you can either reuse the existing Sitecore.Rules.RuleContext class, or create a class that inherits from this base class. Primarily to demonstrate this feature, but also to abstract the Sitecore context, I chose to implement a specific context.
To manage global rules for the new rule context, create the User Defined/Rules/SetContextDeviceRule data template. Include a single-line text field named Name, a field of type Rules named Rule, and a field of type Checkbox named Disabled. Set the Source property of the Rule field to /Sitecore/System/Settings/Rules/Determine Context Device (this controls the conditions and actions you can select for this type of rule). This path does not yet exist, but you will create it in a moment. Set the icon for the data template to Software/32x32/shape_ellipse.png.
If there are any global rules for a rule context, or any conditions or actions specific to that rule context, then create a folder to contain those conditions, actions, and rules, and within the new folder, create child folders named Actions, Conditions, and Rules, all using the Common/Folder data template. The Actions folder will contain action definition items specific to this rule context, the Conditions folder will contain condition definition items specific to this context, and the Rules folder will contain global rules for this context. When defining a rule for this context, the user interface will list the conditions and actions defined in the Conditions and Actions folders under this path merged with the contents of the Conditions and Actions folders under the /Sitecore/System/Settings/Rules/Common item. For some rule contexts, you might not need all three of these child folders (for instance, if there are no context-specific Actions or no global rules for this context), or you might not need to create this branch at all (if the rule context uses only Common conditions and actions). For the device resolution context, create the /Sitecore/System/Settings/Rules/Determine Context Device item containing children named Actions and Conditions (if you expect to use global rules, also create a child named Rules). Configure insert options for the Actions child to include only the System/Rules/Action data template. Configure insert options for the Conditions child to include only the System/Rules/Condition data template. Configure insert options for the Rules child to include only the User Defined/Rules/SetContextDeviceRule data template.
Using the System/Rules/Condition data template, create the /Sitecore/System/Settings/Rules/Determine Context Device/Conditions/Context Item Has Layout Details for this Device condition definition item. Set the Text field to where the context item has layout details for this device, and set the Type field to Sitecore.Sharedsource.Rules.Conditions.ContextItemHasLayoutDetailsForThisDevice,assembly. This condition tests whether the context item has layout details for a device, where the item containing the rule is also the device definition item. Because this condition accesses the device that defines the rule, you should only use this condition in rules defined in specific devices, not in global rules.
Using the System/Rules/Action data template, create the /Sitecore/System/Settings/Rules/Determine Context Device/Actions/Set Context Device item action definition item. Set the Text field to set context device to this device, and set the Type field to Sitecore.Sharedsource.Rules.Actions.SetContextDeviceToThisDevice,assembly. Because this action accesses the device that defines the rule, you should only use this action in rules defined in specific devices, not in global rules.
Using the System/Rules/Action data template, create the /Sitecore/System/Settings/Rules/Determine Context Device/Actions/Set Context Device to This Device action definition item. Set the Text field to set the context device to the [DeviceID,tree,root=/sitecore/layout/devices,specific] device, and set the Type field to Sitecore.Sharedsource.Rules.Actions.SetContextDeviceToSpecificDevice,assembly. Global rules for this context can use this action to set the context device.
To add a field to contain the rule to the data template for devices, create the User Defined/Layout/Device data template containing a Detection section containing one field named Rule of type Rules with source set to /Sitecore/System/Settings/Rules/Determine Context Device. You can add any number of fields of type Rules to the data template. Then add this data template to the base templates for the Layout/Device data template. Then, go to the device definition items under /Sitecore/Layout/Devices, and in the Rule field, select conditions and actions. I simply used the condition and action I had just created.
To define global rules, under the /Sitecore/System/Settings/Rules/Determine Context Device/Rules item, insert items using the User Defined/Rules/SetContextDeviceRule data template, and fill out the fields appropriately.
The default DeviceResolver logic for matching user agents matches individual devices. For a prototype processor that uses regular expressions to match the user agents of multiple devices, see the List of User Agent Strings for Mobile Devices on the Sitecore Developer Network (SDN) forums. Alternatively, you could implement a global rule that sets the context device if a condition that matches the user agent against a regular expression evaluates to true. I included code for a prototype of such a condition.
To use this prototype, create the /Sitecore/System/Settings/Rules/Determine Context Device/Conditions/User Agent Matches Expression item using the System/Rules/Condition data template. Set the Text field to where user agent matches [Expression,,,expression] and the Type field to Sitecore.Sharedsource.Rules.Conditions.UserAgentMatchesExpression,assembly. When creating a rule with this condition (such as a global device resolution rule), set Expression to a regular expression something like the following that should match chrome and opera user agents:
This post is long and it might seem complicated if you've never used the rules engine before. But note that this took just a few dozen lines of code, and provides a powerful feature with a standardized, browser-based user interface with very flexible configuration options.
This is truly awesome application of rules engine!
Consider DeviceAtlas sdn.sitecore.net/.../ShowPost.aspx http://deviceatlas.com/
See also: sitecoresnippets.blogspot.com/.../wurfl-based-mobile-detection-solution.html sitecoresnippets.blogspot.com/.../mobile-device-detector-performance.html
Another blog post about the rules engine: adeneys.wordpress.com/.../
The rules engine must be getting popular, because here's another blog post about it: blog.awareweb.com/.../Sitecore-Rules-Engine-Managing-Top-Level-Navigation-Inclusion.aspx
Additional relevant information/example: www.sitecore.net/.../Apply-Rules-to-All-Items-in-the-Sitecore-ASPNET-CMS.aspx
Am I seeing right that in the order of events, putting this in the httpRequest begin pipeline comes before the existence of the HttpContext.Current.Session object? I'm trying to base a rule off something in session.
@Thomas: That is beyond my knowledge of ASP.NET. I think it depends on whether ASP.NET creates the session automatically when you try to access it or if some step in the ASP.NET page lifecycle populates the session. Note www.sitecore.net/.../What-Invokes-This-Pipeline-in-the-Sitecore-ASPNET-CMS.aspx - BeginRequest calls httpRequestBegin, and is basically the first step in the lifecycle, which may be before session exists. In the Message() method, you could debug HttpContext.Current.Session to see when it becomes non-null. Unfortunately I don't have time today. If session is not available to httpRequestBegin, I think one option might be to use global.asax or an http module to intercept session initiation; hopefully that's not too late for your purpose.
Do I understand right that in the following sentence the first path should end with /Rules instead of /Conditions since it is where we are looking for the rules in the code? Thanks "....To define global rules, under the /Sitecore/System/Settings/Rules/Determine Context Device/Conditions item, insert items using the User Defined/Rules/SetContextDeviceRule data template, and fill out the fields appropriately."
@Alexei: I believe so; sorry about that! I don't have time to go back and test, but I updated the post accordingly. Hmm, nobody else mentioned that since I wrote this in 2010...
Thank you John. Having worked as a technical editor for a long time I am just trained to report any misprint ). Others who applied the code most probably just fixed it silently. Thank you for the invaluable information in the post.