How to change the default page of a SharePoint site using a Feature

I am currently working on a project where we plan on having thousands of teamsites.  In order to help the search process we had to make a few modifications to the default page, namely adding some meta tags that once crawled, we could surface as managed properties and use to enhance the search experience.  As we all know, modifying the out of the box files is a no-no so we were left with 2 options to consider.

The first option we considered was to create a custom site definition with our custom default.aspx page.  The second option was to create our custom default.aspx page and swap it out with the original one via a Feature.  After giving this much thought, we decided to go with the second, Feature based option.

If you want to read a good discussion on how/when/why to use a custom site definition, head on over to Joel Oleson's recent post on the subject and the conversations that followed; good stuff!

To start the process, I created a Feature called Custom Default Page.  This Feature contains the following 5 files, all of which are discussed in more detail below.

  • feature.xml
  • customDefault.xml
  • customDefault.aspx
  • customDefault.aspx.cs
  • CustomDefaultPageFeatureReceiver.cs

feature.xml

As we know all Features require at least one file and that file is feature.xml.  This file contains the definition for the Feature, including it's name, description, id, and other meta data.  It also contains references to supporting element files if they exist as well as a class and assembly if the Feature uses a receiver, which our does.  Our Feature is defined as follows:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <Feature
   3:    Id="featureGuid"
   4:    Title="Custom Default Page"
   5:    Description="This Feature contains a custom default page."
   6:    Version="1.0.0.0"
   7:    Scope="Web"
   8:    Hidden="TRUE"
   9:    DefaultResourceFile="core"
  10:    SolutionId="solutionGuid"
  11:    ReceiverAssembly="CustomDefaultPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=yourToken"
  12:    ReceiverClass="CustomDefaultPage.CustomDefaultPageFeatureReceiver"
  13:    xmlns="http://schemas.microsoft.com/sharepoint/">
  14:    <ElementManifests>
  15:      <ElementManifest Location="customDefault.xml" />
  16:      <ElementFile Location="customDefault.aspx" />
  17:    </ElementManifests>
  18:  </Feature>

All Features must have a unique identifier, specified by the Id attribute.  We then add a Title, Description, and Version.  This Feature is scoped to Web as any site at any level can use it.  I made my Feature hidden since I don't want it available on all sites.  In fact, the original purpose of this Feature in my specific case was that it be used on the top level site of a teamsite site collection only, such that we can index the home page of the site collection independently of its content and have it appear and be ranked in search results based on the custom meta information we added to it.  That may sound long winded but I plan on blogging that particular problem and solution at a later date.

We can see that the Feature is backed by an assembly and class that define its receivers.  Also, we see that the customDefault.aspx we are going to use is defined by an ElementFile element and the module that will provision that file is defined by the an ElementManifest element in the customDefault.xml file.

customDefault.xml

This file contains a module element that will provision our customDefault.aspx page to the root folder of the SharePoint site in which the Feature is activated.  It is defined as follows:

   1:  <?xml version="1.0" encoding="utf-8"?>
   2:  <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:    <Module Name="CustomDefault" Url="" RootWebOnly="TRUE">
   4:      <File Path="customDefault.aspx" Url="customDefault.aspx" IgnoreIfAlreadyExists="TRUE" />
   5:    </Module>
   6:  </Elements>

This is quite bare bones and simple.  The module specifies the local Path (in the feature folder), the Url on the SharePoint site (no folder = root folder) and to always replace it when the Feature is activated.

customDefault.aspx

I am not going to get into the details of what I placed into this file other than the fact that I grabbed a copy of the default.aspx file located at C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\SiteTemplates\sts\default.aspx and renamed it in my project.  I also created a backing class for it and consequently needed to changed the directive at the top of the file from this:

<%@ Page language="C#" MasterPageFile="~masterurl/default.master" Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage,Microsoft.SharePoint,Version=12.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %>

to something like this:

<%@ Page Language="C#" MasterPageFile="~masterurl/default.master" Inherits="CustomDefaultPage.CustomDefaultPage, CustomDefaultPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=yourToken" %>

Then, in my CustomDefaultPage.aspx.cs class, I can add code to do whatever I want on my custom default page, such as insert custom meta data as was the case for me.  This allows me to have complete control over the custom default page exactly like I would in a regular asp.net web application.

customDefault.aspx.cs

I won't go into the details of this file either, but it looks similar to this, with my logic inserted.

   1:  using System;
   2:  using System.Web.UI.HtmlControls;
   3:  using System.Web.UI.WebControls;
   4:   
   5:  using Microsoft.SharePoint;
   6:  using Microsoft.SharePoint.WebPartPages;
   7:   
   8:  namespace CustomDefaultPage
   9:  {
  10:      public class CustomDefaultPage : WebPartPage
  11:      {
  12:          /// <summary>
  13:          /// Raises the <see cref="E:System.Web.UI.Control.Load"></see> event.
  14:          /// </summary>
  15:          /// <param name="e">The <see cref="T:System.EventArgs"></see> object that contains the event data.</param>
  16:          protected override void OnLoad(EventArgs e)
  17:          {
  18:              // code removed for brevity
  19:          }
  20:   
  21:      }
  22:  }

CustomDefaultPageFeatureReceiver.cs

All of the magic happens when the Feature is activated AND when the Feature is deactivated. You may be wondering why the deactivation code is required.  Consider a hosted environment where the need to remove customization WITHOUT breaking SharePoint.  Depending on how your activation and deactivation code is written, it is possible to render the home page of your site inaccessible (I know since in the process of building this it happened to me).  This is very undesirable, so it is always a good idea to anticipate what may need to happen when you deactivate a Feature.  Some Features may not require any special logic when they are deactivated, but some most certainly do and I believe that this is a case when you should handle that.

The code for the Feature Receiver will look something like this:

   1:  using System;
   2:  using System.IO;
   3:  using System.Web.UI.WebControls.WebParts;
   4:  using System.Xml;
   5:   
   6:  using Microsoft.SharePoint;
   7:  using Microsoft.SharePoint.Utilities;
   8:  using Microsoft.SharePoint.WebPartPages;
   9:   
  10:  namespace CustomDefaultPage
  11:  {
  12:      public class CustomDefaultPageFeatureReceiver : SPFeatureReceiver
  13:      {
  14:          /// <summary>
  15:          /// Occurs after a Feature is activated.
  16:          /// </summary>
  17:          /// <param name="properties">An <see cref="T:Microsoft.SharePoint.SPFeatureReceiverProperties"></see> object that represents the properties of the event.</param>
  18:          public override void FeatureActivated(SPFeatureReceiverProperties properties)
  19:          {
  20:              if (properties != null)
  21:              {
  22:                  // get a reference to the web
  23:                  SPWeb web = properties.Feature.Parent as SPWeb;
  24:   
  25:                  // back up the original home page
  26:                  SPFile defaultPage = web.Files["default.aspx"];
  27:                  defaultPage.MoveTo("default-old.aspx");
  28:   
  29:                  // add components to the new custom default page here, if necessary
  30:   
  31:                  // move the new default page to default.aspx
  32:                  SPFile newDefaultPage = web.Files["CustomDefault.aspx"];
  33:                  newDefaultPage.MoveTo("default.aspx");
  34:   
  35:                  // update navigation, if necessary, here
  36:              }
  37:          }
  38:   
  39:          /// <summary>
  40:          /// Occurs when a Feature is deactivated.
  41:          /// </summary>
  42:          /// <param name="properties">An <see cref="T:Microsoft.SharePoint.SPFeatureReceiverProperties"></see> object that represents the properties of the event.</param>
  43:          public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
  44:          {
  45:              if (properties != null)
  46:              {
  47:                  // get a reference to the web
  48:                  SPWeb web = properties.Feature.Parent as SPWeb;
  49:   
  50:                  // delete the default page
  51:                  SPFile defaultPage = web.Files["default.aspx"];
  52:                  defaultPage.DeleteAllPersonalizationsAllUsers();
  53:                  defaultPage.Delete();
  54:   
  55:                  // restore the back up
  56:                  SPFile originalDefaultPage = web.Files["default-old.aspx"];
  57:                  originalDefaultPage.MoveTo("default.aspx");
  58:              }
  59:          }
  60:   
  61:          /// <summary>
  62:          /// Occurs after a Feature is installed.
  63:          /// </summary>
  64:          /// <param name="properties">An <see cref="T:Microsoft.SharePoint.SPFeatureReceiverProperties"></see> object that represents the properties of the event.</param>
  65:          public override void FeatureInstalled(SPFeatureReceiverProperties properties)
  66:          {
  67:              //throw new Exception("The method or operation is not implemented.");
  68:          }
  69:   
  70:          /// <summary>
  71:          /// Occurs when a Feature is uninstalled.
  72:          /// </summary>
  73:          /// <param name="properties">An <see cref="T:Microsoft.SharePoint.SPFeatureReceiverProperties"></see> object that represents the properties of the event.</param>
  74:          public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
  75:          {
  76:              //throw new Exception("The method or operation is not implemented.");
  77:          }
  78:   
  79:      }
  80:  }

I played around a lot with the MoveTo() and CopyTo() methods and this is the combination that worked 100% of the time.  I have found CopyTo() to be a little unpredictable but your mileage may vary.  The key takeaway here is to always anticipate your Features deactivation needs, especially in a hosted environment.  All hosting companies have an Service Level Agreement (SLA) to adhere to and if they determine that your customization is causing a problem, I would venture to bet that there is language in that agreement that allows them to deactivate you customizations to honor that agreement and if that were to happen, I would want my users experience to not be interrupted, at least not to the degree that an unavailable home page might produce, wouldn't you?

Published Wednesday, October 29, 2008 11:56 AM by J. Dan Attis
Filed under: ,

Comments

Wednesday, October 29, 2008 8:13 PM by Links (10/29/2008) « Steve Pietrek - Everything SharePoint

# Links (10/29/2008) &laquo; Steve Pietrek - Everything SharePoint

Pingback from  Links (10/29/2008) « Steve Pietrek - Everything SharePoint

Sunday, November 02, 2008 10:33 PM by || yeschandana ||

# My favorite links from the 4th week of October 2008

My favorite links from the 4th week of October 2008

# WSS 3.0 &amp; MOSS: Recopilatorio de enlaces interesantes (XXII)! &laquo; Pasi??n por la tecnolog??a&#8230;

Pingback from  WSS 3.0 & MOSS: Recopilatorio de enlaces interesantes (XXII)! « Pasi??n por la tecnolog??a…

Thursday, November 06, 2008 2:06 AM by YESChandana

# My favorite links from the 4th week of October 2008

My favorite links from the 4th week of October 2008

Monday, November 10, 2008 6:36 AM by girish [girishm@maqsoftware.com]

# help with live id integration

hey Attis,

I figure out that you are a sharepoint MVP, & I m stuck with a problem here, I want to ask you some questions about live id integration into sharepoint & since i dont have ur email forgive me for contacting you here.

my problem is regarding live id integration into sharepoint, but it isnt how to integrate live id integration into sharepoint sites as i referred to the solution provided by Keith at blog.solanite.com/.../default.aspx & with alot of customization & i was fairly succesful in doing so, but then i am stuck at one issue i.e. i am not able to authorize the users for the site say for example i want some users to have admin rights & some not but i m not able to enable this. the membership provider & role provider are given in the code & i m not actually familiar about how to tweak them. I m asking this cos in future i will going to use audience targeting n that will not be possible if i wont be able to assign users to particular security groups.

can u in any way guide me how i can do that. thanks for any help u can provide.

Wednesday, November 12, 2008 4:07 PM by J. Dan Attis

# re: How to change the default page of a SharePoint site using a Feature

I am actually working on getting Live ID integration working myself and when I do I will blog about it.

Monday, March 02, 2009 2:22 PM by John S

# re: How to change the default page of a SharePoint site using a Feature

Hello,

I'm stuggling to get your example working... I get "SHAREPOINT1 : Feature '47e32574-dc30-4b81-9a67-4fabcabea312' could not be installed because the loading of event receiver assembly "CustomDefaultPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" failed: System.IO.FileNotFoundException: Could not load file or assembly 'CustomDefaultPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c' or one of its dependencies. The system cannot find the file specified. " So I cannot get the VS to compile a binary. Am I seriously doing something wrong?

J

Friday, April 10, 2009 10:28 AM by Trent Foley

# re: How to change the default page of a SharePoint site using a Feature

Dan,

Thanks for the example.  You have a comment that reads "update navigation, if necessary, here".  I find, as you probably did too, that the SPFile.MoveTo method is "smart" in that links to the original default.aspx file are updated to the renamed "default-old.aspx" file.  Is there an easy way to prevent this behavior and to apply all personalizations from the original file to the new custom file?

Thanks,

Trent

Friday, April 10, 2009 11:54 AM by Trent Foley

# re: How to change the default page of a SharePoint site using a Feature

I got this working the way I wanted by using OpenBinaryStream and SaveBinary instead of the MoveTo method.  This preserves navigation and personalizations.

<pre lang="CSharp">

   public class CustomDefaultPageFeatureReceiver : BaseFeatureReceiver<SPWeb>

   {

       public const string DefaultFileName = "default.aspx";

       public const string BackupFileName = "default-orig.aspx";

       public override void FeatureActivated(SPWeb web, SPFeatureReceiverProperties properties)

       {  

           // back up the original home page

           var defaultPage = web.Files[DefaultFileName];

           var originalStream = defaultPage.OpenBinaryStream();

           web.Files.Add(BackupFileName, originalStream, true);

           // move the new default page to default.aspx

           var newDefaultPage = web.Files["CustomDefault.aspx"];

           var newFileStream = newDefaultPage.OpenBinaryStream();

           defaultPage.SaveBinary(newFileStream);

       }

       public override void FeatureDeactivating(SPWeb web, SPFeatureReceiverProperties properties)

       {

           // restore the back up

           var defaultPage = web.Files[DefaultFileName];

           var originalDefaultPage = web.Files[BackupFileName];

           var originalStream = originalDefaultPage.OpenBinaryStream();

           defaultPage.SaveBinary(originalStream);

           originalDefaultPage.DeleteAllPersonalizationsAllUsers();

           originalDefaultPage.Delete();

       }

   }

</pre>

I hope this might be of help to someone.

Trent

Friday, July 24, 2009 10:17 AM by Bilal

# re: How to change the default page of a SharePoint site using a Feature

Nice post.

I am looking to do the same thing, but by using Feature Stapling. I need my default.aspx provisioned as soon as the site is created. The problem is that the Site Provisioning order creates the Module Pages in Onet.XML as the last step whereas stapled features are provisioned before that. So one will not really get the reference of default.aspx page in a Stapled Feature as it has not been created yet. I did a work around by calling a asynchronous function from Feature Activated event and introduced a Thread.Sleep for 10 Seconds. The ugly part is that user has to refresh the screen to get the provisioned page. Can you suggest some nice idea to avoid this or assigning a custom provisioning order.

Thanks,

Bilal

# Make Custom Landing Page As Default Page of the SharePoint Team Site same as Wiki Home Page &laquo; Nik Patel&#039;s SharePoint World

Pingback from  Make Custom Landing Page As Default Page of the SharePoint Team Site same as Wiki Home Page « Nik Patel's SharePoint World