Friday, October 17, 2008

Use layout page metadata in masterpages

This post is a tribute to my colleague Riad TIZERARINE that has found this tip. We had to use some of the properties of the layout Pages inside the master page. We wanted precisely to take the value of the field description of the Layout Page, and put it inside the meta tag "description.
So I will illustrate the elegant solution that have found Riad in a short tutorial and will show that this practice can be extended.

1 - Create a minimal master page and put it in the master page gallery

I usually work on master pages starting with minimal one as recommanded by Microsoft :How to: Create a Minimal Master Page
So I did a feature in order to deploy this master page and let it to the ghosted state because I want to use visual studio to customize it.
When I have deployed this master page in the master page gallery, and change it for my Litware portal root site, I obtain this result.


2 - populate your publishing page fields

Now I have to populate my publishing page fields in order to have data to use in the demonstration.


3 - Programming with C# in line script

So the idea now is to use the metadata of the page layout inside the master page to display informations that are stored at the Layout Page level, but WITHOUT writing a single line of code in the layout page.
We will illustrate this by using description porperty of the layout page in a meta tag description, and using ["Modified By"], [Modified"] and Page Layout Contact e-mail to populate the footer of the master page.
So how to do it in an elegant way?
Riad found the solution with this instruction:

SPcontext.Current.File

MSDN doesn't teach so much about this instruction : Gets the file that is associated with the list item object of the given Windows SharePoint Services context. (SPContext Members (Microsoft.SharePoint))
here is the sample that show how to use it :
  • To put the page layout description inside a meta tag description, put this in line script right under the meta tag of your master page.

    
    <%        SPFile myPublishingPage = SPContext.Current.File;
              string description = string.Empty;
              if (myPublishingPage.Item.Fields.ContainsField("Description"))
              {
                  description = (string)myPublishingPage.Item["Description"];
              }
    %>
    
    <meta name="Description" content="<%=description %>" />
    
    
    And there is a much more simple way :
    
    <%        SPItem myPublishingPageItem = SPContext.Current.Item;
              string description = string.Empty;
    
              if (myPublishingPageItem.Fields.ContainsField("Description"))
              {
                  description = (string)myPublishingPageItem["Description"];
              }
    %>
    <meta name="Description" content="<%=description %>" />
    
    
  • Here is the in line script and html code to render the footer using layout page properties as previously defined Warnings: Place your in line script after the form tag otherwise it will not be working.
    And as usual, don't forget that in line script in master page is just a temporary solution, to make a proof of concept, and not a best practice for real applications.

    
        <%
    
            char[] separator = { '#' };
            string strMyUser = string.Empty;
            strMyUser = myPublishingPageItem["Modified By"].ToString().Split(separator)[1];
            char[] separator2 = { '(' };
            string[] maTable;
            strMyUser = strMyUser.Split(separator2)[0];
    
            string creationDate = string.Empty;
            creationDate = myPublishingPageItem["Modified"].ToString();
    
            string authorEmail = string.Empty;
            if (myPublishingPageItem.Fields.ContainsField("Contact E-Mail Address"))
            {
                authorEmail = (string)myPublishingPageItem["Contact E-Mail Address"];
            }
    
     
        %>
        <br />
        <div style="width: 100%; text-align: center; color: #5B5B5B; font-family: Tahoma;
            font-size: 8pt;">
            <table id="footer" style="width: 600px; border: solid 1px #5B5B5B; border-collapse: collapse">
                <tr>
                    <td style="text-align: center; width: 200px">
                        Modified by: <%=strMyUser%></td>
                    <td>
                         | </td>
                    <td width="200px">
                        At
                        <%=creationDate %>
                    </td>
                    <td>
                         | </td>
                    <td width="200px">
                        <a href="mailto:<%=authorEmail%>">contact us</a></td>
                </tr>
            </table>
        </div>
    
    
    

4 - Results

Here is the new home page with the footer after the implementation:



Here is the complete code of the minimal master page after the implementation:


<%-- Identifies this page as a .master page written in Microsoft Visual C# and registers tag prefixes, namespaces, assemblies, and controls. --%>
<%@ Master Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register TagPrefix="SPSWC" Namespace="Microsoft.SharePoint.Portal.WebControls"
    Assembly="Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SharePoint" 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" %>
<%@ Register TagPrefix="wssuc" TagName="Welcome" Src="~/_controltemplates/Welcome.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="DesignModeConsole" Src="~/_controltemplates/DesignModeConsole.ascx" %>
<%@ Register TagPrefix="PublishingVariations" TagName="VariationsLabelMenu" Src="~/_controltemplates/VariationsLabelMenu.ascx" %>
<%@ Register TagPrefix="PublishingConsole" TagName="Console" Src="~/_controltemplates/PublishingConsole.ascx" %>
<%@ Register TagPrefix="PublishingSiteAction" TagName="SiteActionMenu" Src="~/_controltemplates/PublishingActionMenu.ascx" %>
<%-- Uses the Microsoft Office namespace and schema. --%>
<html>
<WebPartPages:SPWebPartManager runat="server" />
<SharePoint:RobotsMetaTag runat="server" />
<%        SPItem myPublishingPageItem = SPContext.Current.Item;
          string description = string.Empty;

          if (myPublishingPageItem.Fields.ContainsField("Description"))
          {
              description = (string)myPublishingPageItem["Description"];
          }
%>
<meta name="Description" content="<%=description %>" />
<%-- The head section includes a content placeholder for the page title and links to CSS and ECMAScript (JScript, JavaScript) files that run on the server. --%>
<head runat="server">
    <asp:contentplaceholder runat="server" id="head">
  <title>
        <asp:ContentPlaceHolder id="PlaceHolderPageTitle" runat="server" />

</title>

    </asp:contentplaceholder>
    <SharePoint:CssLink runat="server" />
    <asp:contentplaceholder id="PlaceHolderAdditionalPageHead" runat="server" />
</head>
<%-- When loading the body of the .master page, SharePoint Server 2007 also loads the SpBodyOnLoadWrapper class. This class handles .js calls for the master page. --%>
<body onload="javascript:_spBodyOnLoadWrapper();">
    <%-- The SPWebPartManager manages all of the Web part controls, functionality, and events that occur on a Web page. --%>
    <form runat="server" onsubmit="return _spFormOnSubmitWrapper();">
        <wssuc:Welcome id="explitLogout" runat="server" />
        <PublishingSiteAction:SiteActionMenu runat="server" />
        <PublishingWebControls:AuthoringContainer ID="authoringcontrols" runat="server">
            <PublishingConsole:Console runat="server" />
        </PublishingWebControls:AuthoringContainer>
        <%-- The PlaceHolderMain content placeholder defines where to place the page content for all the content from the page layout. The page layout can overwrite any content placeholder from the master page. Example: The PlaceHolderLeftNavBar can overwrite the left navigation bar. --%>
        <asp:ContentPlaceHolder ID="PlaceHolderMain" runat="server" />
        <asp:Panel Visible="false" runat="server">
            <%-- These ContentPlaceHolders ensure all default SharePoint Server pages render with this master page. If the system master page is set to any default master page, the only content placeholders required are those that are overridden by your page layouts. --%>
            <asp:ContentPlaceHolder ID="PlaceHolderSearchArea" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderTitleBreadcrumb" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderPageTitleInTitleArea" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderLeftNavBar" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderPageImage" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderBodyLeftBorder" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderNavSpacer" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderTitleLeftBorder" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderTitleAreaSeparator" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderMiniConsole" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderCalendarNavigator" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderLeftActions" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderPageDescription" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderBodyAreaClass" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderTitleAreaClass" runat="server" />
            <asp:ContentPlaceHolder ID="PlaceHolderBodyRightMargin" runat="server" />
        </asp:Panel>
    </form>
    <%
        /*//Use that if you want to explore the page properties
        Response.Write("<Table style='border-collapse:collapse;'>");
        System.Collections.Generic.SortedDictionary<string, string> myList = new System.Collections.Generic.SortedDictionary<string, string>();
        foreach (SPField aField in myPublishingPageItem.Fields)
        {
            try
            {
                myList.Add(aField.ToString(), myPublishingPageItem[aField.ToString()].ToString());
            }
            catch
            {

            }
        }
        foreach (string aString in myList.Keys)
        {
            Response.Write("<tr ><td nowrap style='margin:0px;width:300px;border:solid 1px gray;font-size:9pt;'>" + aString + "</td><td  nowrap style='border:solid 1px gray;font-size:9pt;'>" + myList[aString] + "</td></tr>");
        }
        Response.Write("</Table>");*/

        char[] separator = { '#' };
        string strMyUser = string.Empty;
        strMyUser = myPublishingPageItem["Modified By"].ToString().Split(separator)[1];
        char[] separator2 = { '(' };
        string[] maTable;
        strMyUser = strMyUser.Split(separator2)[0];

        string creationDate = string.Empty;
        creationDate = myPublishingPageItem["Modified"].ToString();

        string authorEmail = string.Empty;
        if (myPublishingPageItem.Fields.ContainsField("Contact E-Mail Address"))
        {
            authorEmail = (string)myPublishingPageItem["Contact E-Mail Address"];
        }

 
    %>
    <br />
    <div style="width: 100%; text-align: center; color: #5B5B5B; font-family: Tahoma;
        font-size: 8pt;">
        <table id="footer" style="width: 600px; border: solid 1px #5B5B5B; border-collapse: collapse">
            <tr>
                <td style="text-align: center; width: 200px">
                    Modified by: <%=strMyUser%></td>
                <td>
                     | </td>
                <td width="200px">
                    At
                    <%=creationDate %>
                </td>
                <td>
                     | </td>
                <td width="200px">
                    <a href="mailto:<%=authorEmail%>">contact us</a></td>
            </tr>
        </table>
    </div>
</body>
</html>

2 comments:

Steve Ruiz said...

Reminder: Of course for this solution to work you must enable code blocks in master pages...this is not considered a 'best practice' in most situations.

Marc Charmois said...

Hi Steve,

thank you to remind the way of allowing in line script in pages where it is not normally allowed, aka Site Pages (Layout Pages, Master Pages,etc.).
You are very right, Steve, it is not a best practice at all, that is why I placed this in a warning in my post:

"...And as usual, don't forget that in line script in master page is just a temporary solution, to make a proof of concept, and not a best practice for real applications."

But it may be good to use that way of programming to quickly test a proof of concept in order to improve your development speed, as if you were drawing a sketch to prepare the final canevas...
cf :
Improve speed of development for SharePoint (Part3) - Using in line scripts in SharePoint programming

For ending I must precise that what you have written is not totaly exact.
For this solution to work you must enable code blocks in master pages if you upload the master page in the Master Page Gallery but it is not necessary if you deploy the master page with a feature .I should have mentioned it.

the reason ?

The Site Pages and the in-line script run just fine as long as the pages remains uncustomized in a ghosted state. Remember that WSS compiles a ghosted page into an assembly DLL for processing. However, as soon as a user modifies any aspect of this page with the SharePoint Designer and moves the site page into an unghosted state, WSS then begins to use safe mode to process it. Because the page contains in-line script, WSS refuses to process it in safe mode and generates an error message.
But a master page deployed by feature runs in ghosted state...Just try it !

For more information about ghosting, un-ghosting and re-ghosting Site Pages cf.
Re-ghost items present in the master page gallery