Cleaning up features

If as newcomer you start developing for SharePoint, after a couple of weeks you will face a serious problem. Your development and test machine will perhaps suffocate from the torture of already forgotten, death, cancelled and zombie features, web-parts and virtual directories. The list is by far not complete. So, good manner would be just clean up the garbage immediately after each finished exercise ort test. In order to see the wastes to be cleaned up, SharePoint Manager from codeplex could be your helpful friend.

Let’s see what exactly my previously posted Web-Parts Example leaves behind. First of all note the SilverlightXap virtual folder beneath the RootFolder of my site-collection (Picture 1 – click on the picture to see larger resolution). Next note the Provider- and Consumer-Web-Parts beneath the Web Part Gallery’s Items (Picture 2). Using SharePoint Manager 2010 just right mouse click on the item to be removed and select the delete menu command. The question is however how to do that automatically in code, e.g. when deactivating the feature. If any feature has been deactivated it s clearly not pleasant to offer to any user some zombie Web-Parts coming form those deactivated features.

image
Picture 1
image
Picture 2

While talking about cleanup techniques we have to mention, that physical file-system related contents (application page artifacts) are removed when the solution is retracted. Virtualized contents which are kept in the SharePoint Content Database must be cleaned up manually (programmatically) using feature receivers, especially the “public override void FeatureDeactivating(…)” method.

Here Code 1 presents the cleanup solution which is very specific for this feature, meaning all the targeted items (virtual folders an web part names) are named exactly; see the highlighted parts. If you extend your original feature with a couple of new web parts or if you rename some of the feature contents, the CleanUpFeature method called within FeatureDeactivated event handler will need appropriate changes in order to reflect those changes. 

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    FeatureCleanupBase.CleanUpFeature(properties);
}
public class FeatureCleanupBase
{
    public static void CleanUpFeature(SPFeatureReceiverProperties roperties)
    {
        using (SPSite site = properties.Feature.Parent as SPSite)
        {
            SPFolder folder = site.RootWeb.GetFolder(“SilverlightXap”);
            if (folder != null)
                folder.Delete();

            List<string> wpNames = new List<string>();
            wpNames.Add(“DetailsView2WebPart”);
            wpNames.Add(“Scheduler2Overview”);
            using (SPWeb web = site.RootWeb)
            {
                SPList list = web.Lists[“Web Part Gallery”];
                for (int i = list.ItemCount – 1; i >= 0; i–)
                {
                    string wpName = list.Items[i].Name;
                    wpName = wpName.Substring(0, wpName.IndexOf(‘.’));

                    foreach (string name in wpNames)
                    {
                        // delete web parts that have been added
                        if (wpName == name)
                        {
                            list.Items[i].Delete();
                        }
                    }
} } }       }   }

Code 1

So the question is whether we could figure some generic algorithm, which will do the job and cleanup virtual directories and web-parts regardless what the exact content and name of the feature is? The answer is yes, however with one particular constraint. Such algorithm will work only for web-farm solutions. Let’s see why.

The core of such a generic cleanup solution reflects the very fabric of any feature. The feature structure is described using a Feature-Manifest, which is named Feature.xml. In web-farm solutions this file is available on the file-system (Picture 3) and it’s content (Picture 4) could be read an analyzed in order to know what exactly has to be removed or cleaned up.  

image

Picture 3

image

Picture 4

The manifest contains a few <ElementFile> elements which are there in order to reflect the feature’s presence on the physical file-system (Picture 3). We learned, that these directories will be removed by default when the solution is retracted, so why bother about? The cheating idea is however to look only for those Location-Attribute values which have the “.webpart” substring. This information could be used to remove the Web-Parts from the Gallery without explicitly declaring the names in code. Just read simply the Feature.xml file, search for “.webpart”s and try removing them from gallery (Code 2). Take a look on the LINQ to XML statement doing this kind of selection.

private static void IterateOverFeatureWebPart(
            SPWeb currentSite,
            string elementsPath,
            SPFeatureReceiverProperties properties)
{
    List<string> webPartNames = new List<string>();
    XDocument elementsXml = XDocument.Load(elementsPath);

    // get each Elemnts.xml’s relative Location 
    IEnumerable<string> elementLocation = from module in elementsXml.Root
              .Elements(sharePointNamespace + “ElementManifests”)
              .Elements(sharePointNamespace + “ElementFile”)
         where module.Attribute(“Location”).Value.Contains(“.webpart”)
         select module.Attribute(“Location”).Value;

        // Iterate now over all Locations and
        // try delete WebParts from Gallery
        foreach (string location in elementLocation)
        {
            string webpartName = location.Substring(0, location.IndexOf(‘\\’));
            if (webpartName.Length > 0)
            {
                webPartNames.Add(webpartName);
            }
        }
        DeleteFeatureWebPart(properties, webPartNames);
}

 
private static void DeleteFeatureWebPart(
            SPFeatureReceiverProperties properties,
            List<string> webPartNames)
{
        SPSite site = null;
        try
        {
            site = properties.Feature.Parent as SPSite;
            using (SPWeb web = site.RootWeb)
            {
                SPList list = web.Lists[“Web Part Gallery”];
                // go through the items in reverse
                for (int i = list.ItemCount – 1; i >= 0; i–)
                {
                // format name to look like a feature name
                string wpName = list.Items[i].Name;
                wpName = wpName.Substring(0, wpName.IndexOf(‘.’));
                    foreach (string name in webPartNames)
                    {
                        // delete web parts that have been added
                        if (wpName == name)
                            list.Items[i].Delete();
                    }
                }
            }
        }
        finally
        {
            if (site != null)
                site.Dispose();
        }
}

Code 2

So we have resolved half of the stated problems. How to remove virtual directories? The most simplistic idea could be either selecting the very same  <ElementFile> elements and try to remove the folders assuming they exist virtually as well or focus on the <ElementManifest> elements (Picture 4), open each referenced Element.xml (Picture 5) then look inside for <Module> elements knowing, that Modules are good candidates and try remove the folder declared in the Module’s Name-Attribute (Code 3). I believe in most of the cases the simplistic approach will do fine. Renaming the Module will be reflected anyway both in the Feature.xml and in Elements.xml files. So at this time I do not see any issue why the second approach would be better. I’m providing this solution due to the reason that I have found some very similar approach in the web.

image

Picture 5

private static void IterateOverFeatureFile(
            SPWeb currentSite,
            string elementsPath,
            SPFeatureReceiverProperties properties)
{
        XDocument elementsXml = XDocument.Load(elementsPath);

        // get each Elemnts.xml’s relative Location
        var elementLocation = from module in elementsXml.Root
               .Elements(sharePointNamespace + “ElementManifests”)
               .Elements(sharePointNamespace + “ElementManifest”)
                select new
                {
                    Location = module.Attribute(“Location”).Value
                };

        // Iterate now over all Locations and
        // try delete Folders containing a Module
        foreach (var location in elementLocation)
        {
            string elementsPath1 = string.Format(@”{0}\FEATURES\{1}\{2}”,
              SPUtility.GetGenericSetupPath(“Template”),
              properties.Definition.DisplayName, location.Location);
              DeleteFeatureFolders(currentSite, elementsPath1);
        }
}

 
private static void DeleteFeatureFolders (SPWeb currentSite, string elementsPath)
{
        XDocument elementsXml = XDocument.Load(elementsPath);

        // get each module name and the files in it
        var moduleList = from module in elementsXml
                         .Root.Elements(sharePointNamespace + “Module”)
                         select module;

        foreach (var module in moduleList)
        {
            SPFolder file = currentSite
                    .GetFolder(string.Format(“{0}/{1}”,
                            currentSiteUrl,
                            module.Attribute(“Name”).Value));
            if (file.Exists)
                file.Delete();
        }
}
  

Code 3

The complete solution is provided in the previously posted article. Here is however the link to download the classes doing Feature-Cleanup: 

http://cid-8d365142bc4869ab.office.live.com/self.aspx/.Documents/FeatureCleanup.zip

Advertisements
This entry was posted in SharePoint 2010. Bookmark the permalink.

One Response to Cleaning up features

  1. Tambor says:

    Hello,
    Very useful post !!! I didn’t manage to download source code, could you help me please ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s