Monday, July 26, 2010

Provision publishing pages in a sandbox solution

This one turned out to be an interesting finding – at first I thought that I can just copy a regular “Module” feature from a farm solution and that it will work smoothly within a sandbox solution. For instance:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <Module Name="MyPages" Path="MyPages" Url="Pages">

    <File Url="mypage.aspx" Path="mypage.aspx" Type="GhostableInLibrary">

      <Property Name="Title" Value="Home" />

      <Property Name="ContentType" Value="Welcome Page" />

      <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/MyWelcome.aspx, My Welcome Page" />

      <AllUsersWebPart WebPartZoneID="BottomLeftZone" WebPartOrder="1">       

          <![CDATA[

<WebPart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/WebPart/v2">

  <Title>Search Box</Title>

  <FrameType>None</FrameType>

  <Description>Displays a search box that allows users to search for information.</Description>

  <MissingAssembly>Cannot import this Web Part.</MissingAssembly>

  <Assembly>Microsoft.Office.Server.Search, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>

  <TypeName>Microsoft.SharePoint.Portal.WebControls.SearchBoxEx</TypeName>

</WebPart>]]>       

      </AllUsersWebPart>

    </File>

  </Module>

</Elements>

As you see, this is a fairly simple “Module” elements file with a single “File” element that provisions a publishing page containing one web part to the standard “Pages” library. The “mypage.aspx” file is the standard template redirection one-liner page that you can take from any of the standard SharePoint publishing site definitions:

<%@ Page="" Inherits="Microsoft.SharePoint.Publishing.TemplateRedirectionPage,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %><%@ Reference="" VirtualPath="~TemplatePageUrl" %><%@ Reference="" VirtualPath="~masterurl/custom.master" %>

So, when I added this feature to a sample sandbox solution of mine, deployed the latter and activated the feature I immediately noticed two quite unpleasant issues:

  • the first one was that the file was provisioned in a draft state (not checked in)
  • the second was even worse – the web part was missing on the page

I started wondering what may be causing these issues and thought that at least the first one was not that serious – after all with a little bit of code in a feature receiver it should be possible to publish the provisioned page or pages. But then thinking about the second issue I realized that it will require much more than several lines of code if I want to get that fixed with custom programming (and why should I get into that much trouble since this is supposed to be available out-of-the-box). The fact was that I already had the code that does the whole trick of parsing the “Module” elements file, parses a selected “File” element and adds the web parts to a web part page using the SharePoint object model – I had used that in my “web part manager” tool, though I wasn’t sure if the code (and it was several hundred lines of code) would work normally in a sandbox.

Reluctant to use this cumbersome “fix”, it occurred to me that I can try using the built-in SharePoint feature of saving a site as a template which in SharePoint 2010 creates a “wsp” package (which is a sandbox solution) that contains a “Module” feature provisioning all files (including publishing pages) in the site. My plan was simple – after I create the “wsp” package of my test site with publishing pages I will create a sub-site based on it and will check whether the provisioning of the publishing pages will fail in the same miserable way and if it doesn’t I will open the “wsp” file and inspect the XML of the “Module” element to see what does the trick.

So, as I suspected, the first part went smoothly which meant that the “save as template” feature worked perfectly with publishing pages although the “save site as template” command is missing in the “site settings” page of sites based on publishing site templates and you have to navigate to it manually - _layouts/savetmpl.aspx (it’s actually interesting to know why it is hidden in the first place, I can only suspect that there is a serious reason for that).

Then came the interesting part of checking the “Module” and “File” elements in the “Module” feature containing the publishing pages (actually I was testing with just one page) – first I reproduced the normal working of the feature by copying the elements without any changes and then started to remove parts, that I thought, weren’t necessary so that I could find out which is the critical part. First I checked the attributes of the “Module” and “File” elements but there was nothing extra or peculiar there (the “Level” attribute of the “File” element was not present), it wasn’t a “Property” element for the “_ModerationStatus” system column either. Then I saw a big chunk worth deleting – the “Property” element of the “MetaInfo” system column – it basically contains the concatenated values of all other fields and file property bag items of the provisioned file – and after I deleted it the feature started to behave in the exactly same way as my first feature. I had a closer look at the “MetaInfo” element and after some testing found out that it is this part that does the trick:

vti_setuppath:SR|SiteTemplates\\SPS\\Default.aspx

The meaning of this one was almost clear to me – instead of using the “aspx” file provided in the feature the publishing page should use a file already available in the subfolders of the “14” hive. Another thing that immediately occurred to me was that the “Module” element also accepts a “SetupPath” attribute, so I quickly rewrote my first feature so that it now looked like:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <Module Name="MyPages" SetupPath="SiteTemplates\SPS" Url="Pages">

    <File Url="mypage.aspx" Path="default.aspx" Type="GhostableInLibrary">

      <Property Name="Title" Value="Home" />

      <Property Name="ContentType" Value="Welcome Page" />

      <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/MyWelcome.aspx, My Welcome Page" />

      <AllUsersWebPart WebPartZoneID="BottomLeftZone" WebPartOrder="1">       

          <![CDATA[

<WebPart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/WebPart/v2">

  <Title>Search Box</Title>

  <FrameType>None</FrameType>

  <Description>Displays a search box that allows users to search for information.</Description>

  <MissingAssembly>Cannot import this Web Part.</MissingAssembly>

  <Assembly>Microsoft.Office.Server.Search, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>

  <TypeName>Microsoft.SharePoint.Portal.WebControls.SearchBoxEx</TypeName>

</WebPart>]]>       

      </AllUsersWebPart>

    </File>

  </Module>

</Elements>

So, as you see there are only two small changes in the attributes of the “Module” and “File” attributes:

  • the first one is the adding of the “SetupPath” attribute to the “Module” element and removing the existing “Path” attribute
  • the second one is the changing of the value of the “Path” attribute of the “File” element to “default.aspx”

The idea of these two attributes is that under this path “14\Template\SiteTemplates\SPS\default.aspx” there is a standard SharePoint file which is exactly a template redirection page, exactly the same as the one that I showed above. This means that instead of the “aspx” file in the feature (which can be removed from the feature actually) an existing standard SharePoint file will be used for the provisioning of the publishing page. The other positive thing is that the cumbersome “MetaInfo” property is also not necessary since the “SetupPath” attribute of the “Module” element achieves the same result much more elegantly.

And several words as a small conclusion – though the syntax of the standard SharePoint feature elements seem the same there’re certain differences in the way the SharePoint artifacts get provisioned in farm and sandbox solutions. So, I guess, we can expect similar smaller or bigger surprises with the new SharePoint 2010 and especially with the new sandbox solutions.

23 comments:

  1. Thanks for sharing it.

    ReplyDelete
  2. Hi there,
    I have added the ListView web part successfully but how to control what fields to show up on the list view??
    I have used below code but still no luck. Could you help?

    <View List="0GeneralInformationandReports" DisplayName="0GeneralInformationandReports" DefaultView="FALSE" BaseViewID="0" Type="HTML" WebPartOrder="0"
    WebPartZoneID="bottomLeft_LeftZone" ContentTypeID="0x" ID="g_6A655C23_9AED_481A_A8CC_415DA52124AF" Hidden="FALSE">
    <![CDATA[<webParts>
    <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
    <metaData>
    <type name="Microsoft.SharePoint.WebPartPages.XsltListViewWebPart, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
    </metaData>
    <data>
    <properties>
    <property name="Title" type="string" />
    <property name="Height" type="string" >215px</property>
    <property name="ListName" type="string">{$ListId:0GeneralInformationandReports;}</property>
    <property name="WebId" type="System.Guid, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">00000000-0000-0000-0000-000000000000</property>
    <property name="ListUrl" type="string" null="true" />
    <property name="ListId" type="System.Guid, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">$ListId:0GeneralInformationandReports;</property>
    <property name="TitleUrl" type="string">../0GeneralInformationandReports</property>
    <property name="CatalogIconImageUrl" type="string">/_layouts/images/itdl.png</property>
    <property name="XmlDefinition" type="string">&lt;View Name="{6A655C23-9AED-481A-A8CC-415DA52124AF}" MobileView="TRUE" Type="HTML" Hidden="TRUE" DisplayName="" Url="default.aspx" Level="2" BaseViewID="0" ContentTypeID="0x" ImageUrl="/_layouts/images/dlicon.png"&gt;&lt;Query/&gt;&lt;ViewFields&gt;&lt;FieldRef Name="LinkFilenameNoMenu"/&gt;&lt;/ViewFields&gt;&lt;/View&gt;</property>
    <property name="MissingAssembly" type="string">Cannot import this Web Part.</property>
    <property name="SelectParameters" type="string" />
    </properties>
    </data>
    </webPart>
    </webParts>]]>
    </View>

    The <XmlDefinition> has the <ViewFields> section which controls what columns needs to display. But it is not working.

    ReplyDelete
  3. Hi Praveen,
    Unfortunately the options that you have with the declarative approach using the "View" element are quite limited. Actually, you can use only the "BaseViewID" attribute if you want to have a different set of view fields. And the value of the "BaseViewID" attribute should correspond to the "ID" of one of the already existing view definitions in the list definition on which your list is based. This means that you will need to have a custom list definition (or at least a list instance feature that uses the CustomSchema attribute with a separate custom schema.xml file) if you don't have a predefined view definition in it with the desired set of view fields. Another possibility, which would do the job but which defeats the whole idea of the declarative approach is to consider using the BinarySerializedWebPart feature element - you can check my posting with the same name and this blog too - http://pholpar.wordpress.com/2010/10/17/decoding-the-content-of-the-binaryserializedwebpart-the-code/

    Greets
    Stefan

    ReplyDelete
  4. Hi Stefan,
    Can you post a sample of how to pass that xml definition. My List definition has the views configured and they do show up when the list is created. But once i deploy the page with that xslt webpart, it shows default or no view inside it.

    ReplyDelete
  5. Hi,
    check this simple example - a file element with three view elements from a sample "Module" element:

    <File Path="ModuleTasks\test.aspx" Url="test.aspx" Type="Ghostable" >
    <View List="Lists/Tasks" BaseViewID="1" Type="HTML" WebPartZoneID="Left" WebPartOrder="1" ></View>
    <View List="Lists/Tasks" BaseViewID="2" Type="HTML" WebPartZoneID="Left" WebPartOrder="2" ></View>
    <View List="Lists/Tasks" BaseViewID="3" Type="HTML" WebPartZoneID="Left" WebPartOrder="3" ></View>
    </File>

    This provisions a simple web part page to a team site with three XLV web parts displaying the data from the Lists/Tasks list. Each XLV web part displays a different view definition from the standard Tasks list. If you check the schema.xml of the Tasks list definition (C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\FEATURES\TasksList\Tasks\schema.xml) you will see that the view definitions with BaseViewID-s 1, 2 and 3 correspond to the "All tasks", "My tasks" and "Due today tasks" standard tasks views. The XLV web parts in the provisioned page will display the view fields that are defined in these view definitions (and the specific filtering and sorting as well).
    The bottom line is that you can indeed use the <webParts> element in the CDATA below the <View> element but apart from the "Title" and "Description" properties of the web part it cannot set any other of its properties (including the XmlDefinition one).
    I see one other thing from your sample CAML - you also set the "ID" attribute of the "View" element - I have had problems with it especially when I apply the same feature many times in one and the same content DB (in one and the same site). If you don't reference this exact GUID somewhere else (e.g. in some view manipulation or web part connecting), you can safely remove it from the CAML.

    ReplyDelete
  6. Hi Stefen,

    I'm trying to move sitepages from one site to another site with contained all the web parts in aspx pages. I am using FileInformation fi=File.openBinaryDirect(serverRelativeURL); I am using stream in that but I could not get

    content in my final string.. Could you please help me in that.

    Thanks in Advance

    ReplyDelete
  7. Hi Stefen,

    I am trying to open master pages from File.OpenDirect() I can open file if it is not master page but If I tried to open master page its through exception as "File Not Found" could you help me in that?

    ReplyDelete
  8. Hi Sangram,
    I saw this - the File.OpenBinaryDirect method uses a WebDav call (simple HTTP POST request with the "Translate: f" HTTP header) to get the contents of the file. I think that there is a built-in security protection for ".master" files (and probably some other file types) and this is why the server returns a 404 error. I tried to find something on Google but without success.
    I would suggest that you try using the old style SOAP web service: _vti_bin/WebPartPages.asmx (SPD 2010 itself uses it to fetch file contents). The SOAP method that does the job is this: WebPartPagesWebService.GetWebPartPage (you can check it in MSDN: http://msdn.microsoft.com/en-us/library/ms772651%28v=office.12%29).
    Let me know if this works for you

    Greets
    Stefan

    ReplyDelete
  9. Thanks Stefen for your valuable reply.. I get the master page from Sharepoint RPC call like fiddler do that. I just stuck when I'm trying to migrate master page from source to target I have ghosted master page migrate with unghosted on target and unghosted css file which i have modified to corev4.css and its saved at _styles folder automatically. I did not find any reference link in master pages for customized css so I can not migrate them as it is on target

    ReplyDelete
  10. Hi Sangram,
    I couldn't quite understand your issue - the corev4.css link gets rendered by the CssLink control in the master page. You can either remove it or use its Alternate property to specify whether the default stylesheet should be rendered.

    Greets
    Stefan

    ReplyDelete
  11. Hi Stefan,

    I tried to create a module just like your example to deploy to a subweb. But I get the following errors from the ULS logs...

    Failed to find a suitable list id for doc 'SitePages/MyHome.aspx' given List template attribute 'Lists/Tasks'

    Failed with 0x80004005 to create the view query or web parts for web /01, site collection (unspecified), URL SitePages/MyHome.aspx

    any help is much appreciated

    ReplyDelete
  12. Hi,

    it looks like the problem is caused by a web part schema that you have in your Module element. Did you try to provision your page file without any web parts in it? If the issue still persists you can paste the whole Module element snippet in a new comment so that I can check it.

    Greets
    Stefan

    ReplyDelete
  13. Stefan, Can I to use Response Redirect to redirect the page using feature ??

    ReplyDelete
  14. Hi Tal,

    you mean whether it is possible to provision a page containing an inline block of code containing a Response.Redirect method call? If this is the case - the answer is yes - you can provision almost any type of file to a SharePoint library regardless of the contents of the file. The important question after that is whether the page containing inline code will work - and the answer here is no - inline code is by default not allowed in pages in the content database. This can be enabled though in the web.config file using the "PageParserPaths" element - check this posting of mine which contains more details about this: http://stefan-stanev-sharepoint-blog.blogspot.nl/2011/10/normally-user-controls.html
    But if you are using a sandbox solution in the first place you will most probably be not allowed to do web.config changes. If this is the case the last option that you can consider is to use the standard publishing redirection page layout which comes OOB with SharePoint.
    Let me know if this was of help for you

    Greets
    Stefan

    ReplyDelete
  15. Hi,

    For Provisioning Publishing page you are using



    <%@ Page="" Inherits="Microsoft.SharePoint.Publishing.TemplateRedirectionPage,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %><%@ Reference="" VirtualPath="~TemplatePageUrl" %><%@ Reference="" VirtualPath="~masterurl/custom.master" %>

    Can you tell me for provisioning webpart page what we have to use?

    Regards,
    Srinu

    ReplyDelete
  16. Hi Srinu,

    you can reference any file (any aspx page) under the standard SharePoint TEMPLATE folder. So, if you want to provision a page as the one used in the standard team template for instance you can use these values of the SetupPath attribute (of the Module element) and the Path attribute (of the File element): "SiteTemplates\STS" and "default.aspx" (note the different subfolder here STS and not SPS as it was in the posting). Note that the concatenated values of these two attributes give you the relative path of the referenced file in the TEMPLATE folder, so you can choose any of the existing files there.
    The web part pages templates available in the standard team site template are available in this subfolder under the TEMPLATE folder: 1033\STS\DOCTEMP\SMARTPGS - the file names go from spstd1.aspx to spstd8.aspx

    Greetings
    Stefan

    ReplyDelete
  17. You saved my life! Thanks a lot! This is sooo good , that ppl like you share this with others

    ReplyDelete
  18. This comment has been removed by the author.

    ReplyDelete
  19. Excellent article. One other thing I came across from another blog is the token for the Pages library created by the publishing feature. I have not tested, but presumably the pages library would not be called 'pages' in site collections from different language packs. Using the token will be safer.

    In the Url attribute of the Module element, use Url="$Resources:osrvcore,List_Pages_UrlName;"

    ReplyDelete
  20. Hi Justin,

    well spotted, thanks for the correction

    Greets
    Stefan

    ReplyDelete
  21. Hi,

    Apparently it is not possible to do:
    <Property Name="PublishingPageLayout" Value="~Site/_catalogs/masterpage/

    i.e. to reference a Page Layout which is deployed into the current SPWeb, rather than the SPSite. Not sure why...

    Pozdrawi ;)

    ReplyDelete