Wednesday, May 7, 2008

Creating a Publishing Page Layout

This next tutorial will show you how to create a page layout. I have to admit that the bulk of the content of this tutorial comes from a tutorial written here, however, there are a few errors in the example code provided and also STSDEV requires a few minor changes as well to get everything working. In addition, I'll try to elaborate on a few more things so that you get a better understanding.

Now let's dig in.


If you have no experience with page layouts (and Master Pages), I suggest you read the Page Layouts and Master Pages page on MSDN. In short, page layouts allow you to customize the layout and appearance of content and web parts of pages within a MOSS publishing site. These pages are stored in a special Master Page and Page Layouts document library. Pages that are based off these Page Layouts will be stored in a 'Pages' document library at the root of the site. The nice thing about using the publishing infrastructure is that it lets you save drafts of your page revisions and does versioning and check in/out, just like a normal document library.

In order to do this tutorial, you'll need to have the MOSS Publishing Infrastructure and MOSS Publishing features turned on at the site collection and site level, respectively (under site settings).

Step 1: Start with a new STSDEV Project

You don't necessarily have to use STSDEV to create this project, however you will have to manually manipulate your Manifest.xml and .ddf files to create a SharePoint solution for deployment, as well as execute the proper commands to install/deploy etc. If you want to do without STSDEV be my guest, you'll just have to know what you're doing.
  1. Open Visual Studio and run STSDEV.
  2. Create a new project as an 'Simple Feature Solution', I'll call it 'TestPageLayout'
  3. Make sure you create a FeatureReceiver with this project.
  4. Open the 'TestPageLayout' solution once it has been created.

Step 1.5: A little more info

We're going to create a custom layout page based on a content type and deploy it with a feature. We're also going to create a feature receiver class to create a new page in our 'Pages' library based on our new Page Layout. The following steps will provide the walkthrough for doing this.

STSDEV Automatically creates the feature.xml file as well as the FeatureReceiver.cs file for you

Step 2: The feature.xml file
  1. Open the 'Rootfiles' folder in your solution and ensure the following directory structure exists: 'TEMPLATE\FEATURES\TestPageLayout'
  2. Open 'feature.xml' and add to the <ElementManifests> section the following:

    <ElementManifest Location="SiteColumns.xml" />
    <ElementManifest Location="ContentTypes.xml"/>
    <ElementManifest Location="Provisioning.xml"/>
    <ElementFile Location="PageLayouts\CustomPageLayout.aspx"/>

  3. Next add the following to the <ActivationDependencies> node:

    <ActivationDependency FeatureId="F6924D36-2FA8-4f0b-B16D-06B7250180FA"/>

  4. There are a few key items in this file which need to be explored:
    • The 'ReceiverAssembly' and 'ReceiverClass' attributes of the <Feature> element refer to the Assembly that this project will generate and the Class name of the Event Receiver class we'll working with later on.
    • The <ElementManifest> and <ElementFile> elements refer to files that we will be creating in the next steps. *NOTE: If you hand-build your Manifest and .dll files (i.e. you aren't using STSDEV) you don't necessarily need the <ElementFile> referring to the .aspx page. Here's another blog post I wrote with some more information about that.
    • Finally, the 'ActivationDependencies' section includes the features that need to be activated in order for this feature to work. In this case the Publishing Infrastructure feature.
  5. Optional: You can change the Title of the feature or the Description if you like.
Step 3: Create the Site Columns file
  1. Within your 'TEMPLATE\FEATURES\TestPageLayout' directory, add a new xml file called 'SiteColumns.xml'
  2. Open the 'SiteColumns.xml' file, delete any existing markup and paste the following:

    <Elements xmlns="">
    <Field Type="HTML"
    Group="Custom Page Column"
    Name="Introduction" />
    <Field Type="Image"
    Group="Custom Page Column"
    Name="WelcomeImage" />

    This file defines two columns (Fields) that we will add to the content type (in the next step).
Step 4: The ContentTypes.xml file
  1. Within your 'TEMPLATE\FEATURES\TestPageLayout' directory, add a new xml file called 'ContentTypes.xml'
  2. Open the 'ContentTypes.xml' file, delete any existing markup and paste the following:

    <Elements xmlns="">
    <ContentType ID="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900916CECA7C77446059633C4287903AA2A"
    Description="Custom Home Page Template"
    Group="Custom Pages">
    <FieldRef ID="89AEE769-5F51-4608-B389-50C1B36C4FA8"
    ReadOnlyClient="FALSE" />
    <FieldRef ID="49500A41-5AD3-44b8-BC52-FF19B4AF4888"
    DisplayName="Welcome Image"
    ReadOnlyClient="FALSE" />

    You should note that the FieldRef ID's value is the IDs of the Site Columns you created in the previous steps.
Step 5: The CustomPageLayout.aspx file
  1. In your feature directory ('TEMPLATE\FEATURES\TestPageLayout') create a 'PageLayouts' folder.
  2. In the 'PageLayouts' folder add an aspx page called 'CustomPageLayout.aspx'.
  3. Open 'CustomPageLayout.aspx', erase any existing markup and paste the following code:

    <%@ Page language="C#" Inherits="Microsoft.SharePoint.Publishing.PublishingLayoutPage,Microsoft.SharePoint.Publishing,Version=,Culture=neutral,PublicKeyToken=71e9bce111e9429c" meta:webpartpageexpansion="full" meta:progid="SharePoint.WebPartPage.Document" %>
    <%@ Register Tagprefix="SharePointWebControls" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="PublishingWebControls" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
    <asp:Content ContentPlaceholderID="PlaceHolderPageTitle" runat="server">
    <SharePointWebControls:FieldValue id="PageTitle" FieldName="Title" runat="server"/>
    <asp:Content ContentPlaceholderID="PlaceHolderMain" runat="server">
    <div id="WelcomePageImage">
    <PublishingWebControls:RichImageField ID="RichImageField1" FieldName="WelcomeImage" runat="server"></PublishingWebControls:RichImageField>
    <div id="WelcomePageContent">
    <PublishingWebControls:RichHtmlField ID="RichHtmlField1" FieldName="Introduction" runat="server"></PublishingWebControls:RichHtmlField>

    Notice that this page contains two PublishingWebControls that have their 'FieldName' attributes set to the values that we defined in the <FieldRef> elements of the Content Type Definition file (ContentTypes.xml).
Step 6: The Provisioning.xml file
  1. In the 'TEMPLATE\FEATURES\TestPageLayout' directory create a new xml file called 'Provisioning.xml'.
  2. Open Provisioning.xml and erase any existing markup. Then paste the following code:

    <Elements xmlns="">
    <Module Name="PageTemplates" Url="_catalogs/masterpage" Path="PageLayouts" RootWebOnly="TRUE">
    <File Url="CustomPageLayout.aspx" Type="GhostableInLibrary">
    <Property Name="ContentType"
    Value="$Resources:cmscore, contenttype_pagelayout_name;" />
    <Property Name="PublishingAssociatedContentType"
    Value=";#CustomWelcomePageTemplate;#0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900916CECA7C77446059633C4287903AA2A;#" />

    Note the Property Name 'PublishingAssociatedContentType' refers to the ID of the ContentType we declared earlier.
Step 7: The FeatureReceiver.cs file
  1. In the root of your solution STSDEV should have created a FeatureReceiver.cs file. Open this file
  2. Replace the 'FeatureActivated' method with the following code:

    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    using (SPSite siteCollection = properties.Feature.Parent as SPSite)
    using(SPWeb web = siteCollection.RootWeb)
    if (PublishingWeb.IsPublishingWeb(web))
    //Get references to the publishing site
    PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);
    PublishingSite site = new PublishingSite(web.Site);

    //ensure that the page isn't present yet (in case the feature was activated and deactivated)
    if (publishingWeb.GetPublishingPages()["Pages/welcome.aspx"] == null)
    //Create the default page
    SPContentTypeId contentTypeID = new SPContentTypeId("0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900916CECA7C77446059633C4287903AA2A");
    PageLayout[] layouts = publishingWeb.GetAvailablePageLayouts(contentTypeID);
    PageLayout welcomePageLayout = layouts[0];

    PublishingPage welcomePage = publishingWeb.GetPublishingPages().Add("welcome.aspx", welcomePageLayout); welcomePage.ListItem["Introduction"] = "This is some introduction default text";

    //Set the default page
    SPFile welcomeFile = web.GetFile(welcomePage.Url);
    publishingWeb.DefaultPage = welcomeFile;


    In essence, when the feature is activated, this method will create a page called 'welcome.aspx' based on the Page Layout we created. It will then make the welcome.aspx page the default page for this site.
Step 8: Build and Deploy

Change your Build Configuration to BuildDeploy and build the solution.

Step 9: Test it out

If we've done everything right, we should be able to try out our feature.
  1. Go to the root web of your site collection and make sure the Publishing Infrastructure feature is turned on in the Site Collection Features, and that the Publishing feature is turned on in the Site Features.
  2. Activate the feature we created in the Site Collection features (since we scoped the feature at the 'Site' level...if we had scoped it at the 'Web' level, it would be activated from the Site Features).
  3. Go to the root of the site and see if everything worked correctly.

This feature should work without any problems provided your SharePoint server is configured correctly and you followed the steps outlined. If you run into errors here's a for debugging with STSDEV.


Brian said...

Memory leak in your example:
SPWeb web = siteCollection.RootWeb


Paul said...

Thank you for pointing that out. I have updated the code with a 'using' statement for the SPWeb to be disposed of properly.