Tuesday, March 8, 2011

WURFL-based mobile device detection for Sitecore CMS

Mobile web browsing is so popular nowadays, that I was confused by the fact that there is no plug'n'play mobile detection solution for Sitecore and many people still use odd code like "if(userAgent.Contains('iPhone')...".
The best mobile device database is WURFL, and it has a great .NET API from 51 Degrees... so what are we waiting for? Let's create a new module!
The solution should be applicable to any Sitecore project,  and at the same time simple and customizable.
Here is the plan:
  • Use WURFL to detect device
  • Extend Device template (add additional fields for configuration)
  • Extend DeviceResolver class (to take make sure it takes additional fields into account)
  • Let the user override detected device
In this solution, I'll use WURFL wrapper from 51degrees - "Foundation" version is free and contains all required tools.

It can be easily integrated to the site by adding entries to config and adding the following code to global.asax:

public void Application_Start() {
    HttpCapabilitiesBase.BrowserCapabilitiesProvider = new FiftyOne.Foundation.Mobile.Detection.MobileCapabilitiesProvider();
  }

WURFL properties will be accessible using the following code:

HttpContext.Current.Request.Browser["%wurfl property name%"]

By default, Sitecore CMS uses only UserAgent and QueryString to resolve device, Device template contains the following fields:


We can add more field like "screen width", "is mobile device", but will it be customizable and extensible? Fortunately, we have Rules Engine, that can be easily configured and helps avoid additional code, that would handle situations like "if screen width is... else...".
Actually, after creating the initial prototype, I've found blog post with exactly the same idea - Using the Sitecore Rules Engine in a Custom Context: Setting the Context Device, it contains more info about Sitecore device resolving and a lot of useful links - definitely worth reading if you're working with devices.

So, let's add a field of type "Rules" to the template:


The name of the field is "Conditions' as it will contain conditions only, the action is setting Context.Device to the current item. The following code will be used to evaluate rules and find best-matching device:

DeviceItem[] all = database.Resources.Devices.GetAll();

            foreach (var device in all)
            {
                var ruleContext = new RuleContext();

                foreach (Rule<RuleContext> rule in RuleFactory.GetRules<RuleContext>(new[] { device.InnerItem }, "Conditions").Rules)
                {
                    if (rule.Condition != null)
                    {
                        RuleStack stack = new RuleStack();
                        rule.Condition.Evaluate(ruleContext, stack);
                        if (ruleContext.IsAborted)
                        {
                            continue;
                        }
                        if ((stack.Count != 0) && ((bool)stack.Pop()))
                        {
                            return device;
                        }
                    }
                }
            }

Now, let's create a set of conditions that will be used for determining appropriate device. The list is pretty small, but it can be easily extended:


Here are the few examples of how the conditions above can be combined:

1) Apple's iPhones

2) And tablets

3) More complex query

By the way, mobile device detection rules can be also used for conditional renderings. It is possible to provide amazing browsing experience to the visitors - show the nearest service center depending on brand, or suggest using mobile application if device is iOS / Android, etc. 
In order to configure Mobile Device Detector in your solution, you need to perform the following steps:

1) Install the module package. It contains template updates, conditions, class library, WURFL data file, etc.
2) Update Global.asax and Web.config according to instructions received during the installation.

The module was tested on Sitecore 6.4 (Update 1). Please make sure that your site Application Pool NET Framework Version is set to "4.0".

And one more thing about the solution. In order to make it possible to implement "switch to the normal website version"  button, I've added functionality(based on session-level cookies) for persisted device switching. If you switch to another device using "sc_device=%name of the device%" and add "&persisted=true" parameter, the device will remain switched until you reopen the browser.

The goal of this project is a complete mobile device detection solution, applicable to any Sitecore website. Please share your ideas on how to improve this module, I'll collect feedback and update it. Thanks in advance!

28 comments:

  1. Great post and I agree we need this. I like the approach and the generic nature of it fits Sitecore nicely.

    One concern could be performance - the WURFL data file is already massive and likely to grow fast - I admit I have not yet had time to actually try the module, but just had a notion that maybe there could be a performance issue - especially for high load websites, did you crunch any performance numbers?
    One could argue that while we see a lot of mobile enabled sites, probably quite a few (at least so far) require the ability to identify the exact device and so I was thinking that maybe the module could implement an additional more lightweight detection, that only detects the device group? The groups could re-use the WURFL list and I think at least for the time being that the ability to detect weather the device connected is a smartphone, pda or pad would be sufficient for many client solutions and if that would at the same time improve performance maybe that could be and option?

    I will try out the module when I get a chance, as mentioned I think we need it, so thumbs up for this :-)

    ReplyDelete
  2. Thanks for the Feedback!

    >did you crunch any performance numbers?
    There is a slight performance impact, that mostly affects startup time(adds roughly 2-3 seconds). Exact numbers can be found in this file: "~/App_Data/Log.txt" after you've installed and configured package. Actual device resolving takes just a few miliseconds.
    I'm going to perform complete performance testing soon, and publish results in this blog.

    >maybe the module could implement an additional more lightweight detection, that only detects the device group?
    I'll try to implement something like that after the performance testing. And here are the additional conditions, I'm planning to add to the module package:

    pointing_method
    device_os
    cookie_support
    ajax_support_javascript

    The most "general" conditions - "is mobile" and "is tablet" were added to the package. And it will be posible to differentiate "smart" devices by configuring a rule that checks "is mobile" property, and "pointin_method" or "device_os", etc.

    ReplyDelete
  3. Very Nice post, i Will have to try implementing this in a solution of mine. Great job :)

    Also, looking forward to Reading about the results of the performance tests :)

    ReplyDelete
  4. Not sure how this fits with the Sitecore recommendation by Mark Gregor to use Device Atlas:

    http://www.sitecore.net/Community/Best-Practice-Blogs/Mark-Gregor/Posts/2010/12/Powering-the-Mobile-Web-with-Sitecore.aspx

    Does anyone know? And is Device Atlas built into Sitecore?

    ReplyDelete
  5. >Does anyone know? And is Device Atlas built into Sitecore?

    This Shared Source module is using WURFL database, and is not related to Device Atlas.

    Device Atlas is a separate product, it is not built into Sitecore by default, and no licensing provided.

    ReplyDelete
  6. Standard WURFL .NET API is also available directly from the WURFL site:

    http://wurfl.sourceforge.net/dotNet/

    ReplyDelete
  7. What is the best way to provide an option for a user to switch to full site on a mobile device with this plugin?

    ReplyDelete
  8. You can use a link with the following query string:

    ?sc_device=%device name%&persisted=true

    Related code is at the

    "GetQueryStringDevice" method of "DeviceResolverHelper" class.

    ReplyDelete
  9. For the life of me, I can see to get this to work after I doublechecked my installation and am using the rule: "where the device is mobile" I tried using the iPhone rule above and it doesn't work either. But if I put "mobile" into the device's User Agent, it does pick it up. It looks like the rules are not working. Any suggestions would be appreciated.

    ReplyDelete
  10. >It looks like the rules are not working
    Try to compile the module in Debug mode, and attach to the process and see what's going on in the following class:
    Sitecore.SharedSource.MobileDeviceDetector.Rules.Conditions.IsMobileCondition

    does it get instantiated, is Request.Browser["is_wireless_device"] equals true, etc.

    ReplyDelete
  11. I tried the install on another Sitecore instance and it worked fine. After comparing the difference file by file, it turned out that my Global.asax was not updated like I thought it was. Now things are working fine. Thanks for your response back!

    ReplyDelete
  12. I'm getting the following error trying to install the package.

    System.Exception: Failed to add an item. Key: '/sitecore/system/Settings/Rules/Common/Conditions/Device Detection/Brand'. Reason: there's no template with the following ID '{F0D16EEE-3A05-4E43-A082-795A32B873C0}' at Sitecore.Install.Items.ItemInstaller.CreateLightweightItem(ItemReference item, XmlVersionParser parser) at Sitecore.Install.Items.ItemInstaller.GetVersionInstallMode(PackageEntry entry, ItemReference reference, XmlVersionParser parser, ItemInstallerContext context) at Sitecore.Install.Items.ItemInstaller.InstallEntry(PackageEntry entry) at Sitecore.Install.Items.ItemInstaller.Flush() at Sitecore.Install.Framework.SinkDispatcher.Flush() at Sitecore.Install.Utils.EntrySorter.Flush() at Sitecore.Install.Framework.EntryBuilder.Flush() at Sitecore.Install.Zip.PackageReader.Populate(ISink`1 sink) at Sitecore.Install.Utils.EntrySorter.Populate(ISink`1 sink) at Sitecore.Install.Installer.InstallPackage(String path, ISource`1 source, IProcessingContext context) at Sitecore.Shell.Applications.Install.Dialogs.InstallPackage.InstallPackageForm.AsyncHelper.b__0() at Sitecore.Shell.Applications.Install.Dialogs.InstallPackage.InstallPackageForm.AsyncHelper.CatchExceptions(ThreadStart start)

    What am I missing?

    ReplyDelete
  13. >I'm getting the following error trying to install the package.
    What version of Sitecore CMS are you using?

    ReplyDelete
  14. I'm using 6.3 version. Would this module fits my version. Please advise...

    ReplyDelete
  15. The module was tested on Sitecore 6.4 (Update 1). I think it should work on 6.3 as well, just make sure that your site Application Pool NET Framework Version is set to "4.0".

    ReplyDelete
  16. Look also apache mobile filter, is an opensource project. The main features are device detection, image rendering and mobile switcher. The mag supports several device repository such as wurfl, detectright, 51degrees.
    For more info: http://apachemobilefilter.org

    ReplyDelete
  17. It seems that WURFL has changed their licensing agreement and we may not be able to use the 51 degrees API to access the data anymore? 51 Degrees has their own free data, but the MobileDeviceDetector.dll seems to be looking for the WURFL one unless I missed a config somewhere.

    ReplyDelete
  18. Interesting... I think you still can use older version of WURFL xml database, as it was licensed under GPL.
    Anyway, thanks for informing me, I'll review the alternatives and try to release a new version of the module based on open-source database.

    ReplyDelete
  19. It looks like 51 Degrees offers their own XML file now. I think the only change you'd have to make is the xpath'ing. Maybe a config option to pick what datafile is being used (WURFL or 51 Degrees) and the path to that file?

    ReplyDelete
  20. OK...I see what they did. In v2.* of 51 Degrees' DLL, the data file is embedded. But on top of that, plugging it in doesn't work: They changed the property names. So now it's HttpContext.Current.Request.Browser["IsMobile"]. Not sure why they didn't make it backwards compatible.

    ReplyDelete
  21. Looks like a great module, has anyone been able to get this to work in Sitecore 6.3? I'm assuming that as .Net 4 Application Pool support only came in for 6.4 that the answer is No?

    ReplyDelete
  22. So at this point would this module only target an older version of the 51degrees/WURFL-based API and data set? And are there any potential licensing issues to deal with? I'm interested in using some of the tablet detection logic but it appears that's part of the premium data set in newer versions of 51degrees.

    ReplyDelete
  23. I don't see any issues in using older version of WURFL data files.
    New version of the module, based on open-source data is planned, but I have no ETA for it.

    ReplyDelete
  24. Sadly WURFL is no longer free for commercial deployments. :(

    ReplyDelete
  25. @Alexander Doroshenko

    Any idea on when/if you'll be releasing an update? If not, any way I can help assist with getting an update completed?

    ReplyDelete
    Replies
    1. Can you suggest any open-source mobile devices data repository with .NET API?

      Delete
  26. Am I doing something wrong or there's been some changes? I cannot run the following code:

    HttpCapabilitiesBase.BrowserCapabilitiesProvider =
    new FiftyOne.Foundation.Mobile.Detection.MobileCapabilitiesProvider();
    There's no BrowserCapabilitiesProvider property and there's no
    MobileCapabilitiesProvider method detected in in Visual Studio. What's wrong?

    ReplyDelete
  27. Can I use this module in Sitecore 6.5?

    ReplyDelete