Saturday, July 4, 2009

Overwriting Files in SharePoint using Module Element

As documented on msdn, the IgnoreIfAlreadyExists attribute in the file element provisions a file even if it already exists at the specified URL. However it doesn't works as expected.
Searching for solution on the web, I came across a fantastic blog post by John Leino where he has suggested a programmatic approach to workaround this issue.
John describes creating a feature receiver which upon activation would get all the files along with their properties from the module element. Based on these properties, the files would then be added/overwritten on their target locations.
I created a small POC using John’s approach and it really worked well !. But his code seems to break under the following conditions:
  • If you have multiple modules in your element.xml file.
  • If the target SharePoint library forces the checking and checkout policy.
So I extended John’s code to address these issues by adding/updating few methods.

Added a CheckOutStaus method to get the file checked out status:
CheckOutStaus


For publishing sites the content needs to be approved before it can be seen by other site users (e.g. on the masterpage library). So I added a method CheckContentApproval to verify that whether content approval is enabled on the target SharePoint library:
CheckContentApproval


Updated the UpdateFilesInModule method to looks like this:
UpdateFilesInModuleUpdateFilesInModule
So in the above method after from getting all the files and their properties from the module, we are doing two thing
  1. Getting the checkout status of the file.
  2. Checking whether content approval is enabled on the target SharePoint library and accordingly approving the file.
The entire source code can be downloaded from .

14 comments:

  1. Great that you found my blogpost as a good example and great that you have extended it with some modifications, I´ll add them to my personal code lib :-). / Johan - not John ;-)

    ReplyDelete
  2. Awesome...had the problem with Page Layouts deployed by features.
    Would be great you post the Project as a zip, not only the source code. Had some trouble with namespaces.
    Thanks, saved me!

    ReplyDelete
  3. Thanks Christoph, I am glad it helped you.
    I'll post the project soon.

    ReplyDelete
  4. Another approach to this would be a feature receiver that simply deletes the file in question before using an standard modules feature to upload your new copy.

    You could use Activation Depenencies to ensure that the orginal file is deleted before the module feature uploads the new one.

    This is much simpler from a code perspective and is just this:

    using (SPSite site = new SPSite("http://yoursiteurl"))
    {
    using (SPWeb web = site.RootWeb) //or find your web by some other method if it is not the root web
    {
    web.GetFile(web.Url +"/the full path to your file").Delete();
    }
    }

    Not saying this is a 'better' solution, the one outlined here is very good and comprehensive, just an alternative is all.

    Regards

    ReplyDelete
  5. I was suffering from the same issue with page layouts not getting updated. A coworker mentioned that he saw a post saying that page layouts with a Page tag that has the following attributes is automatically considered customized as soon as it is installed:

    meta:progid="SharePoint.WebPartPage.Document" meta:webpartpageexpansion="full"

    I thought there was no way it would work and I'd be writing code to update the page layouts, but when they were removed, at least based on my initial testing, they actually update now.

    Now if you already have a bunch of pages using the page layout you want to change and it was installed with those tags still in them, it seems like it might be a catch22 (there's no way to get the updated page layout into those sites since those pages are considered customized, in which case it might take code to unwind it--but more as a one time adminstrative thing). That's just a theory though. I'm not sure if there are any bad side effects to removing those tags, but I'll be on the watch for them.

    ReplyDelete
  6. Just found this, exactly what I need. life saver!

    ReplyDelete
  7. I'm getting an error after running the activation: The base type 'xxx' is not allowed for this page. The type is not registered as safe.

    ANyone else receive this?

    ReplyDelete
  8. Link above is broken...
    Johan Leino's blog entry on IgnoreIfAlreadyExists is at:

    http://johanleino.wordpress.com/2009/04/22/howto-handling-file-updates-in-sharepoint/

    ReplyDelete
  9. Getting Object Reference not set error.. Any ideas?

    ReplyDelete
    Replies
    1. Visual Studio 2010 does not create a Path attribute on the Module element of the Elements.xml file when creating a new Module. The attribute is optional and could be set on the File child element instead (which is also optional). You can update the code to set PhysicalPath for module to

      PhysicalPath = (m.Attribute("Path") == null) ? string.Empty : Path.Combine(featureDir, m.Attribute("Path").Value),

      You can also add a PhysicalPath property to the Files property of Module in the code file and then set that to:

      PhysicalPath = (f.Attribute("Path") == null) ? string.Empty : Path.Combine(featureDir, f.Attribute("Path").Value),

      Then in the foreach loop for each of the files in the module,

      physicalPath = string.IsNullOrEmpty(file.PhysicalPath) ? Path.Combine(module.PhysicalPath, file.Name) : Path.Combine(file.PhysicalPath, file.Name);

      With the assumption being if there is not a Path property on the file element itself, there would be one set for the module.

      This should resolve your exception.

      Delete
    2. Actually, the last line in my comment about the foreach loop, it should be this:

      physicalPath = string.IsNullOrEmpty(file.PhysicalPath) ? Path.Combine(module.PhysicalPath, file.Name) : file.PhysicalPath;

      Delete
  10. Inside 'UpdateFilesInModule', when setting the module variable, you should really call FirstOrDefault() instead of First(). The way it is right now would throw an exception. FirstOrDefault would return null instead. You might want to do some additional checking inside it to see if those properties actually exist.

    ReplyDelete
  11. Yes anonymous, we need adjust a lot of things there, but the principal is troubleshooting the module, because some files need a different approach.

    Some files have some required properties that you need fill in module, that in code you will to get using linq-to-xml (Property).

    In code, we need to analyze too. Let's think:
    - Pages Library: Normally requires check-out and after needs a check-in. If content approval is enabled, we need approve it.
    - MasterPages: You cannot delete a instance that is been used, then you need check if is a masterpage before. If doesn't, you can update (overwrite) the file.

    For another types, you need check firstly if file is checked-out for another user. If yes, you need do a undo-checkout before anything.

    I recommend that you log each step to understand what is happening.

    Soon, when I finish my code here, I'll post the final version.

    See ya!

    ReplyDelete