Friday, October 30, 2009

SharePoint: Dynamically change master page

SharePoint branding is possible with either modification of default master or modification of default CSS. When we develop asp.net application we may need to change master page dynamically based on some criterion. For example in your site you have three groups of users: anonymous, authenticated normal user and site admin(s). Now based on the user group you need to change the site layout. So this requirement can easily be met with changing master page dynamically based on user’s login status.

How to change Master Page in asp.net?

SharePoint is an asp.net application developer by Microsoft. So let’s see how we can change master page in an asp.net application. If you develop an asp.net application then you can easily change the master page dynamically by changing the master page in PreInit event. You can check user’s login status in every page’s PreInit event. But if you have hundreds of pages then you don’t want to write the same code again and again in every page. So in the case the one approach might be to create a class which will inherit from Page class. Say the class is MyBasePage. In MyBasePage’s Page_PreInit event you can change the master page based on condition. The class is shown below:

 

public class MyBasePage : System.Web.UI.Page

{

    protected void Page_PreInit(object sender, EventArgs e)

    {

        if (CurrentUser.UserType == UserType.Admin)

        {

            MasterPageFile = "~/MasterPages/AdminMaster.master";

        }

        else if (CurrentUser.UserType == UserType.NormalAuthenitcated)

        {

            MasterPageFile = "~/MasterPages/NormalAuthenticatedMaster.master";

        }

        else

        {

            MasterPageFile = "~/MasterPages/AnonymouskMaster.master";

        }

    }

}

 

Now every web page can inherit from MyBasePage. To do so you need to modify web page’s code behind file to change the inheritance from System.Web.UI.Page to MyBasePage as shown below.

public partial class HomePage : MyBasePage

{

      protected void Page_Load(object sender, EventArgs e)

      {

 

      }

}

 

How to change Master Page in SharePoint?

Knowing the concept of “Change master page in PreInit event” you may think I’m done. Wait! Don’t stop reading. I have not started yet for SharePoint. SharePoint has its own pages which have installed as part of the SharePoint installation. Those pages’ code behind is complied in dll. How you are going to modify code in those pages’ code behind file? So you can’t override PreInit event as it was possible in asp.net application as you don’t have source code for SharePoint pages. Don’t give up. There’s a workaround where’s a problem. We’ll use HttpModule to hook our code in SharePoint Pages’ PreInit event. But first of all let explain in brief how Asp.net pipeline processing works.

 

Asp.net Pipeline Processing

Asp.net request processing is based on pipeline model as shown in figure 1. Each request is gone through a number of modules where each module can modify the request. Finally the request is passed to a Handler which processes the final request.

 

image

Figure 1: Asp.net Request/response pipeline.

Usually each request is handler by multiple modules and one handler. In the above request/response pipeline we don’t want to process the final request but want to modify the request to add a PreInit event handler for asp.net page.

 

Develop a HttpModule to attach PreInit event handler

To develop a custom HttpModule we need to write a class which will inherit from System.Web.IHttpModule . IHttpModule defines two methods to implement: Init and Dispose. Init method will be invoked when the module will be initialized and dispose method will be invoked when the module will be disposed.

public class DynamicMasterPageModule : IHttpModule

{

    public void Dispose()

    {

 

    }

 

    public void Init(HttpApplication context)

    {

        context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);

    }

 

    void context_PreRequestHandlerExecute(object sender, EventArgs e)

    {

        Page page = HttpContext.Current.CurrentHandler as Page;

        if (page != null)

        {

            page.PreInit += new EventHandler(page_PreInit);

        }

    }

 

    void page_PreInit(object sender, EventArgs e)

    {

 

    }

}

As shown in the above code block, In Init method I have hooked an event handler for PreRequestHandlerExecute event. The PreRequestHandlerExecute fires just before Asp.net starts executing a page. In the context_PreRequestHandlerExecute method we can get the current executing page by accessing the CurrentHandler object. If current handler is for a page, then the page variable will not be null. In case of other request (say web service request) the CurrentHandler casting to page object will return null. Now we can modify the master page in the page_PreInit method. So you are thinking it’s done. Wait! There’s more to make it working in SharePoint. Let’s take a tour on how SharePoint guides us to change master page.

 

SharePoint Master Page

SharePoint has a default master page. But if you want you can develop your own master page for SharePoint in SharePoint designer. To refer a master page from content page in SharePoint, there are four tokes available. In normal asp.net application we set the master page from content page by setting the Page.MasterPageFile property of the page to the location of the master page as shown below:

Page.MasterPageFile="path to master file";

But in SharePoint, this MasterPageFile property is used differently. It may be or may be not the location of the master page file in SharePoint. In SharePoint MasterPagefFile can take four predefined values (called tokens). Based on the token values SharePoint determine which master page to use for the page (default, custom, site collection etc). The four tokes fall into two categories: dynamic and static. Static token used by itself to define the master page location. The two static token is described below:

  • Token "~site/MasterPageName.master"

In this token the ~site refer to site relative url and MasterPageName.master is the name of the master page. So if you provide the token “~site/masterpages/MyMaster.master” and your site url is like http://sitecollection/site1 then the master page url will be http://sitecollection/site1/masterpages/MyMaster.master.

  • Token "~sitecollection/MasterPageName.master"

Here the token ~sitecollection points to the site collection url. So if you site collection url is http://sitecolleciton then the master page location will be http://sitecolection/MasterPageName.master.

Dynamic token doesn’t provide the master page location rather it points where to get the master page file location. Dynamic tokens are exact values and you can’t change it like static token. The two dynamic token are described below:

  1. Token "~masterurl/default.master"

When you’ll set the value like Page.MasterPageFile= "~masterurl/default.master", SharePoint will use the master page provided in SPWeb.MasterUrl property. So if you set master page file to this token you need to ensure that you have provided the master page location to SPWeb.MasterUrl property. FYI, the dynamic token is not changeable and you need to provide exact “~masterurl/default.master”.

  • Token "~masterurl/custom.master"

When you will use this token then SharePoint will use SPWeb.CustomMasterPageUrl property. So along with setting this token, you also need to set the SPWeb.CustomMasterPageUrl property. FYI, the dynamic token is not changeable and you need to provide exact “~masterurl/default.master”.

 

Develop Custom Master Page in SharePoint

1. Create a custom master page

For Developing SharePoint master page we can use SharePoint designer. The easiest way to create a master page is to copy an existing master page in SharePoint Designer and past it and then modify it.

2. Make the master page you developer as Custom master page

But once you have developed a master page in SharePoint it doesn’t become custom master page automatically. You can set your master page as custom one from SharePoint Designer. Open you site in SharePoint Designer and then navigate to _catalogos > Masterpage and then select the your master page. Finally right click on the master page to bring the context menu and click “Set as Custom Master page” from the context menu. To following figure shows how to set a master page as custom one from SharePoint designer.

image

Figure 2: How to set custom master page in SharePoint Designer.

3. User your custom Master Page in PreInit event.

In the following code block I have read the page number from web.cofing and if I find page no to 1 then I use the custom1.master, for page number 2 I use custom2.master. Either I use default master page. In real life scenario you will change the master page based on some other criterion.

void page_PreInit(object sender, EventArgs e)

    {

        Page page = sender as Page;

        string pageNo = ConfigurationManager.AppSettings["MasterPageNo"];

 

 

        if (page != null)

        {

            if (pageNo.Equals("1"))

            {

                page.MasterPageFile = "~masterurl/custom.master";

                if (SPContext.Current != null)

                {

                    SPContext.Current.Web.CustomMasterUrl = "/_catalogs/masterpage/custom1.master";

                }

            }

            else if (pageNo.Equals("2"))

            {

                page.MasterPageFile = "~masterurl/custom.master";

                if (SPContext.Current != null)

                {

                    SPContext.Current.Web.CustomMasterUrl = "/_catalogs/masterpage/custom2.master";

                }

 

            }

            else

            {

                page.MasterPageFile = "~masterurl/default.master";

                if (SPContext.Current != null)

                {

                    SPContext.Current.Web.MasterUrl = "/_catalogs/masterpage/default.master";

                }

            }

 

        }

 

In the code block above if you can just comment out the else section. I have just used this to show to you can also set the default master page. You can skip this code block as you don’t need to set the default master page as it’s already set by default.

Integrate the custom module with SharePoint site

Once you have developed the custom module you need to use it in SharePoint site. You can do so by adding an entry in the web.config file. Open the web.config file of the SharePoint web application (which is normally in c:\inetpub\wwwroot\wss\VirtualDirectories\Port) and add your http module name in the <httpModules> </httpModules> block.

27 comments:

  1. This is nice article. Thank you.
    I implemented this code, i have changed the code to display master page based on user. but it is not working.
    Please suggest me how to do

    ReplyDelete
  2. Have u made the code working without considering user? Try first if it works for a simple scenario as like in the post. If it works then try to integrate your per-user-change-masterpage logic.

    ReplyDelete
  3. I have implemented the code as mentioned in the post. but it is not working. i have deployed the below code as Httpmodule

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Web;
    using System.Web.UI;
    using Microsoft.SharePoint;
    using System.Configuration;

    namespace DynamicMasterPageModule
    {
    public class DynamicMasterPageModule : IHttpModule
    {
    public void Dispose()
    {
    }
    public void Init(HttpApplication context)
    {
    context.PreRequestHandlerExecute +=
    new EventHandler(context_PreRequestHandlerExecute);
    }
    void context_PreRequestHandlerExecute(object sender, EventArgs e)
    {
    Page page = HttpContext.Current.CurrentHandler as Page;
    if (page != null)
    {
    page.PreInit += new EventHandler(page_PreInit);
    }
    }

    void page_PreInit(object sender, EventArgs e)
    {
    Page page = sender as Page;

    string pageNo = System.Configuration.ConfigurationSettings.AppSettings["MasterPageNo"];

    if (page != null)
    {
    if (pageNo.Equals("1"))
    {
    page.MasterPageFile = "~masterurl/default.master";
    if (SPContext.Current != null)
    {
    SPContext.Current.Web.CustomMasterUrl = "/_catalogs/masterpage/defaultnew.master";
    }
    }
    else if (pageNo.Equals("2"))
    {
    page.MasterPageFile = "~masterurl/default.master";
    if (SPContext.Current != null)
    {
    SPContext.Current.Web.CustomMasterUrl = "/_catalogs/masterpage/defaultnewUSA.master";
    }
    }

    }
    }
    }
    }

    Thanks,
    Murali.

    ReplyDelete
  4. Had you put the module in web.config file as described in section 'Integrate the custom module with SharePoint site'. You can debug to check if the module is working or not by attaching w3p process.

    ReplyDelete
  5. Thanks for your reply.
    Yes,I added the module in web.config.
    I'll debug the code and inform you.

    Thanks,
    Murali.

    ReplyDelete
  6. i debug the code but event handler is not at all executed.

    void page_PreInit(object sender, EventArgs e)

    Thanks
    Murali

    ReplyDelete
  7. Can you debug the HttpModule? Please make sure the module is integrated.

    ReplyDelete
  8. I have copied the dll in bin folder and added in web.config.

    ReplyDelete
  9. You need to copy dll in gac and put an entry in safecontrols in web.config file. The deployment is similar to any sharepoint deployment.

    ReplyDelete
  10. I copied the dll in GAC. but still it is not working.

    Steps done:
    1.Created HTTP module as Post
    2.Created strong name and put the dll in GAC.
    3.Added the dll in SAFE Control in web.config.
    4.Added the dll in in web.config
    5.added in appsetting in web.config.

    but still is not working. Please help me.

    ReplyDelete
  11. I copied the dll in GAC. but still it is not working.

    Steps done:
    1.Created HTTP module as Post
    2.Created strong name and put the dll in GAC.
    3.Added the dll in SAFE Control in web.config.
    4.Added the dll in httpmodules in web.config
    5.added in appsetting in web.config.

    but still is not working. Please help me.

    ReplyDelete
  12. add key="MasterPageNo" value="2" in web.config

    ReplyDelete
  13. 5.added key="MasterPageNo" value="2" in appsettings web.config

    ReplyDelete
  14. I did not get any reply from you since my last comments. Please help me.

    ReplyDelete
  15. I'm not sure how much and how I can help. But you need to find out and debug your self. I think you are missing some basic point. I'll suggest you to do the following:
    1. Create a basic http module and test it works with SharePoint.
    2.If you find way how to develop and integrate with SharePoint, then integrate the Masterpage code and debug it.

    ReplyDelete
  16. I'll do as you said. Thank you for very much for giving suggestions.

    Thanks,
    Murali.

    ReplyDelete
  17. I have done the functionality displaying links dynamically in master page through user control.

    1.Created class library which contains the logic for identifying user.
    2.Created user control uses the class library and custom links.
    3.deployed class library in GAC.
    4.Deployed user control in contenttemplate folder.
    5.used the control in master page.
    it works..thanks

    ReplyDelete
  18. I have problems with this statement:

    Page page = HttpContext.Current.CurrentHandler as Page;
    if (page != null)...

    as the CurrentHandler only seems to ever be of type 'SPHttpHandler'

    ReplyDelete
  19. further investigation and...

    'page_PreInit' is being added with
    page.PreInit += new EventHandler(page_PreInit);

    but is never called, I'm at a loss as to why?

    ReplyDelete
  20. Are you getting the page from CurrentHandler? The master page change trick worked for me for application pages but I never tried for web part pages. Are u using webpart page or application page?

    ReplyDelete
  21. that's it - it works for the application page but not master page of a publishing site. Hmmm i'll have to try and work out why.

    ReplyDelete
  22. Hi,

    I tried the same thing on publishing site but page_preinit function is not getting called on any publishing page. But when i go to any library or list then this function gets a call and it applies the master page as well..

    Please let me know if anyone figured out this problem.

    Thanks

    ReplyDelete
  23. one more thing i am trying this on SharePoint 2010.

    ReplyDelete
  24. why if I go to others pages like "_layouts/user.aspx", it does't work?

    ReplyDelete
  25. @Bruno, I tried to show it for custom master page but not default master page. It's better to use sharepoint out of the box master page as default master page and you custom one as custom master page. However, if you set your custom master page as default as well as custom master page then it might work.

    ReplyDelete
  26. Solution works great with teamsites template but when I tried with puiblishing site didn't work. Do you know what I am doing wrong here?
    Thanks for your help

    ReplyDelete
  27. Hi , I have same problem with Publishing pages . Can anyone please help me if it worked for you?

    ReplyDelete