Replace SharePoint web parts with add-in parts in farm solutions

You can use the transformation process to replace SharePoint web parts with add-in parts on pages by using CSOM to find and remove specific web parts, and then adding the new add-in parts.

Important

Farm solutions cannot be migrated to SharePoint Online. By applying the techniques and code described in this article, you can build a new solution with similar functionality that your farm solutions provide, which can then be deployed to SharePoint Online. After you apply these techniques, your pages will be updated to use add-in parts, which can then be migrated to SharePoint Online.

The code in this article requires additional code to provide a fully working solution. For example, this article does not discuss how to authenticate to Office 365, how to implement required exception handling, and so on. For additional code samples, see the Office 365 Developer Patterns and Practices project.

Note

The code in this article is provided as-is, without warranty of any kind, either express or implied, including any implied warranties of fitness for a particular purpose, merchantability, or non-infringement.

To replace web parts with new add-in parts:

  1. Export the new add-in part to get the add-in part definition.

  2. Create the new add-in part on the page by using the add-in part definition.

  3. Find all pages with web parts to be replaced, and then remove the web parts.

Before you begin

Before performing the steps in this article to replace your web parts with add-in parts, ensure that:

Export the new add-in part

To use CSOM to replace a web part with an add-in part, you need to get the add-in part definition by exporting the add-in part. To export the add-in part to get the add-in part's definition:

  1. Open your SharePoint site and choose Settings, or the gear icon, and then choose Edit page.

  2. On the ribbon, choose INSERT > Add-in Part.

  3. Choose your add-in part, and then choose Add.

  4. When the add-in part is added to your page, choose the down arrow in the top-right corner of the add-in part, and then choose Edit web part.

  5. In Advanced, in Export Mode, choose Export all data, and then choose OK.

  6. When you return to the edit page with your add-in part displayed, choose the down arrow in the top-right corner of the add-in part, and then choose Export.

  7. Choose Save.

  8. After the download completes, choose Open folder.

  9. Open Notepad, and then choose File > Open.

  10. Choose the add-in part file that you downloaded, and then choose Open to view the add-in part definition.

<webParts>
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
    <metaData>
      <type name="Microsoft.SharePoint.WebPartPages.ClientWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      <importErrorMessage>Cannot import this web part.</importErrorMessage>
    </metaData>
    <data>
      <properties>
        <property name="TitleIconImageUrl" type="string" />
        <property name="Direction" type="direction">NotSet</property>
        <property name="ExportMode" type="exportmode">All</property>
        <property name="HelpUrl" type="string" />
        <property name="Hidden" type="bool">False</property>
        <property name="Description" type="string">WelcomeAppPart Description</property>
        <property name="FeatureId" type="System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">0b846986-3474-4f1a-93cf-b7817ef057f9</property>
        <property name="CatalogIconImageUrl" type="string" />
        <property name="Title" type="string">WelcomeAppPart</property>
        <property name="AllowHide" type="bool">True</property>
        <property name="ProductWebId" type="System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">741c5404-f43e-4f01-acfb-fcd100fc7d24</property>
        <property name="AllowZoneChange" type="bool">True</property>
        <property name="TitleUrl" type="string" />
        <property name="ChromeType" type="chrometype">Default</property>
        <property name="AllowConnect" type="bool">True</property>
        <property name="Width" type="unit" />
        <property name="Height" type="unit" />
        <property name="WebPartName" type="string">WelcomeAppPart</property>
        <property name="HelpMode" type="helpmode">Navigate</property>
        <property name="AllowEdit" type="bool">True</property>
        <property name="AllowMinimize" type="bool">True</property>
        <property name="ProductId" type="System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">0b846986-3474-4f1a-93cf-b7817ef057f8</property>
        <property name="AllowClose" type="bool">True</property>
        <property name="ChromeState" type="chromestate">Normal</property>
      </properties>
    </data>
  </webPart>
</webParts>

Create the new add-in part on the page by using the add-in part definition

To use the add-in part definition in your CSOM code:

  • Replace all double quotes (") with a pair of double quotes ("") in the add-in part definition.

  • Assign the add-in part definition to a string that will be used in your CSOM code.

private const string appPartXml = @"<webParts>
  <webPart xmlns=""http://schemas.microsoft.com/WebPart/v3"">
    <metaData>
      <type name=""Microsoft.SharePoint.WebPartPages.ClientWebPart, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"" />
      <importErrorMessage>Cannot import this web part.</importErrorMessage>
    </metaData>
    <data>
      <properties>
        <property name=""TitleIconImageUrl"" type=""string"" />
        <property name=""Direction"" type=""direction"">NotSet</property>
        <property name=""ExportMode"" type=""exportmode"">All</property>
        <property name=""HelpUrl"" type=""string"" />
        <property name=""Hidden"" type=""bool"">False</property>
        <property name=""Description"" type=""string"">WelcomeAppPart Description</property>
        <property name=""FeatureId"" type=""System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"">0b846986-3474-4f1a-93cf-b7817ef057f9</property>
        <property name=""CatalogIconImageUrl"" type=""string"" />
        <property name=""Title"" type=""string"">WelcomeAppPart Title</property>
        <property name=""AllowHide"" type=""bool"">True</property>
        <property name=""ProductWebId"" type=""System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"">717c00a1-08ea-41a5-a2b7-4c8f9c1ce770</property>
        <property name=""AllowZoneChange"" type=""bool"">True</property>
        <property name=""TitleUrl"" type=""string"" />
        <property name=""ChromeType"" type=""chrometype"">Default</property>
        <property name=""AllowConnect"" type=""bool"">True</property>
        <property name=""Width"" type=""unit"" />
        <property name=""Height"" type=""unit"" />
        <property name=""WebPartName"" type=""string"">WelcomeAppPart</property>
        <property name=""HelpMode"" type=""helpmode"">Navigate</property>
        <property name=""AllowEdit"" type=""bool"">True</property>
        <property name=""AllowMinimize"" type=""bool"">True</property>
        <property name=""ProductId"" type=""System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"">0b846986-3474-4f1a-93cf-b7817ef057f8</property>
        <property name=""AllowClose"" type=""bool"">True</property>
        <property name=""ChromeState"" type=""chromestate"">Normal</property>
      </properties>
    </data>
  </webPart>
</webParts>";

Find all pages with web parts to be replaced, and then remove the web parts

Find the web parts to replace

ReplaceWebPartsWithAppParts starts the process of finding the web parts to be replaced by:

  1. Getting some properties from the Web to find the Pages library on the site.

  2. In the Pages library, getting the list items and the file associated with the list items.

  3. For each list item returned from the Pages library, calling FindWebPartToReplace.

     protected void ReplaceWebPartsWithAppParts(object sender, EventArgs e)
     {
         var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);
         using (var clientContext = spContext.CreateUserClientContextForSPHost())
         {
             Web web = clientContext.Web;
             // Get properties from the Web.
             clientContext.Load(web,
                                 w => w.ServerRelativeUrl,
                                 w => w.AllProperties);
             clientContext.ExecuteQuery();
             // Read the Pages library name from the Web properties.
             var pagesListName = web.AllProperties["__pageslistname"] as string;
    
             var list = web.Lists.GetByTitle(pagesListName);
             var items = list.GetItems(CamlQuery.CreateAllItemsQuery());
             // Get the file associated with each list item.
             clientContext.Load(items,
                                 i => i.Include(
                                         item => item.File));
             clientContext.ExecuteQuery();
    
             // Iterate through all pages in the Pages list.
             foreach (var item in items)
             {
                 FindWebPartForReplacement(item, clientContext, web);
             }
         }
     }
    

Find web parts to be replaced with new add-in part

FindWebPartToReplace finds web parts that should be replaced with the new add-in part by:

  1. Setting page to the file associated with the list item returned from the Pages library.

  2. Using LimitedWebPartManager to find all web parts on a specific page. The title of each web part is also returned in the lambda expression.

  3. For each web part in the web part manager's WebParts property, determining whether the web part's title and the oldWebPartTitle variable, which is set to the title of the web part you are replacing, are equal. If the web part title and oldWebPartTitle are equal, call ReplaceWebPart; otherwise continue with the next iteration of the foreach loop.

     private static void FindWebPartToReplace(ListItem item, ClientContext clientContext, Web web)
     {
         File page = item.File;
         // Requires Full Control permissions on the Web.
         LimitedWebPartManager webPartManager = page.GetLimitedWebPartManager(PersonalizationScope.Shared);
         clientContext.Load(webPartManager,
                             wpm => wpm.WebParts,
                             wpm => wpm.WebParts.Include(
                                                 wp => wp.WebPart.Title));
         clientContext.ExecuteQuery();
    
         foreach (var oldWebPartDefinition in webPartManager.WebParts)
         {
             var oldWebPart = oldWebPartDefinition.WebPart;
             // Modify the web part if we find an old web part with the same title.
             if (oldWebPart.Title != oldWebPartTitle) continue;
    
             ReplaceWebPart(web, item, webPartManager, oldWebPartDefinition, clientContext, page);
         }
     }
    

Replace the new web part

ReplaceWebPart replaces the new web part by:

  1. Using LimitedWebPartManager.ImportWebPart to convert the XML string in appPartXml into a WebPartDefinition.

  2. Using LimitedWebPartManager.AddWebPart to add a web part to a page in a specific web part zone. Ensure that you pass the zoneId to AddWebPart, otherwise an exception will be thrown when ExecuteQuery runs.

  3. Using WebPartDefinition.DeleteWebPart to delete the old web part from the page.

     private static void ReplaceWebPart(Web web, ListItem item, LimitedWebPartManager webPartManager,
           WebPartDefinition oldWebPartDefinition, ClientContext clientContext, File page)
       {
    
           // Create a web part definition using the XML string.
           var definition = webPartManager.ImportWebPart(appPartXml);
           webPartManager.AddWebPart(definition.WebPart, "RightColumn", 0);
    
           // Delete the old web part from the page.
           oldWebPartDefinition.DeleteWebPart();
           clientContext.Load(page,
                               p => p.CheckOutType,
                               p => p.Level);
    
           clientContext.ExecuteQuery();
    
       }
    

See also