Tuesday, January 17, 2012

Simple Feature Files Cleanup using Extension Methods

As every seasoned SharePoint developer knows, deactivating a feature does not remove the files deployed by that feature. The deployed masterpages, web part pages, wiki pages, page layouts, web-part definitions, styling artifacts, etc files will stay in the target libraries - and they will not be overwritten on feature activation. Don't let Visual Studio 2010 trick you into believing otherwise.

You have to delete those deployed files yourself in the FeatureDeactivating event. The classic approach is to delete the files one-by-one, but this is tedious and error-prone. The following is a set of extension methods that allows you to simply do this:

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
     SPSite site = (SPSite) properties.Feature.Parent;
     properties.Definition.DeleteFeatureFiles("MasterPages", site.RootWeb);
     properties.Definition.DeleteFeatureWebPartFiles(site.RootWeb);
}

The code is an adaptation of Corey Roth's LINQ to XML and Deleting Files on Feature Deactivation, using extension methods and supporting cleanup of specific feature modules and all feature web-parts.

namespace Puzzlepart.SharePoint.Core.SPExtentions
{
    public static class SPFeatureDefinitionExtentions
    {
        public class Module
        {
            public string Name { get; set; }
            public string Path { get; set; }
            public List<string> Files { get; set; }
        }
 
 
        public static void DeleteFeatureFiles
(this SPFeatureDefinition spFeatureDefinition, string moduleName, SPWeb web)
        {
            List<Module> modules = GetModuleFiles(spFeatureDefinition, moduleName);
            foreach (Module module in modules)
            {
                DeleteModuleFiles(module, web);
            }
        }
 
 
        public static void DeleteFeatureWebPartFiles
(this SPFeatureDefinition spFeatureDefinition, SPWeb web)
        {
            List<Module> modules = GetAllModuleFiles(spFeatureDefinition);
            foreach (Module module in modules)
            {
                if (string.Compare(module.Path, "_catalogs/wp"
StringComparison.CurrentCultureIgnoreCase) == 0)
                    DeleteModuleFiles(module, web);
            }
        }
 
 
        private static List<Module> GetModuleFiles
(SPFeatureDefinition spFeatureDefinition, string moduleName)
        {
            string elementsPath = string.Format(@"{0}\FEATURES\{1}\{2}\Elements.xml"
SPUtility.GetGenericSetupPath("Template"), 
spFeatureDefinition.DisplayName, moduleName);
            XDocument elementsXml = XDocument.Load(elementsPath);
            XNamespace sharePointNamespace = "http://schemas.microsoft.com/sharepoint/";
 
            // get each module name and the files in it
            var moduleList =
                from module in elementsXml.Root.Elements(sharePointNamespace + "Module")
                select new
                {
                    Name = (module.Attributes("Name").Any()) 
? module.Attribute("Name").Value : null,
                    ModuleUrl = (module.Attributes("Url").Any()) 
? module.Attribute("Url").Value : null,
                    Files = module.Elements(sharePointNamespace + "File")
                };
 
            List<Module> modules = new List<Module>();
            // iterate through each module with files
            foreach (var module in moduleList)
            {
                Module m = new Module()
                               {
                                   Name = module.Name,
                                   Path = module.ModuleUrl
                               };
                List<string> files = new List<string>();
                foreach (var fileElement in module.Files)
                {
                    string filename = (fileElement.Attributes("Name").Any()) 
? fileElement.Attribute("Name").Value : fileElement.Attribute("Url").Value;
                    files.Add(filename);
                }
                m.Files = files;
                modules.Add(m);
            }
            return modules;
        }
 
        private static void DeleteModuleFiles(Module module, SPWeb web)
        {
            foreach (string filename in module.Files)
            {
                if (!string.IsNullOrEmpty(module.Path))
                    web.GetFile(string.Format("{0}/{1}", module.Path, filename)).Delete();
                else
                    web.Files.Delete(filename);
            }
        }
 
        private static List<Module> GetAllModuleFiles
(SPFeatureDefinition spFeatureDefinition)
        {
            var moduleList = new List<Module>();
 
            string modulesPath = string.Format(@"{0}\FEATURES\{1}\", 
SPUtility.GetGenericSetupPath("Template"), 
spFeatureDefinition.DisplayName);
            DirectoryInfo folder = new DirectoryInfo(modulesPath);
            foreach (DirectoryInfo moduleFolder in folder.GetDirectories())
            {
                moduleList.AddRange(GetModuleFiles(spFeatureDefinition, moduleFolder.Name));
            }
 
            return moduleList;
        }
 
    }
}

Note that page layouts cannot simply be deleted if they are in use. Use code to revert the "GhostableInLibrary" files to the uncustomized (ghosted) feature files on disk in the SharePoint root [14].

1 comment:

wildcard ssl said...

Thank you so much for script as we had been search engine it. But finally your blog has helped us.