Change context device based on user agent with Sitecore Rule Engine

I pick this up from one of the John West blog post, i want to able to change the Sitecore context device based on the user agent that it detected, so if the device that access my application is a mobile devices then give them the mobile view, if not give them the standard view.

What we’re going to use here are the Sitecore Rule Engine, so we define a certain rule which trigger an action when the condition is fulfilled.In John’s blog he explained that this can be achieved by creating global rules where the rule set the context device if condition is met, or by running rules that is associated with each device.In this post i’m only interested in the global rule approach.

First add this code in the web.config

note: my assembly is Sitecore.RoyalBorough.SharedSource, if you use something else, you need to replace that

1 <processor type="Sitecore.RoyalBorough.SharedSource.Pipelines.HttpRequest.RulesEngineDeviceResolver, Sitecore.RoyalBorough.SharedSource"/>

Which is used to register the RulesEngineDeviceResolver class below

1 using System; 2 using Sitecore.Data.Fields; 3 using Sitecore.Data.Items; 4 using Sitecore.Pipelines.HttpRequest; 5 using Sitecore.Rules; 6 using Sitecore.RoyalBorough.SharedSource.Rules; 7 8 namespace Sitecore.RoyalBorough.SharedSource.Pipelines.HttpRequest 9 { 10 public class RulesEngineDeviceResolver : HttpRequestProcessor 11 { 12 public override void Process(HttpRequestArgs args) 13 { 14 if (Sitecore.Context.Database == null 15 || Sitecore.Context.Item == null 16 || String.Compare(Sitecore.Context.Database.Name, "core", true) == 0) 17 { 18 return; 19 } 20 21 if (this.InvokeGlobalRules()) 22 { 23 return; 24 } 25 } 26 27 /// <summary> 28 /// Invoke global device resolution rules. 29 /// </summary> 30 /// <returns>True if a global device resolution rule applied.</returns> 31 protected bool InvokeGlobalRules() 32 { 33 Sitecore.Data.Items.Item ruleRoot = Sitecore.Context.Database.GetItem("/sitecore/system/Settings/Rules/Determine Context Device/Rules"); 34 35 if (ruleRoot == null) 36 { 37 return false; 38 } 39 40 // TODO: use descendants instead of children 41 // for each possible global rule definition item 42 foreach (Sitecore.Data.Items.Item child in ruleRoot.Children) 43 { 44 string ruleXml = child["Rule"]; 45 46 if (String.IsNullOrEmpty(ruleXml) || child["Disabled"] == "1") 47 { 48 continue; 49 } 50 51 // parse the rule XML 52 RuleList<RuleContext> rules = new RuleList<RuleContext>(); 53 rules.Name = child.Paths.Path; 54 RuleList<RuleContext> parsed = RuleFactory.ParseRules<RuleContext>( 55 Sitecore.Context.Database, 56 ruleXml); 57 rules.AddRange(parsed.Rules); 58 59 if (rules == null || rules.Count < 1) 60 { 61 continue; 62 } 63 64 // invoke the rule 65 RuleContext ruleContext = new RuleContext(); 66 ruleContext.Item = Sitecore.Context.Item; 67 rules.Run(ruleContext); 68 69 // rule applied 70 if (ruleContext.IsAborted) 71 { 72 return true; 73 } 74 } 75 76 return false; 77 } 78 } 79 80 public class SetContextDeviceRuleContext : Sitecore.Rules.RuleContext 81 { 82 private DeviceItem _evaluateDevice; 83 84 public SetContextDeviceRuleContext(DeviceItem deviceItem) 85 { 86 this.EvaluateDevice = deviceItem; 87 this.Item = this.EvaluateDevice.InnerItem; 88 } 89 90 public Item ContextItem 91 { 92 get { return Sitecore.Context.Item; } 93 } 94 95 public DeviceItem ContextDevice 96 { 97 get { return Sitecore.Context.Device; } 98 set 99 { 100 Sitecore.Context.Device = value; 101 this.Abort(); 102 } 103 } 104 105 public Database ContextDatabase 106 { 107 get { return Sitecore.Context.Database; } 108 } 109 110 public DeviceItem EvaluateDevice 111 { 112 get { return _evaluateDevice; } 113 set { _evaluateDevice = value; } 114 } 115 116 public Language ContextLanguage 117 { 118 get { return Sitecore.Context.Language; } 119 } 120 } 121 }

We need to create a new template for our rule, so in /sitecore/Templates/User Defined/Rules create a new template called SetContextDeviceRule

sitecore_contextdevice_createtemplate_setcontextdevicerule

which has the following structure

sitecore_contextdevice_template_setcontextdevicerule

Notice that we set the source for “Rule” to /sitecore/System/Settings/Rules/ Then in Sitecore Content Editor create new folder called Determine Context Device under /sitecore/System/Settings/Rules

sitecore_contextdevice_createdeterminecontextdevicefolder

The rule that we are going to defined works by specifying a condition, and if the condition is passed then run a certain action(s), so under that new folder create folders called Actions,Conditions, and Rules

sitecore_contextdevice_create3newfolders

Next we need to configure the insert options for those folders, do the following :

  • Set the Actions folder insert options to templates/system/rules/Actionsitecore_contextdevice_insertoptions_setaction
  • Do the same for Conditions folder, but instead pointing to Action template point it to Condition Template
  • For the Rules folder, point it to templates/User Defined/Rules/SetContextDeviceRule

Now that we have configured the insert options for those folders, it’s time to add some item to it. Start by adding a new condition under Conditions folder and call it User Agent Is Mobile

sitecore_contextdevice_condition_createuseragentismobile

Next set the Text and Type field

sitecore_contextdevice_condition_edituseragentismobile

Note that the Type field refers to UserAgentIsMobile class in Sitecore.RoyalBorough.SharedSource assembly, which is responsible to figure out if the accessing device is a mobile device or not based on the incoming user agent, code below

1 using System; 2 using System.Web; 3 using Sitecore.Rules; 4 using Sitecore.Rules.Conditions; 5 6 namespace Sitecore.RoyalBorough.SharedSource.Rules.Conditions 7 { 8 public class UserAgentIsMobile<T> : OperatorCondition<T> where T : RuleContext 9 { 10 /// <summary> 11 /// Determines whether [is mobile mobdification]. 12 /// Get from http://www.codeproject.com/KB/aspnet/mobiledetect.aspx 13 /// </summary> 14 /// <value> 15 /// <c>true</c> if this instance is mobile browser; otherwise, <c>false</c>. 16 /// </value> 17 private bool IsMobileBrowser 18 { 19 get 20 { 21 //GETS THE CURRENT USER CONTEXT 22 HttpContext context = HttpContext.Current; 23 24 //FIRST TRY BUILT IN ASP.NT CHECK 25 if (context.Request.Browser.IsMobileDevice) 26 { 27 return true; 28 } 29 //THEN TRY CHECKING FOR THE HTTP_X_WAP_PROFILE HEADER 30 if (context.Request.ServerVariables["HTTP_X_WAP_PROFILE"] != null) 31 { 32 return true; 33 } 34 //THEN TRY CHECKING THAT HTTP_ACCEPT EXISTS AND CONTAINS WAP 35 if (context.Request.ServerVariables["HTTP_ACCEPT"] != null && 36 context.Request.ServerVariables["HTTP_ACCEPT"].ToLower().Contains("wap")) 37 { 38 return true; 39 } 40 //AND FINALLY CHECK THE HTTP_USER_AGENT 41 //HEADER VARIABLE FOR ANY ONE OF THE FOLLOWING 42 if (context.Request.ServerVariables["HTTP_USER_AGENT"] != null) 43 { 44 //Create a list of all mobile types 45 string[] mobiles = new[] 46 { 47 "midp", "j2me", "avant", "docomo", 48 "novarra", "palmos", "palmsource", 49 "240x320", "opwv", "chtml", 50 "pda", "windows ce", "mmp/", 51 "blackberry", "mib/", "symbian", 52 "wireless", "nokia", "hand", "mobi", 53 "phone", "cdm", "up.b", "audio", 54 "SIE-", "SEC-", "samsung", "HTC", 55 "mot-", "mitsu", "sagem", "sony" 56 , "alcatel", "lg", "eric", "vx", 57 "NEC", "philips", "mmm", "xx", 58 "panasonic", "sharp", "wap", "sch", 59 "rover", "pocket", "benq", "java", 60 "pt", "pg", "vox", "amoi", 61 "bird", "compal", "kg", "voda", 62 "sany", "kdd", "dbt", "sendo", 63 "sgh", "gradi", "jb", "dddi", 64 "moto", "iphone" 65 }; 66 67 //Loop through each item in the list created above 68 //and check if the header contains that text 69 foreach (string s in mobiles) 70 { 71 if (context.Request.ServerVariables["HTTP_USER_AGENT"].ToLower().Contains(s.ToLower())) 72 { 73 return true; 74 } 75 } 76 } 77 78 return false; 79 } 80 } 81 82 protected override bool Execute(T ruleContext) 83 { 84 if (HttpContext.Current == null 85 || String.IsNullOrEmpty(HttpContext.Current.Request.UserAgent)) 86 { 87 return false; 88 } 89 90 return IsMobileBrowser; 91 } 92 } 93 }

Next we need to setup the Action, create a new action under the Actions folder and call it “Set Context Device to This Device”

sitecore_contextdevice_action_editsetcontextdevicetospecificdevice

It refer to SetContextDeviceToSpecificDevice class which will be triggered when the condition is true, this class is responsible to change the sitecore context device, code below

1 using Sitecore.Data.Items; 2 3 namespace Sitecore.RoyalBorough.SharedSource.Rules.Actions 4 { 5 public class SetContextDeviceToSpecificDevice<T> : Sitecore.Rules.Actions.RuleAction<T> where T : Sitecore.Rules.RuleContext 6 { 7 public string DeviceID 8 { 9 get; 10 set; 11 } 12 13 public override void Apply(T ruleContext) 14 { 15 Sitecore.Context.Device = new DeviceItem(ruleContext.Item.Database.GetItem(this.DeviceID)); 16 ruleContext.Abort(); 17 } 18 } 19 }

Now we need to setup the rule, under the Rules folder create a new item and call it “Set Context Device To Mobile for Mobile Devices”

sitecore_contextdevice_rule_editsetcontextdevicetomobileformobiledevices

That’s it, make sure the assembly –for me it’s Sitecore.RoyalBorough.SharedSource.dll-  is in the sitecore bin folder, and you can start test it right away. If all goes well, you should see different view rendered when you access the site through Mobile devices and from PC

update Jan 17, 2012

If we’re to use Sitecore 6.1, we have a problem when we try to change the context device, the layout does not get updated it’s still uses the old layout and not the mobile layout, as a result when the mobile device access our page the rendered layout is all messed up. This is fixed on Sitecore 6.4, for 6.1 here’s the workaround

Create a new LayoutResolver class

1 namespace Sitecore.Royalborough.SharedSource.Pipelines.HttpRequest 2 { 3 public class LayoutResolver : Sitecore.Pipelines.HttpRequest.LayoutResolver 4 { 5 public override void Process(Sitecore.Pipelines.HttpRequest.HttpRequestArgs args) 6 { 7 if (Sitecore.Context.Item != null 8 && Sitecore.Context.Device != null 9 && Sitecore.Context.Item.Visualization.GetLayout(Sitecore.Context.Device) != null) 10 { 11 Sitecore.Data.Items.LayoutItem layout = 12 Sitecore.Context.Item.Visualization.GetLayout(Sitecore.Context.Device); 13 Sitecore.Context.Page.FilePath = layout.FilePath; 14 return; 15 } 16 17 base.Process(args); 18 } 19 } 20 }

Edit the web.config and replace Sitecore LayoutResolver with our own

1 <!--<processor type="Sitecore.Pipelines.HttpRequest.LayoutResolver, Sitecore.Kernel" />--> 2 <processor type="Sitecore.RoyalBorough.Pipeline.HttpRequest.LayoutResolver, Sitecore.RoyalBorough.SharedSource" />

Now things should work as expected in Sitecore 6.1

Advertisements

2 thoughts on “Change context device based on user agent with Sitecore Rule Engine

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s