LINQ to XML and Deleting Files on Feature Deactivation

Posted Monday, February 16, 2009 5:15 PM by CoreyRoth

Using the Module element of a Feature has always been a great way to deploy a file.  Unfortunately, the main issue with this method is that it doesn’t clean up after itself.  How many times have you seen a site like this when working with features and the Module element?

FeatureFail

This is because Feature Activation doesn’t remove anything.  The easiest way to take care of this is write code to either remove the web parts from the zones or even easier just delete the page and assume activating the feature again is going to recreate it.  Before, when I wrote code like this, it was always hard coded to delete a specific file or whatever, but now I have a better approach.  I am going to use LINQ to XML to iterate through the Elements.xml file of my feature, find any files that were created and then delete them.  Of course, you can do this without LINQ to XML, but it just makes it easier.

Before, we begin, consider a typical elements.xml file that looks like the one below.  It deploys two files named default.aspx.  One in the root and one in a folder called Test.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <Module Name="TestModule" Path="" Url="">

    <File Name="default.aspx" Url="default.aspx" IgnoreIfAlreadyExists="FALSE" NavBarHome="True">

      <AllUsersWebPart WebPartZoneID="Right" WebPartOrder="1">

        <![CDATA[

                  <WebPart xmlns="http://schemas.microsoft.com/WebPart/v2" xmlns:iwp="http://schemas.microsoft.com/WebPart/v2/Image">

                        <Assembly>Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>

                        <TypeName>Microsoft.SharePoint.WebPartPages.ImageWebPart</TypeName>

                        <FrameType>None</FrameType>

                        <Title>$Resources:wp_SiteImage;</Title>

                        <iwp:ImageLink>/_layouts/images/homepage.gif</iwp:ImageLink>

                        <iwp:AlternativeText>$Resources:core,sitelogo_wss;</iwp:AlternativeText>

                  </WebPart>

                  ]]>

      </AllUsersWebPart>

    </File>

  </Module>

  <Module Name="TestModule" Path="" Url="Test">

    <File Name="default.aspx" Url="default.aspx" IgnoreIfAlreadyExists="FALSE">

      <AllUsersWebPart WebPartZoneID="Right" WebPartOrder="1">

        <![CDATA[

                  <WebPart xmlns="http://schemas.microsoft.com/WebPart/v2" xmlns:iwp="http://schemas.microsoft.com/WebPart/v2/Image">

                        <Assembly>Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>

                        <TypeName>Microsoft.SharePoint.WebPartPages.ImageWebPart</TypeName>

                        <FrameType>None</FrameType>

                        <Title>$Resources:wp_SiteImage;</Title>

                        <iwp:ImageLink>/_layouts/images/homepage.gif</iwp:ImageLink>

                        <iwp:AlternativeText>$Resources:core,sitelogo_wss;</iwp:AlternativeText>

                  </WebPart>

                  ]]>

      </AllUsersWebPart>

    </File>

  </Module>

</Elements>

I created a Feature Receiver as usual by inheriting from SPFeatureReceiver.  On the FeatureDeactivting event, I have the following code.  The code simply gets an instance of the web object and gets the path to the elements.xml file as described in my past post.

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

{

    using (SPWeb currentSite = (SPWeb)properties.Feature.Parent)

    {

        string elementsPath = string.Format(@"{0}\FEATURES\{1}\Elements.xml",

                SPUtility.GetGenericSetupPath("Template"), properties.Definition.DisplayName);

 

        DeleteFeatureFiles(currentSite, elementsPath);

    }

}

The DeleteFeatureFiles method takes an instance of the web object and the path to the elements.xml.  I then use LINQ to XML to load the document and call another method to delete the files.  In this case, I am specifically interested in the Url attribute of any Module element and any child File element.  The Url attribute contains the subfolder that the file is in.  The result is a list of all modules and the Files element of each one.   I had to specify the SharePoint namespace on the root element (as expected), but also on the Files child element.  Nothing else required it strangely enough. 

private void DeleteFeatureFiles(SPWeb currentSite, string elementsPath)

{

    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

                    {

                        ModuleUrl = (module.Attributes("Url").Any()) ? module.Attribute("Url").Value : null,

                        Files = module.Elements(sharePointNamespace + "File")

                    };

 

    // iterate through each module and delete the child files

    foreach (var module in moduleList)

    {

        DeleteModuleFiles(module.ModuleUrl, module.Files, currentSite);

    }

}

The URL of the module and a collection of XElement representing each file is then passed to a method for deletion.  I then just iterate through that list of files for deletion.  Note, I did have to handle files in the root of the site differently.  I think I can probably clean that code up, but for now this works.

private void DeleteModuleFiles(string moduleUrl, IEnumerable<XElement> fileList, SPWeb currentSite)

{

    // delete each file in the module

    foreach (var fileElement in fileList)

    {

        // use the name attribute if specified otherwise use Url attribute (since it is required)

        string filename = (fileElement.Attributes("Name").Any()) ? fileElement.Attribute("Name").Value

            : fileElement.Attribute("Url").Value;

 

        // pass the moduleUrl if it has a value

        if (!string.IsNullOrEmpty(moduleUrl))

            currentSite.GetFile(string.Format("{0}/{1}", moduleUrl, filename)).Delete();

        else

            currentSite.Files.Delete(filename);

    }

}

This works great for me and I can now just use one FeatureReceiver to handle this on any feature that I deploy.  This can obviously be applied to other concepts.  I’ll finally write my post on how to remove items from the web part gallery next.

UPDATE: I updated the DeleteModuleFiles method to look for the file in the Name attribute first and then the Url attribute for the name of the file.

Comments

# re: LINQ to XML and Deleting Files on Feature Deactivation

Wednesday, December 29, 2010 5:44 PM by Brad

Can you do this in VS 2010 and SharePoint 2010?

# re: LINQ to XML and Deleting Files on Feature Deactivation

Sunday, January 2, 2011 6:10 PM by CoreyRoth

@Brad Yes you should be able to.  However, VS2010 does do some cleanup and conflict resolution for you when it deploys and retracts solutions.

# re: LINQ to XML and Deleting Files on Feature Deactivation

Wednesday, January 12, 2011 4:49 PM by Chas

You reference SPUtility, but i can't seem to find it.  Is this something that was part of 2007 but has been removed for 2010?

# re: LINQ to XML and Deleting Files on Feature Deactivation

Friday, June 10, 2011 2:51 PM by CoreyRoth

@Chas SPUtility is in Microsoft.SharePoint.Utilities I believe.

# re: LINQ to XML and Deleting Files on Feature Deactivation

Monday, August 27, 2012 9:09 AM by Grégoire

Excellent article ! very great job...

I would like to suggest to you some improvements:

- using the Directory.GetFiles method with the SearchOption AllDirectories to loop on each Elements.xml files in the folder

- managing some exceptions as masterpage that you can't remove by just deleting the file (need to create a temp folder, move the master page in it, and delete the folder)

Thank's again!! it's a very good base for my project context.

# re: LINQ to XML and Deleting Files on Feature Deactivation

Wednesday, December 5, 2012 8:34 AM by VenuGopal

Its a very useful article,Thanks for posting this ...!!!

Leave a Comment

(required) 
(required) 
(optional)
(required)