Here´s one of those really annoying things in SharePoint. When you provision a file through a module,
in this example it´s a webpart file, and add the IgnoreIfAlreadyExist attribute you would expect it to be overwritten (upgraded) when you reactivate the feature but that´s not working.
There are numerous blogs about this problem, some of them are here, here and here.
So, let´s first see how it works and then see if we can get around it somehow.
In this little example I have made a feature that provisions a webpart file to the _catalogs/wp library. After doing that the xml looks like this when I open it in IE from the library in SharePoint.
To prove that the attribute (IgoreIfAlreadyExist) doesn´t work I’ll make a small change in the webpart file and reactivated the feature.
As expected (or maybe not expected) nothing happens in the webpartfile in SharePoint. So what now?
UPDATE: Since I posted this code on Spaces, Vivek has made some changes to it that I will include in my post.
UPDATE AGAIN: Since I posted this code on Spaces, I have made a lot of changes to it (among other to include multiple module elements). I have also decided to make an extension method out of it instead.
Doing it by code
So I´ll make a featurereceiver for my webpart feature and try to code my way around this problem instead:
class WebPartFeature : SPFeatureReceiver
{
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
properties.UpdateFilesInModule();
}
UpdateFilesInModule extension method
public static class SPFeatureReceiverPropertiesExtensions
{
public static void UpdateFilesInModule(this SPFeatureReceiverProperties instance)
{
var modules = (from SPElementDefinition element in instance.Feature.Definition.GetElementDefinitions(CultureInfo.CurrentCulture)
where element.ElementType == "Module"
let xmlns = XNamespace.Get(element.XmlDefinition.NamespaceURI)
let module = XElement.Parse(element.XmlDefinition.OuterXml)
select new
{
Url = module.Attribute("Url").Value,
Path = Path.Combine(element.FeatureDefinition.RootDirectory, module.Attribute("Path").Value),
Files = (from file in module.Elements(xmlns.GetName("File"))
select new
{
Url = file.Attribute("Url").Value,
Properties = (from property in file.Elements(xmlns.GetName("Property"))
select property).ToDictionary(
n => n.Attribute("Name").Value,
v => v.Attribute("Value").Value)
}).ToList()
}).ToList();
using (SPWeb web = (instance.Feature.Parent as SPSite).OpenWeb())
{
modules.ForEach(module =>
{
module.Files.ForEach(file =>
{
string hivePath = Path.Combine(module.Path, file.Url); // in 12-hive
string virtualPath = string.Concat(web.Url, "/", module.Url, "/", file.Url); // in MOSS
if (File.Exists(hivePath))
{
using (StreamReader sr = new StreamReader(hivePath))
{
SPFile virtualFile = web.GetFile(virtualPath);
bool CheckOutEnabled = virtualFile.Item.ParentList.ForceCheckout;
bool NeedsApproval = virtualFile.Item.ParentList.EnableModeration;
if (CheckOutEnabled)
{
virtualFile.CheckOut();
}
virtualFile = web.Files.Add(virtualPath, sr.BaseStream, new Hashtable(file.Properties), true);
if (CheckOutEnabled)
{
virtualFile.CheckIn("Updated", SPCheckinType.MajorCheckIn);
}
if (NeedsApproval)
{
virtualFile.Approve("Updated");
}
virtualFile.Update();
}
}
});
});
}
}
}
So, what is actually happening here?
First up is to get the element.xml file from the SPElementDefinition. I´m using LinqToXml to parse it to an anonymous ”Module” class with “Files” and “Properties”. It´s just a neat C# representation of the xml content that is easier to work with (or at least I think so).
Each file in each module is then uploaded into MOSS with the help of a StreamReader to first open the file in the 12-hive and then using the content of that file to overwrite the file in the SharePoint database.
And now when I reactivate the feature:
By the way this works for all sorts of files (not just webpart files)!
UPDATE 2010-02-20: The code for the extension method can now be found at codeplex.
#1 by Charlie Holland on August 21, 2009 - 16:44
I couldn’t get your NeedsCheckOut method to behave as expected.
I’ve found that:
return file.Item.ParentList.ForceCheckout;
does the trick though!
#2 by Johan Leino on August 21, 2009 - 16:53
Great! I actually wrote that method outside of VS just updating my post with the stuff that I got feedback on beforehand. I have updated the code with your corrections. Once again..thanks!
#3 by Perry SharePoint on January 7, 2010 - 04:09
You said that you posted the code somewhere (“Spaces”?)? Would you mind giving a hyperlink to where the code is posted?
#4 by Johan Leino on January 7, 2010 - 11:16
Ah, you misunderstood me. I first posted this code at my old blog at spaces but since then I have made a couple of changes to it. All the code you need is in this post, though I haven´t made a zip file of it and posted it somewhere.
#5 by Vignesh on June 20, 2011 - 21:38
Fantastic, thanks a bunch.