Provisioning Publishing Pages and Web Parts using the App Model


In the previous articles of this series we looked at working with Site Columns, Content Types and Lists using the App Model. In this article we will look at leveraging the App Model to deploy Publishing Pages and Web Parts.

Creating Publishing Pages using the App Model

In order to create a Publishing Page your app requires the Write permission on the Web where the page is to be created. Additionally, if your app is installed a subweb to create pages there, it will also need the Read permission on the Site Collection in order for it to retrieve the Page Layout to be used to create the page.

Before you create a Publishing Page you will need to get a reference to the List Item associated with the Page Layout that you want to use to create your Publishing Page.

Web web = clientContext.Web;
clientContext.Load(clientContext.Site.RootWeb, w => w.ServerRelativeUrl);
clientContext.ExecuteQuery();

// Get Page Layout
File pageFromDocLayout = clientContext.Site.RootWeb.GetFileByServerRelativeUrl(String.Format("{0}/_catalogs/masterpage/BlankWebPartPage.aspx", clientContext.Site.RootWeb.ServerRelativeUrl.TrimEnd('/')));
Microsoft.SharePoint.Client.ListItem pageLayoutItem = pageFromDocLayout.ListItemAllFields;
clientContext.Load(pageLayoutItem);
clientContext.ExecuteQuery();

// Create Publishing Page
PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(clientContext, web);
PublishingPage page = publishingWeb.AddPublishingPage(new PublishingPageInformation {
    Name = "My-Page.aspx",
    PageLayoutListItem = pageLayoutItem
});
clientContext.ExecuteQuery();

// Set Page Title and Publish Page
Microsoft.SharePoint.Client.ListItem pageItem = page.ListItem;
pageItem["Title"] = "My Page";
pageItem.Update();
pageItem.File.CheckIn(String.Empty, CheckinType.MajorCheckIn);
clientContext.ExecuteQuery();

In this article we focus on a structured and repeatable deployment scenario where we know which Page Layouts are available to us and what their names are. Because of this we are able to retrieve the Page Layout by its URL. Before we do this however, we need to retrieve the server-relative URL of the Site Collection (line 2) in order to ensure that our app will work in both Host-Named (or the main Site Collection of the Office 365 tenant) and path-based Site Collections.

Using the Web.GetFileByServerRelativeUrl method we can retrieve the Page Layout File object (line 6). What we need to create a Publishing Page however is the underlying List Item which we can retrieve by reading the File.ListItemAllFields property (line 7).

To create a Publishing Page we first wrap the current Web into a Publishing Web (line 12). Then we call the PublishingWeb.AddPublishingPage method passing a PublishingPageInformation object that contains information about the Publishing Page to be created (lines 13-16). Unfortunately, as you can see in the example above, the properties that you can set on this object are very limited, which means that some additional work is required after the creating the page to have the page created exactly as you want.

After creating the page the odds are high that you would want to set its Title and other properties. For this we need to get a reference to the underlying List Item (line 20). We complete the process by publishing the page (line 23).

Adding a Friendly URL to a Publishing Page

SharePoint 2013 introduces Managed Navigation that allows you to model the navigation of your site using Managed Metadata. By specifying Friendly URLs you can decouple the navigation from the physical structure of your website making it easier to maintain.

The following code sample continues on the process of creating the Publishing Page. If you would like to configure Friendly URLs on existing Publishing Pages or do it in a separate method, you would need to retrieve the Publishing Page first. You will learn how to do that later in the article where we discuss provisioning Web Parts to Publishing Pages.

In order to add a Friendly URL to a Publishing Page your app will need the Write permission on the Taxonomy: after all we will be creating a new Term that will represent our Publishing Page in the site’s navigation.

// Add Friendly URL
// Requires Write permissions on Taxonomy
TaxonomySession taxonomySession = TaxonomySession.GetTaxonomySession(clientContext);
NavigationTermSet navigationTermSet = TaxonomyNavigation.GetTermSetForWeb(clientContext, web, "GlobalNavigationTaxonomyProvider", true);
clientContext.Load(navigationTermSet);
clientContext.ExecuteQuery();
NavigationTermSet editableNavigationTermSet = navigationTermSet.GetAsEditable(taxonomySession);
clientContext.Load(editableNavigationTermSet);
clientContext.ExecuteQuery();
ClientResult<string> furl = page.AddFriendlyUrl("my-page", editableNavigationTermSet, true);
clientContext.ExecuteQuery();

// Set Friendly URL Title
NavigationTerm friendlyUrlTerm = editableNavigationTermSet.FindTermForUrl(furl.Value);
friendlyUrlTerm.Title.Value = "My Page";
friendlyUrlTerm.GetTaxonomyTermStore().CommitAll();
clientContext.ExecuteQuery();

We start by establishing a session with Taxonomy (line 3). Next, we retrieve the Navigation Term Set for the current Site (line 4). Please note, that in this sample we assume that Managed Navigation has already been configured on the site that we are working with.

Because we will be adding a new Friendly URL we need to be able to write to the Navigation Term Set. For this we need to turn the Navigation Term Set into Editable Navigation Term Set (line 7). Having done that, we can call the PublishingPage.AddFriendlyUrl method passing the Friendly URL that we want to associate with our page, the Editable Navigation Term Set where the Friendly URL should be added to and a boolean value specifying whether our newly added page should be visible in the navigation or not (line 10).

If we stopped here, our Publishing Page would have a Friendly URL associated with it, but the display name of the link in the navigation would be set to the Friendly URL (my-page) rather than the page’s Title (My Page). We can fix this by retrieving the Navigation Term (line 14), setting its Title (line 15) and committing our changes to the Term Store (line 16).

Deploying Web Parts

One of the frequent scenarios in structured and repeatable deployments is provisioning preconfigured Web Parts to pages. The great thing is that this can be also accomplished using the App Model. The downside is however, that currently adding Web Parts to pages requires your app to have Full Control on the Site that stores the pages to which you want to add Web Parts.

Adding a Web Part to a page is relatively easy. Probably the easiest way to do it, is to have the XML of your preconfigured Web Part available in your app and use that to add the Web Part to the page.

In the following example we will add a preconfigured Content Editor Web Part to the Header Web Part Zone on a Publishing Page.

Web web = clientContext.Web;

clientContext.Load(web, w => w.ServerRelativeUrl, w => w.AllProperties);
clientContext.ExecuteQuery();
string pagesListName = web.AllProperties["__pageslistname"] as string;

File page = web.GetFileByServerRelativeUrl(String.Format("{0}/{1}/My-Page.aspx", web.ServerRelativeUrl.TrimEnd('/'), pagesListName));
page.CheckOut();

// Requires Full Control permissions on the Web
LimitedWebPartManager wpmgr = page.GetLimitedWebPartManager(PersonalizationScope.Shared);
WebPartDefinition wpd = wpmgr.ImportWebPart(Properties.Resources.CEWP);
wpmgr.AddWebPart(wpd.WebPart, "Header", 0);

page.CheckIn(String.Empty, CheckinType.MajorCheckIn);
clientContext.ExecuteQuery();

The first thing that we need is a reference to the File object behind our Publishing Page. Already here we stumble upon the first challenge. Currently, there is no easy way in the Client-Side Object Model (CSOM) to retrieve a Publishing Page. The only way to do it, is to do it manually using the page’s URL, but for that we need the name (URL part) of the Pages Library which is translated to the language used to create the Publishing Web. Because of this we cannot hard-code the name in the app and have to retrieve it from the __pageslistname property of the Web (lines 3-5).

Next, having constructed a server-relative URL of our Publishing Page, we can retrieve it using the Web.GetFileByServerRelativeUrl method (line 7). As our Publishing Web uses versioning, we need to check out the page before proceeding (line 8).

We begin the process of adding a Web Part by getting a reference to the Web Part Manager (line 11). Next, we import the Web Part. As mentioned before, it’s probably the easiest to have the XML of the preconfigured Web Part available in the app. In this sample the following XML of a preconfigured Content Editor Web Part has been exported from SharePoint and included in the app’s resources:

<?xml version="1.0" encoding="utf-8"?>
<WebPart xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/WebPart/v2">
  <Title>Content Editor</Title>
  <FrameType>None</FrameType>
  <Description>Allows authors to enter rich text content.</Description>
  <IsIncluded>true</IsIncluded>
  <ZoneID>Header</ZoneID>
  <PartOrder>0</PartOrder>
  <FrameState>Normal</FrameState>
  <Height />
  <Width />
  <AllowRemove>true</AllowRemove>
  <AllowZoneChange>true</AllowZoneChange>
  <AllowMinimize>true</AllowMinimize>
  <AllowConnect>true</AllowConnect>
  <AllowEdit>true</AllowEdit>
  <AllowHide>true</AllowHide>
  <IsVisible>true</IsVisible>
  <DetailLink />
  <HelpLink />
  <HelpMode>Modeless</HelpMode>
  <Dir>Default</Dir>
  <PartImageSmall />
  <MissingAssembly>Cannot import this Web Part.</MissingAssembly>
  <PartImageLarge>/_layouts/15/images/mscontl.gif</PartImageLarge>
  <IsIncludedFilter />
  <Assembly>Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
  <TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName>
  <ContentLink xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor" />
  <Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor"><![CDATA[<h1>​It&#39;s all about apps</h1>]]></Content>
  <PartStorage xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor" />
</WebPart>

If the process of importing the Web Part succeeds, we will get a reference to a WebPartDefinition object (line 12). This object can then be used to add the Web Part to a Web Part Zone (line 13). We complete the process by publishing the page (line 15).

Download: Sample App for SharePoint (232KB, ZIP)

Others found also helpful: