Adding functionalities to pages by inheriting PublishingLayoutPage

If you have worked a lot with MOSS you probably know how to make new page layouts. But if you create new page layouts you might sometimes wonder that how could I add some common functionalities to my page layout pages. One example could be localization. You have decided that Variations isn't the way to go in your case, but you still want to have different site structures for different languages... and of course you want to have texts localized. Or you want to change your master page for some reason on the fly... one example could be for printing reasons. Or even wilder... you want to change you page layout to another! You could do this kind of stuff pretty easily if you create your own PublishingLayoutPage class that has support your new functionalities. I'm going to explain how you can do that with SharePoint Designer and Visual Studio.

Create new class that will extend the functionality of PublishingLayoutPage

I started my journey by creating new Class Library project. I named it "Microsoft.MCS.Common" (since I work in MCS inside Microsoft... cool naming right :-). I added new class and named it PublishingLayoutPageEx. I inherited that from PublishingLayoutPage which is class behind page layouts. Where did I got that class name? Well I just opened ArticleLeft.aspx with SharePoint Designer and checked the first line:

<%@ Page language="C#" Inherits="Microsoft.SharePoint.Publishing.PublishingLayoutPage, Microsoft.SharePoint.Publishing, Version=12.0.0.0,Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

So it was pretty obvious that if I want to extend the functionality of the basic publishing page, I needed to inherit from it.

At this point my code looked like this (not much since we just started):
 1  using System; 2  using System.Collections.Generic; 3  using System.Text; 4  using System.Web.UI;  5  using System.Globalization;  6  using System.Threading;  7  using Microsoft.SharePoint.Publishing;  8  using Microsoft.SharePoint;  9 10  namespace Microsoft.MCS.Common 11  { 12    public class PublishingLayoutPageEx : PublishingLayoutPage 13    { 14      public PublishingLayoutPageEx() 15        : base() 16      { 17      } 18    } 19  }

And now I'm ready to test my new class in action. I just added strong name key, compiled and put it in the GAC. And then I changed the ArticleLeft.aspx to use my new class:

<%@ Page language="C#" Inherits="Microsoft.MCS.Common.PublishingLayoutPageEx, Microsoft.MCS.Common,Version=1.0.0.0, Culture=neutral,PublicKeyToken=b1e9400215c03709" %>

<small sidetrack to .NET Reflector>

If you're interestested in the stuff that's implemented in PublishingLayoutPage, then you can play around with incredible tool: Lutz Roeder's NET Reflector:

In just few clicks we can see that there is some MasterPageFile retrieving in OnPreInit:

</small sidetrack to .NET Reflector>

If you now try your new PublishingLayoutPageEx in action you'll get this kind of error message:

Server Error in '/' Application.


Parser Error

Description: An error occurred during the parsing of a resource required to service this request. Please review the following specific parse error details and modify your source file appropriately.

Parser Error Message: The base type 'Microsoft.MCS.Common.PublishingLayoutPageEx' is not allowed for this page. The type is not registered as safe.

Source Error:

 Line 1:  <%@ Page language="C#"   Inherits="Microsoft.MCS.Common.PublishingLayoutPageEx,Microsoft.MCS.Common,Version=1.0.0.0,Culture=neutral,PublicKeyToken=b1e9400215c03709" %>Line 2:  <%@ Register Tagprefix="SharePointWebControls" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="PublishingWebControls" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="PublishingNavigation" Namespace="Microsoft.SharePoint.Publishing.Navigation" Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>Line 3:  <asp:Content ContentPlaceholderID="PlaceHolderPageTitle" runat="server">

Source File: /_catalogs/masterpage/ArticleLeft.aspx Line: 1


Version Information: Microsoft .NET Framework Version:2.0.50727.42; ASP.NET Version:2.0.50727.210

That only means that we need to mark that component as Safe so that SharePoint will load it. Let's just modify our applications web.config file by adding following line in there:

<SafeControl Assembly="Microsoft.MCS.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b1e9400215c03709" Namespace="Microsoft.MCS.Common" TypeName="PublishingLayoutPageEx" Safe="True" AllowRemoteDesigner="true" />

And then hit F5 in your browser and you should be all set. Now you have base what we're going to extend in next.

Add localization support to your pages

If you haven't played with ASP.NET Resource files, then you should take small detour into www.asp.net localization quickstart

So now you know about .RESX files :-) I created Example.resx, Example.en-US.resx and Example.fi-FI. I have added only two words to the resource files:

  1. House:
    • en-US: House
    • fi-FI: Talo
  2. You:
    • en-US: You
    • fi-FI: Sinä

I copied those resource files to my applications App_GlobalResouces folder:

C:\Inetpub\wwwroot\wss\VirtualDirectories\80\App_GlobalResources 

Now I modified my default_Janne.master page so that it would receive text from my resource files. I added following line just before </body> in master page.

<asp:Literal ID="house" runat="server" Text="<%$Resources:Example,House%>" /> <-> <asp:Literal ID="you" runat="server" Text="<%$Resources:Example,You%>" />

We have now added resource files and modified master page so that it will take text from our resource file. Let's just add code to our new class so that we could change the language on the fly.

 1  using System; 2  using System.Collections.Generic; 3  using System.Text; 4  using System.Web.UI;  5  using System.Globalization;  6  using System.Threading;  7  using Microsoft.SharePoint.Publishing;  8  using Microsoft.SharePoint;  9 10  namespace Microsoft.MCS.Common 11  { 12    public class PublishingLayoutPageEx : PublishingLayoutPage 13    { 14      public PublishingLayoutPageEx() 15        : base() 16      { 17      } 18 19      protected override void OnPreInit() 20      { 21         base.OnPreInit();22         this.InitializeCulture(); 23      }2425      protected override void InitializeCulture() 26      { 27         if (Request["mylang"] != null) 28         { 29           Thread.CurrentThread.CurrentCulture = new CultureInfo(Request"mylang"].ToString()); 30           Thread.CurrentThread.CurrentUICulture = new CultureInfo(Request["mylang"].ToString()); 31         } 32  33         base.InitializeCulture(); 34      } 35    } 36  }

And now we can change the language from URL:

Here is result without the mylang parameter:

Of course you might not want to change your language by url parameter :-) This is just sample that you CAN do that. Maybe it would be much wiser to use some kind of site structure for localization. But I'll leave that to you...

Change master page on the fly

Now we want to make something fancier... like changing the master page on the fly. You could want to use this for print layouts, smaller screen, mobile etc. But anyway.. You just might want to do that sometimes :-)

So let's throw some code in here and see what happens:

...`` 1      protected override void OnPreInit()  2      {  3         base.OnPreInit(); 4         if (Request["Print"] != null)  5         {  6           this.MasterPageFile = "/_catalogs/masterpage/BlueBand.master"; 7         }  8      }``...`` 

On lines 4 to 6 we have just check that if there is mysterious Print parameter set. If that is set, we'll change the master page to nicely hardcode one. Let's see what happens on our browser:

So the result is quite easy to see... our master page changed from default_janne.master to BlueBand.master.

Change the page layout

Before I start... I'm going to give credit of this idea to Vesa Juvonen (colleague of mine at MCS who also works with SharePoint). He said that this would be interesting thing to checkout. And since I happened to have some code ready we tried this stuff on my environment. But he's going to create full solution of this page layout change and publish it in his blog. So you probably want to check that place out too. Okay.. but let's get back to the subject.

This might sound a bit strange but still... sometimes you might want to change page layout after the page has been created. Consider the Article Page content type which is OOB content type in SharePoint. It has 4 different kind of page layouts. User could have selected Image on right layout and has filled the page with data. After a while you want to change it to another layout.... BUT there isn't easy way to do that... unless we'll extend our nice class again.

Idea is take (again) some nice url parameter that tells the destination page layout. In this example I'll just take integer which is used to get the correct page layout from array of possible page layouts of this content type. And yes... I know that this code sample has a lot to improve... It just gives you ideas.

Let's throw some code in here and see what happens:

...`` 1      protected override void OnPreInit()  2      {  3        SPContext current = SPContext.Current; 4        if (current != null &&  5            Request["changepagelayout"] != null &&  6            Request["done"] == null)  7        {  8          SPWeb web = current.Web;  9          // We need to allow unsafe updates in order to do this: 10          web.AllowUnsafeUpdates = true; 11          web.Update(); 12          13          PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web); 14          PageLayout[] layouts = publishingWeb.GetAvailablePageLayouts(current.ListItem.ContentType.Parent.Id); 15          PublishingPage publishingPage = PublishingPage.GetPublishingPage(current.ListItem); 16          publishingPage.CheckOut(); 17          // This is the magic:18          publishingPage.Layout = layouts[Convert.ToInt32(Request["changepagelayout"])]; 19          publishingPage.Update(); 20          publishingPage.CheckIn("We have changed page layout"); 21          22          SPFile file = current.ListItem.File; 23          file.Publish("Publishing after page layout change"); 24          // We have content approval on: 25          file.Approve("Approving the page layout change"); 26          Response.Redirect(Request.Url + "&done=true"); 27        } 28        base.OnPreInit(e);29      }``...`` 

And you can right away see from code that there isn't any checks or any error handling. So this code is only for demonstration purposes and you shouldn't take it any other way.... but here we can see the results after user has type in parameter changepagelayout=1 (Image on left):

And here is page if parameter is 2 (Image on right) .

If you look at the code on line 14 where page layouts are retrieved... I'm using Parent of the current content type. You might ask why... But the reason is simple since your content type from the list actually inherits the Article Page content type from Site collection level. So if you would use ListItem.ContentType you wouldn't get those 4 page layouts of Article Page. Insted you need to get the parent of the content type in the list and then you get 4 different page layouts. Makes sense if you think how inheritance in SharePoint works.

If you wonder that done parameter I'm using... It is just helper to avoid recursive page layout change ;-)

Note: If you look at the code you probably already noticed that it's not changing the layout for this rendering... it has changed the page layout permanently. Of course you can change it back if you want to.

Summary

You can use a lot of stuff from ASP.NET right in your SharePoint page layouts. I can't even imagine all the capabilities of this but I'm just going to give you brief list what I can think of now:

1) Localization:

  • This one is obvious :-) I live in Finland and we need to deal with this in every project.

2) Change the master page

  • Print layouts
  • Mobile UIs

3) Change page layout

  • If you had created page but you later on want to change to more suitable one... here's how you can do it

 

I hope you got the idea of this post. I know I could improve those samples a lot, but I just wanted to share my idea and give you the opportunity to make it much better than I did.

Anyways... happy hacking!

J