Tuesday, November 18, 2008

How To Attach Custom Master Pages to SharePoint Application Pages

As you all ready know SharePoint facilitates custom branding through master pages. it is very easy for you to change the look and feel of a site just by changing the site master page and system master page. but, unfortunately if you are in position where you have to apply custom master page to an out-of-the-box application page, such as _layouts/Upload.aspx, _layouts/CreatePage.aspx or a any other custom application page you are in a bit of a difficult situation. simply, because SharePoint user interface does not provide this functionality. SharePoint simply apply Application.master to all of the application pages. one way of by passing this problem is that changing the Application.master which is not recommended at all. simply because, this may get overridden during system upgrades and in the other hand you don't have a control over which application pages you want to be attached with custom master page.

So, how do we apply custom branding to application pages? one simple solution is to intercept page requests that are generated from application pages and change master page property before the page is rendered. this is where System.Web.IHttpModule comes in to play.

First of all, I implement IHttpModule interface and implement two interface methods Init() and Dispose() as follows;

      
private String pagesToBeProcessed = "_layouts/Upload.aspx,_layouts/CreatePage.aspx";
private String masterPageName = "mycustom.master";
 
public void Init(HttpApplication context)
{
 context.PreRequestHandlerExecute += new EventHandler(Context_PreRequestHandlerExecute);
 //get pages to be processes
 if (!String.IsNullOrEmpty(ConfigurationSettings.AppSettings["Application.Pages"]))
{
   pagesToBeProcessed = ConfigurationSettings.AppSettings["Application.Pages"].ToString();
 }
 
 //get master page
 if (!String.IsNullOrEmpty(ConfigurationSettings.AppSettings["Application.MasterPage"]))
{
    masterPageName = ConfigurationSettings.AppSettings["Application.MasterPage"].ToString();
 }
}
 
public void Dispose()
{
}

After this, we have to implement Context_PreRequestHandlerExecute and Page_PreInit methods as follows;

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)
{
   try
   {
     Page page = sender as Page;
     ChangeMasterPage(page);
   }
   catch (Exception ex)
   {
      //Handle Exception
   }
}

This where all the processing is happening. in the method, the page request is intercepted and if the page is from the list of pages that needs to be attached with custom master page then replace the master page url of the page to the custom master page.

private void ChangeMasterPage(Page page)
{
  if (page != null)
  {
    String[] tokens = page.Request.Url.GetLeftPart(UriPartial.Path).ToString().Split('/');
    /* get hold of the page name of the request */
    String pageName = tokens[tokens.Length - 1];
    if (pagesToBeProcessed.Contains("_layouts/"+pageName))
    {
      // Is there a master page defined?
      if (page.MasterPageFile != null)
      {
        // only replace the application.master file
        if (page.MasterPageFile.Contains("application.master"))
        {
           try
           {
              String url = "~" + SPContext.Current.Site.ServerRelativeUrl + "/_catalogs/masterpage/" + masterPageName;
              page.MasterPageFile = url;
           }
catch(Exception ex)
           {
             //don't do any thing
           }
        }
      }
    }
 
  }
}

Finally, sign, build the project and drop the assembly file to GAC. after placing the assembly in the GAC do an iisreset.

Now, we have a IHttpModule that can trap page requests and change master page reference. next, this module has to be registered in web.config.

Register the code module as safe control by adding a <SafeControl> entry under <SafeControls>.

Register the IHttpModule as follows;

<httpModules>
        .
        .
        .
        .
 <add name="MasterPageHttpModule" type="[assembly name], [namespace], Version=[version number], Culture=neutral, PublicKeyToken=[public key token]" />
<httpModules>

replace assembly name, namespace and public key token accordingly.

as the very last step, you may add two AppSettings values in to web.config that defines the custom master page name and the page names that need to be attached with custom master page. by default the module will change the master page urls of _layouts/Upload.aspx, _layouts/CreatePage.aspx.

After implementing the module, next time your application pages will be rendered with your custom master page.

Friday, November 14, 2008

How to Customize Quick Launch Group Names

HTML clipboard

Recently my client asked me to come up with a solution to display groups related only to the current web site in the Quick Launch Bar. The first thing came in to my mind was SPWeb.Properties["vti_associategroups"]. So, I decided to write a 'Web' scoped feature to modify the list of group names that go in to Quick Launch Bar. When activated, the feature will display the group names belong to the web site and when deactivated group names will be reverted back to its original values.
First, I implemented FeatureActivated method as follows;

public override void FeatureActivated(SPFeatureReceiverProperties properties)

{

SetQLGroups(properties);

}

My strategy here is to loop through all the group names of the sub site that feature is being activated and if the group name contains the site name then it should go in to quick launch bar. Therefor I store the group ID in a List for later use.

private void SetQLGroups(SPFeatureReceiverProperties properties)

{

SPWeb spWeb = null;

SPSite spSite = null;

Object oParent = properties.Feature.Parent;

SPFeature spFeature = properties.Feature;

List<String> groupIDs = new List<string>();

.

.

.

if (spWeb != null)

{

groupIDs.Clear();

/* loop through the groups to find groups having web site name as a part of group's name*/

foreach (SPGroup group in spWeb.Groups)

{

if (group.Name.Contains(spWeb.Name))

{

groupIDs.Add(group.ID.ToString());

}

}

/* this will be used for feature deactivation. when the feature

* is deactivated we need to revert the group Ids back

*/

spWeb.Properties.Add("vti_associategroups_original",

spWeb.Properties["vti_associategroups"]);

spWeb.Properties["vti_associategroups"] = String.Join(";",

groupIDs.ToArray());

spWeb.Properties.Update();

spWeb.Update();

}

Now, lets look what happens when the Feature is Deactivated.

Here, I simply replace SPWeb.Properties["vti_associategroups"] with the value of SPWeb.Property["vti_associategroups_original"] that I saved before during the future activation.

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

{

UnSetQLGroups(properties);

}

private void SetQLGroups(SPFeatureReceiverProperties properties)

{

SPWeb spWeb = null;

SPSite spSite = null;

Object oParent = properties.Feature.Parent;

SPFeature spFeature = properties.Feature;

List<String> groupIDs = new List<string>();

.

.

.

if (spWeb != null)

{

spWeb.Properties["vti_associategroups"] = spWeb.Properties["vti_associategroups_original"];

spWeb.Properties.Update();

spWeb.Update();

}

}

The next step is, you have to sign the code and get the version information including the public key token for future usage.

Now, the feature receiver code in place, the next step is to define feature.xml and element.xml.

I have created the feature.xml as follows;

xml version="1.0" encoding="utf-8"?>

<Feature Id="GUID"

Title="QuickLaunchGroupsFeature"

Description=""

Version="12.0.0.0"

Hidden="FALSE"

Scope="Web"

DefaultResourceFile="core"

ReceiverAssembly="QuickLaunchGroups, Version=1.0.0.0, Culture=neutral, PublicKeyToken=25be03d338bc65ac"

ReceiverClass="QuickLaunchGroups.QuickLaunchGroupsFeatureReceiver"

xmlns="http://schemas.microsoft.com/sharepoint/">

<ElementManifests>

<ElementManifest Location="elements.xml"/>

ElementManifests>

Feature>

Next, define the default element.xml file as follows;

xml version="1.0" encoding="utf-8" ?>

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

Elements>

Now, you can just go ahead install the feature in the system and activate in the web site.Once you activate the feature on a site, quick launch bar will be populated with the group names belong to the site.

To revert the group names to the default, just de-activate the feature.

Welcome to Bhakthi's Blog

It has been a while that I have decided that I should start my own blog so that I can contribute to the community. I have been in the industry for more than 8 years and I am surprised why I have not done this before.

so, finally I got my blog.

My blog will focus primarily on SharePoint work I am doing and also some Java related post time to time. I have been a Java developer for about four years in early days of my career. Still I have a part of my heart open to Java and related technologies.

Once again, welcome to my blog and hope to see you soon with a series of articles on 'How To......'s.

Rgds,
Bhakthi