Inconvenient programmatically exporting Web Parts


Programmatically provisioning Web Part instances is an important piece of structured and repeatable deployment of SharePoint solutions. Preferably you would like to be able to deploy preconfigured Web Parts at particular places in your SharePoint solution. In order to do that you need two at least things: the target location where do you want to deploy the Web Part instance and the preconfigured Web Part itself. SharePoint Web UI allows you to export Web Parts. While it’s definitely doable to export a couple of them manually, wouldn’t it be better if we could automate the process?

The good news is that the SharePoint API provides you with the SPLimitedWebPartManager.ExportWebPart method which allows you to export Web Parts from your pages to XML in a straight-forward way:

using (SPLimitedWebPartManager wpMgr =
    SPContext.Current.Web.GetLimitedWebPartManager(
    "Pages/mypage.aspx", PersonalizationScope.Shared))
{
    UTF8Encoding encoding = new UTF8Encoding(true);
    MemoryStream stream = new MemoryStream();
    StreamWriter w = new StreamWriter(stream);
    XmlTextWriter xwTmp = new XmlTextWriter(w);
    wpMgr.ExportWebPart(wpMgr.WebParts[0], xwTmp);
    xwTmp.Flush();
    xwTmp.Close();

    string xml = new string(encoding.GetChars(
        (w.BaseStream as MemoryStream).ToArray()));
}

While the above code snippet works in the most situations, there are some inconvenient exceptions.

It all depends on the context

A while I was working with programmatically provisioning instances of the Content Query Web Part (CQWP). Back then I found out that you need to provide context information in order to provision a CQWP. For most of the time I’ve been working with exporting configured Web Parts using the SharePoint Web UI. As it’s web based the context information is available within every operation triggered from that UI.

Just recently however I started working on a custom STSADM command which would allow you to export configured Publishing Pages including both the content and the web parts. I have found out that the Web Part import process is not the only one depending on the context information.

While both importing and exporting (subclasses) Content Query Web Parts depend on the context information there is a little difference. If all you do is to import an instance of CQWP from XML, it’s sufficient to provide the context information just before adding the Web Part using the SPLimitedWebPartManager.AddWebPart method:

using (SPLimitedWebPartManager wpMgr =
    SPContext.Current.Web.GetLimitedWebPartManager(
    "Pages/mypage.aspx", PersonalizationScope.Shared))
{
    // provide context information
    if (HttpContext.Current == null)
    {
        HttpRequest request =
            new HttpRequest("", wpMgr.Web.Url, "");
        HttpContext.Current = new HttpContext(request,
            new HttpResponse(new StringWriter()));
        HttpContext.Current.Items["HttpHandlerSPWeb"] =
            wpMgr.Web;
    }

    string errors = String.Empty;
    string webPartXml =
        "..."; // string with the exported Web Part XML
    XmlReader xr = XmlReader.Create(
        new StringReader(webPartXml));
    System.Web.UI.WebControls.WebParts.WebPart webPart =
        wpMgr.ImportWebPart(xr, out errors);
    string zoneId = "SomeZone";
    int zoneIndex = 0;
    wpMgr.AddWebPart(webPart, zoneId, zoneIndex);
}

While exporting the CQWP instances however you need to provide the context information before instantiating the SPLimitedWebPartManager:

// provide context information
if (HttpContext.Current == null)
{
    HttpRequest request =
        new HttpRequest("", SPContext.Current.Web.Url, "");
    HttpContext.Current = new HttpContext(request,
        new HttpResponse(new StringWriter()));
    HttpContext.Current.Items["HttpHandlerSPWeb"] =
        SPContext.Current.Web;
}

using (SPLimitedWebPartManager wpMgr =
    SPContext.Current.Web.GetLimitedWebPartManager(
    "Pages/mypage.aspx", PersonalizationScope.Shared))
{
    UTF8Encoding encoding = new UTF8Encoding(true);
    MemoryStream stream = new MemoryStream();
    StreamWriter w = new StreamWriter(stream);
    XmlTextWriter xwTmp = new XmlTextWriter(w);
    wpMgr.ExportWebPart(wpMgr.WebParts[0], xwTmp);
    xwTmp.Flush();
    xwTmp.Close();

    string xml = new string(encoding.GetChars(
            (w.BaseStream as MemoryStream).ToArray()));
}

If you try to export an instance of a (subclassed) CQWP without the context information you will find the following XML in the output:

<webParts>
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
    <metaData>
      <type name="Microsoft.SharePoint.WebPartPages.ErrorWebPart,
            Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
            PublicKeyToken=71e9bce111e9429c" />
      <importErrorMessage>
        Cannot import this Web Part.</importErrorMessage>
    </metaData>
    <data>
      <properties>
        <property name="AllowClose" type="bool">True</property>
        <property name="Width" type="string" />
        <property name="AllowMinimize" type="bool">True</property>
        <property name="AllowConnect" type="bool">True</property>
        <property name="ChromeType" type="chrometype">
          Default</property>
        <property name="TitleIconImageUrl" type="string" />
        <property name="Description" type="string" />
        <property name="Hidden" type="bool">False</property>
        <property name="TitleUrl" type="string" />
        <property name="AllowEdit" type="bool">True</property>
        <property name="Height" type="string" />
        <property name="MissingAssembly" type="string">
          Cannot import this Web Part.</property>
        <property name="HelpUrl" type="string" />
        <property name="Title" type="string" />
        <property name="CatalogIconImageUrl" type="string" />
        <property name="Direction" type="direction">NotSet</property>
        <property name="ChromeState" type="chromestate">
          Normal</property>
        <property name="AllowZoneChange" type="bool">True</property>
        <property name="AllowHide" type="bool">True</property>
        <property name="HelpMode" type="helpmode">Modeless</property>
        <property name="ExportMode" type="exportmode">All</property>
      </properties>
    </data>
  </webPart>
</webParts>

The above output is a standard output for a Web Part that cannot be exported. The worst part is that you can actually import the above XML as a Web Part and instantiate it on your page. Don’t expect to see much though.

Summary

Being able to programmatically export preconfigured Web Parts allows you to improve your process of structured and repeatable deployment. Depending on the kind of solution you’re working on you should know that in some situations a straight-forward call to the SPLimitedWebPartManager.ExportWebPart method is not enough to export the Web Part to XML. Some Web Part like the Content Query Web Part require you to provide context information before starting the export process.

Technorati Tags: SharePoint, SharePoint 2007, WSS 3.0, MOSS 2007, CQWP, Content Query Web Part

Others found also helpful: