Preventing provisioning duplicate Web Parts instances on Feature reactivation


Recently I wrote about various approaches to provisioning Web Part instances in a structured and repeatable way. One of the approaches I have mentioned was using the AllUsersWebPart element within Feature manifest. While being manageable and flexible this approach has one big downside: it causes provisioning duplicate instances after the Feature has been reactivated (either by Activate-Deactivate-Activate or Activate using the -force parameter). In this article I present some possible approaches to prevent it and make your Feature provision always only one instance of each Web Part.

Removing the duplicated Web Parts manually upon the Feature activation

After the Feature has been activated the information about Web Parts which should be included within a Page Layout can be accessed using the Web Part Maintenance View of a Page Layout. You can access it by navigating to the Master Page Gallery, opening your Page Layout by simply clicking on it and append ?contents=1 to the url. Web Part Page Maintenance Using the presented information you can remove the duplicated Web Part instances without customizing the Page Layout. Using this approach to remove the duplicate Web Part instances is really straight forward. It is a plausible concept unless you’re not allowed to do anything manual on the production environment.

Automatically removing the duplicated Web Parts instances using Feature Receiver

The duplicated Web Parts instances are being provisioned during the Feature activation. Unfortunately the WSS team didn’t provide an asynchronous event to plug into the Feature activation process and control it in any way. Instead there is a FeatureActivated event which fires right after the Feature has been activated. While it might seem that it’s too late to do the cleaning there are still some possibilities to avoid displaying the duplicated Web Parts on Publishing Pages. While thinking on a solution for this challenge I think about two approaches: an easy one but less foolproof and a more general one removing only the Web Parts defined by the activated Feature.

Removing all duplicated Web Part instances

The first approach you might consider is iterating through the WebParts collection of the particular Page Layout, comparing the XML of each one of them and removing any duplicate found. You could do this for example like that:

using (SPSite site = new SPSite("http://sharepoint"))
{
  SPList list = site.GetCatalog(SPListTemplateType.MasterPageCatalog);
  SPListItemCollection items = list.Items;
  List<string> webParts = new List<string>();

  // find the right Page Layout
  foreach (SPListItem item in items)
  {
    if (item.Name.Equals("CustomPageLayout.aspx",
      StringComparison.CurrentCultureIgnoreCase))
    {
      SPFile file = item.File;
      // get the Web Part Manager for the Page Layout
      SPLimitedWebPartManager wpm =
        file.GetLimitedWebPartManager(PersonalizationScope.Shared);
      // iterate through all Web Parts and remove duplicates
      while (wpm.WebParts.Count > 0)
      {
        StringBuilder sb = new StringBuilder();
        XmlWriterSettings xws = new XmlWriterSettings();
        xws.OmitXmlDeclaration = true;
        XmlWriter xw = XmlWriter.Create(sb, xws);
        System.Web.UI.WebControls.WebParts.WebPart wp =
          wpm.WebParts[0];
        wpm.ExportWebPart(wp, xw);
        xw.Flush();
        string md5Hash = getMd5Hash(sb.ToString());
        if (webParts.Contains(md5Hash))
          wpm.DeleteWebPart(wp);
        else
          webParts.Add(md5Hash);
      }
    }
  }
}

The first step is to get the reference to the Page Layout. Then you will obtain a reference to the SPLimitedWebPartManager of the given Page Layout which contains information about all Web Parts attached to that Page Layout. Last but not least you will iterate through the WebPart collection and remove any duplicate Web Parts found. To do the comparison I have used an MD5 hash which I store in a list. I compute the hash using the method presented on the MSDN site. In my opinion the above method will work in almost every case because you are not likely going to display two exactly the same Web Parts on one page. It’s biggest limitation is the lack of distinction between Web Parts provisioned by different Features. However, if your main goal is removing the duplicate Web Parts, it might be just something for you.

Removing only the duplicate instances of the Web Parts provisioned by the currently activated Feature

While trying to prevent a Feature to provision multiple instances of the same Web Part automatically there is another approach, than the one above, you could consider. On one hand it is more complicated. On the other hand it provides you more control about what you are doing and thanks to it abstract character it is reusable in Features without any need of modification. Because of its complexity I’d rather focus on the concept itself rather than providing the exact code. The whole concept bases, just like before, on comparing Web Parts to determine whether they should be removed or not. For this purpose you could reuse the code part responsible for MD5 hash generation based on the Web Part definition as presented above. The first step would be obtaining information about the Web Parts which belong to the Feature which just has been activated. You would therefore obtain the Feature definition, parse the XML file to get the references to all Element files and then parse these to get the information about Web Parts. You would parse this information together with the information about Page Layouts which reference these Web Parts. After having that information stored you would then go to iterating through the available Page Layouts - just like in the previous approach. The difference here would be the comparison which would now be done based on the information stored earlier rather than in the Page Layouts itself. As I’ve already mentioned this approach is a bit more complicated than the one before. The only reason you might want to choose it is the extra bit of control it gives you about the comparison process.

Summary

Provisioning Web Part instances using the AllUsersWebPart element is, in my opinion, one of the most structured and repeatable approaches for deploying Web Part instances. While it introduces a minor challenge of provisioning duplicated Web Part instances upon Feature reactivation, it still remains a plausible concept for many scenarios. Gaining extra piece of control on provisioning Web Part instances might be required if you want to use this approach and still keep the ability to reactivate the Feature when necessary without any impact on your deployment package.

Technorati Tags: SharePoint, SharePoint 2007, MOSS 2007, WSS

Others found also helpful: