Thursday 12 July 2012

Mobile MasterPage Swap

Have you ever wanted to swap your MasterPage? Let's say that you have a sexy Public Publishing Site, the kind with anonymous access, the kind that the out of the box SharePoint 2010 mobile solution proves to be quite useless for. Well have no fear cause I've got the solution you need right here...

Basically we are going to create a module that's going to temporary trick SharePoint into thinking the request from a mobile device is no different then any other request, then at the last second swap out our desktop MasterPage for a much more mobile friendly one.

So to get started fire up VS 2010 and build an httpModule... no clue how to do that? that's OK it's actually rather trivial and I'm sure you could find countless examples online, but hey your already here so open up and let the airplane come in for a landing.

With your SharePoint project open add a solution folder called HttpModules; within your newly created folder add a new C# Class Library Project and name it MasterPageSwap.


Now with your class library added you're going to want to give the class.cs file a much more descriptive name, I'm going with MobileMasterSwap.cs; makes sense to me, name yours whatever you want just make note of your namespace, you're going to need it later when you register the Module.


Next you have Four tasks:
  • Add a reference to System.SharePoint
  • Add a reference to System.SharePoint.Publishing
  • Add a reference to System.Web
  • Inherit from IHttpModule
using System;
using System.Web;
 
namespace MasterPageSwap
{
    public class MobileMasterSwap : IHttpModule
    {
    }
} 
 
with that ready you're going to have to create the following functions
  • Init: extent your context events
  • BeginRequest: get the current browser capabilities, swap them with fake ones
  • AuthenticateRequest: swap in the real browser capabilities
  • PreRequestHandlerExecute: swap in your mobile MasterPage, and add the page_preInit event for site pages
  • page_PreInit: handle any SitePages you want redirected
  • dispose: clean up any loose ends, we'll leave it blank
Once those are complete you also have to create a custom class that inherits from HttpBrowserCapablities and overwrites the isMobileDevice property to false. With all of the above complete you should have something along the lines of.
using System;
using System.Web;
using System.Web.UI;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint;
 
namespace MasterPageSwap
{
    public class MobileMasterSwap : IHttpModule
    {
        private HttpBrowserCapabilities bc;
        private bool isMobile;
        private const string mobileMasterURL = "/_catalogs/masterpage/Mobile.master";
 
        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(cxt_BeginRequest);
            context.AuthenticateRequest += new EventHandler(cxt_AuthenticateRequest);
            context.PreRequestHandlerExecute += new EventHandler(cxt_PreRequestHandlerExecute);
        }
 
        void cxt_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = sender as HttpApplication;
 
            bc = application.Request.Browser;
            isMobile = bc.IsMobileDevice;
            application.Request.Browser = new TempBrowserCapabilities(bc);
        }
 
        void cxt_AuthenticateRequest(object sender, EventArgs e)
        {
            HttpApplication application = sender as HttpApplication;
            application.Request.Browser = bc;
        }
 
        void cxt_PreRequestHandlerExecute(object sender, EventArgs e)
        {
            Page page = HttpContext.Current.CurrentHandler as Page;
 
            if (isMobile && (page is PublishingLayoutPage || page is TemplateRedirectionPage))
            {
                SPContext.Current.Web.CustomMasterUrl = mobileMasterURL;
                page.PreInit += new EventHandler(page_PreInit);
            }
        }
 
        void page_PreInit(object sender, EventArgs e)
        {
            PublishingLayoutPage page = sender as PublishingLayoutPage;
            //make sure that all cases are in lower case.
            switch (page.Request.Url.AbsolutePath.ToString().ToLower())
            {
                case "/pages/home.aspx":
                    page.Server.Transfer("/Pages/MobileHomePage.aspx");
                    break;
            }
 
            page.Dispose();
        }
    }
 
    public class TempBrowserCapabilities : HttpBrowserCapabilities
    {
        public override bool IsMobileDevice
        {
            get { return false; }
        }
    }
}

Now with your http module complete you have to build the project: right click and hit build and now add it to your GAC (Global Assembly Cache); you're GAC should be located at C:\Windows\assembly\ once there you should see something along the lines of:

the complied dll of your http module resides in the debug folder of your project: to get there, in your solution explorer hit the show all files folder and expand  "YourProjectName-> bin -> debug" once you're at debug, right click on it and click "Open folder in windows explorer".
with your debug folder open, just drag and drop the .dll file into the GAC.
with you're dll added to the GAC the next thing you have to do is register the module in your webconfig file, now technically you're never suppose to manually edit your webconfig. So you probably shouldn't do it like this, but make sure it gets in there.


<modules runAllManagedModulesForAllRequests="true">
      <remove name="AnonymousIdentification" />
      <remove name="FileAuthorization" />
      <remove name="Profile" />
      <remove name="WebDAVModule" />
      <remove name="Session" />
      <add name="CustomhttpModule" type="SwitchMasterPage.CustomhttpModule,  SwitchMasterPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=08fec9e36d8f298a" />
      <add name="SPRequestModule" preCondition="integratedMode" type="Microsoft.SharePoint.ApplicationRuntime.SPRequestModule, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      <add name="ScriptModule" preCondition="integratedMode" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      <add name="SharePoint14Module" preCondition="integratedMode" />
      <add name="StateServiceModule" type="Microsoft.Office.Server.Administration.StateModule, Microsoft.Office.Server, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      <add name="RSRedirectModule" type="Microsoft.ReportingServices.SharePoint.Soap.RSRedirectModule, RSSharePointSoapProxy, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" />
      <add name="PublishingHttpModule" type="Microsoft.SharePoint.Publishing.PublishingHttpModule, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    </modules>

now make sure you add your module before the SPRequestModule, once all that's done save the changes to your web config and do an iisreset.