Updating a SharePoint master page via a solution (WSP)

In my last blog post, I wrote how to deploy a SharePoint theme using Features and a solution package.  As promised in that post, here is how to update an already deployed master page.

There are several ways to update a master page in SharePoint.  You could upload a new version to the master page gallery, or you could upload a new master page to the gallery, and then set the site to use this new page.  Manually uploading your master page to the master page gallery might be the best option, depending on your environment.  For my client, I did these steps in code, which is what they preferred.

editmenu-790590(Image courtesy of: http://www.joiningdots.net/blog/2007/08/sharepoint-and-quick-launch.html )

Before you decide which method you need to use, take a look at your existing pages.  Are they using the SharePoint dynamic token or the static token for the master page reference?  The wha, huh?

SO, there are four ways to tell an .aspx page hosted in SharePoint which master page it should use:

  • ~masterurl/default.master” – tells the page to use the default.master property of the site
  • ~masterurl/custom.master” – tells the page to use the custom.master property of the site
  • ~site/default.master” – tells the page to use the file named “default.master” in the site’s master page gallery
  • ~sitecollection/default.master” – tells the page to use the file named “default.master” in the site collection’s master page gallery

For more information about these tokens, take a look at this article on MSDN.

Once you determine which token your existing pages are pointed to, then you know which file you need to update.  So, if the ~masterurl tokens are used, then you upload a new master page, either replacing the existing one or adding another one to the gallery.  If you’ve uploaded a new file with a new name, you’ll just need to set it as the master page either through the UI (MOSS only) or through code (MOSS or WSS Feature receiver code – or using SharePoint Designer).

If the ~site or ~sitecollection tokens were used, then you’re limited to either replacing the existing master page, or editing all of your existing pages to point to another master page.  In most cases, it probably makes sense to just replace the master page.

For my project, I’m working with WSS and the existing pages are set to the ~sitecollection token.  Based on this, I decided to just upload a new version of the existing master page (and not modify the dozens of existing pages).

Also, since my client prefers Features and solutions, I created a master page Feature and a corresponding Feature Receiver.  For information on creating the elements and feature files, check out this post: http://sharepointmagazine.net/technical/development/deploying-the-master-page .

This works fine, unless you are overwriting an existing master page, which was my case.  You’ll run into errors because the master page file needs to be checked out, replaced, and then checked in.  I wrote code in my Feature Activated event handler to accomplish these steps.

Here are the steps necessary in code:

  1. Get the file name from the elements file of the Feature
  2. Check out the file from the master page gallery
  3. Upload the file to the master page gallery
  4. Check in the file to the master page gallery

Here’s the code in my Feature Receiver:

   1:          public override void FeatureActivated(SPFeatureReceiverProperties properties)
   2:          {
   3:              try
   4:              {
   6:                  SPElementDefinitionCollection col = properties.Definition.GetElementDefinitions(System.Globalization.CultureInfo.CurrentCulture);
   8:                  using (SPWeb curweb = GetCurWeb(properties))
   9:                  {
  10:                      foreach (SPElementDefinition ele in col)
  11:                      {
  12:                          if (string.Compare(ele.ElementType, "Module", true) == 0)
  13:                          {
  14:                              //  <Module Name="DefaultMasterPage" List="116" Url="_catalogs/masterpage" RootWebOnly="FALSE">
  15:                              //    <File Url="myMaster.master" Type="GhostableInLibrary" IgnoreIfAlreadyExists="TRUE" 
  16:                              //      Path="MasterPages/myMaster.master" />
  17:                              //  </Module>
  18:                              string Url = ele.XmlDefinition.Attributes["Url"].Value;
  19:                              foreach (System.Xml.XmlNode file in ele.XmlDefinition.ChildNodes)
  20:                              {
  21:                                  string Url2 = file.Attributes["Url"].Value;
  22:                                  string Path = file.Attributes["Path"].Value;
  23:                                  string fileType = file.Attributes["Type"].Value;
  25:                                  if (string.Compare(fileType, "GhostableInLibrary", true) == 0)
  26:                                  {
  27:                                      //Check out file in document library
  28:                                      SPFile existingFile = curweb.GetFile(Url + "/" + Url2);
  30:                                      if (existingFile != null)
  31:                                      {
  32:                                          if (existingFile.CheckOutStatus != SPFile.SPCheckOutStatus.None)
  33:                                          {
  34:                                              throw new Exception("The master page file is already checked out.  Please make sure the master page file is checked in, before activating this feature.");
  35:                                          }
  36:                                          else
  37:                                          {
  38:                                              existingFile.CheckOut();
  39:                                              existingFile.Update();
  40:                                          }
  41:                                      }
  43:                                      //Upload file to document library
  44:                                      string filePath = System.IO.Path.Combine(properties.Definition.RootDirectory, Path);
  45:                                      string fileName = System.IO.Path.GetFileName(filePath);
  46:                                      char slash = Convert.ToChar("/");
  47:                                      string[] folders = existingFile.ParentFolder.Url.Split(slash);
  49:                                      if (folders.Length > 2)
  50:                                      {
  51:                                          Logger.logMessage("More than two folders were detected in the library path for the master page.  Only two are supported.",
  52:                                               Logger.LogEntryType.Information); //custom logging component
  53:                                      }
  55:                                      SPFolder myLibrary = curweb.Folders[folders[0]].SubFolders[folders[1]];
  57:                                      FileStream fs = File.OpenRead(filePath);
  59:                                      SPFile newFile = myLibrary.Files.Add(fileName, fs, true);
  61:                                      myLibrary.Update();
  62:                                      newFile.CheckIn("Updated by Feature", SPCheckinType.MajorCheckIn);
  63:                                      newFile.Update();
  64:                                  }
  65:                              }
  66:                          }
  67:                      }
  68:                  }
  69:              }
  70:              catch (Exception ex)
  71:              {
  72:                  string msg = "Error occurred during feature activation";
  73:                  Logger.logException(ex, msg, "");
  74:              }
  76:          }
  78:          /// <summary>
  79:          /// Using a Feature's properties, get a reference to the Current Web
  80:          /// </summary>
  81:          /// <param name="properties"></param>
  82:          public SPWeb GetCurWeb(SPFeatureReceiverProperties properties)
  83:          {
  84:              SPWeb curweb;
  86:              //Check if the parent of the web is a site or a web
  87:              if (properties != null && properties.Feature.Parent.GetType().ToString() == "Microsoft.SharePoint.SPWeb")
  88:              {
  90:                  //Get web from parent
  91:                  curweb = (SPWeb)properties.Feature.Parent;
  93:              }
  94:              else
  95:              {
  96:                  //Get web from Site
  97:                  using (SPSite cursite = (SPSite)properties.Feature.Parent)
  98:                  {
  99:                      curweb = (SPWeb)cursite.OpenWeb();
 100:                  }
 101:              }
 103:              return curweb;
 104:          }

This did the trick.  It allowed me to update my existing master page, through an easily repeatable process (which is great when you are working with more than one environment and what to do things like TEST it!).  I did run into what I would classify as a strange issue with one of my subsites, but that’s the topic for another blog post.

Print | posted @ Wednesday, March 24, 2010 10:43 PM