Sunday, 16 December 2007

Copying a SharePoint Document Library Programmatically

Based on the number of posts out there about copying the content of a list or document library, I'm willing to suggest SharePoint hasn't made this particular task easy. Whether it's through the various UIs or programmatically, this task seems like it's more difficult than it should be. As I recently found out, even clearing the content in an existing list is a hassle.

Before I go on, a bit of background. We were initially using the in-built variation tools to copy content from a source language site to a number of target English sites--in other words, www.westernaustralia.com/en was being copied to the /uk, /au, /nz, and /sg sites. I won't bore you with the details but the variation tool was deemed too blunt for our requirements and one of the developers on my team wrote a custom variation tool to do exactly what we want. The custom variation tool copies sub sites and pages but unfortunately it doesn't copy documents in document libraries; we don't make extensive use of the Documents library you get when you create a new publishing site but a few speicific sites do contain PDFs that need to be pulled over.

Since a document library is a list at heart, I started by examining the SPList API, assuming it would provide me with everything I need to manage the list items. I also looked into the SPListItemCollection returned by the Items property of SPList, and the SPListItem class. SPList was pretty hopeless. SPListItemCollection was somewhat more helpful, exposing the standard Add, Delete, and Count methods, and SPListItem was really enticing with its CopyFrom and CopyTo methods. Of course this was nearly a complete waste of time as few of these methods and properties really helped out at all. CopyFrom and CopyTo failed at runtime, Delete works as advertised but SPListItemCollection does not overload the Delete method or provide a sister method to delete everything in the list, and Add only adds a new list item if you get the very confusing url parameter pointing at the right location (a quick hint: it's expecting the URL of the destination file in the case of a document library...).

When it was all said and done, I'd written my own ClearList helper method, cast my destination list to a SPDocumentLibrary, accessed the Files collection via the RootFolder property of said document library, and passed in the byte array representing the uploaded file.

Here's the code I ended up with to copy the contents of the Documents list in one sub site to the Documents list in another sub site within the same web application:

using (SPSite site = new SPSite ("http://dev-moss-mh:101/"))
{
using (SPWeb sourceWeb = site.AllWebs ["Source_Site"])
{
using (SPWeb destinationWeb = site.AllWebs ["Destination_Site"])
{


SPList sourceDocuments = sourceWeb.Lists ["Documents"];
SPList destinationDocuments = destinationWeb.Lists ["Documents"];


if (sourceDocuments.ItemCount > 0)
{
ClearList (destinationDocuments);


foreach (SPListItem currentSourceDocument in sourceDocuments.Items)
{
Console.WriteLine ("Adding: {0}", currentSourceDocument.Name);


byte [] fileBytes = currentSourceDocument.File.OpenBinary ();

const bool OverwriteDestinationFile = true;
string relativeDestinationUrl =
destinationDocuments.RootFolder.Url +
"/" +
currentSourceDocument.File.Name;

SPFile destinationFile =
((SPDocumentLibrary) destinationDocuments).RootFolder.Files.Add (
relativeDestinationUrl,
fileBytes,
OverwriteDestinationFile);
}
} } } }


As you can tell by the variable name, the Add method requires a relative URL pointing within the context of the destination site. This seems odd to me since Add ( ) is called on the destination list itself--why it can't figure out the destination URL is beyond me.

My ClearList implementation is also mildly interesting: deleting items within a foreach loop is obviously a no-no since the foreach syntax in C# is interacting with an IEnumerator object so my first attempt was to iterate over the list using a for loop and deleting list items from the zero index through to the final item in the list. This worked but only sporadically, occasionally leaving items behind. Calling ClearList a second time did the trick with a small list but that's just weird programming.

The solution I arrived at is detailed below and essentially comes down to the fact that the SharePoint list API must maintain a real-time (or part-time) connection with the database; in other words, deleting an item at index zero could mean SharePoint re-fetches the list content so by the time my for loop moves on to the next index, the list has effectively shuffled itself down so index zero is still populated. As you can see, I'm now simply iterating over the list and always deleting the item at index zero. I could have possibly used a while loop and the listToClear.Items.Count property directly but it's difficult to know whether SharePoint can be trusted in a case like this. I'll leave that up to you to try out...

private static void ClearList (SPList listToClear)
{
int initialItemCount = listToClear.Items.Count;

for (int counter = 0; counter < initialItemCount; counter++)
{
// Always delete the list item at index 0
SPListItem listItemToClear = listToClear.Items [0];
listItemToClear.Delete ();
}
listToClear.Update ();
}
 
 
 

10 comments:

  1. Clearing the items could also work if you delete each item starting from the end.

    ReplyDelete
  2. Is this of any help? http://sourceforge.net/projects/splistcp

    ReplyDelete
  3. Hi soumyendra,

    Let me know if you got any code. actually i am also trying to copy DocumentLibray1 to DocumentLibrary2.i have eventreceiver when i drag folders into DocumentLibray1 i have to copy that folder into DocumentLibrary2.
    hope you understand?

    Thanks

    ReplyDelete
  4. Great explanation and code. Thanks.

    ReplyDelete
  5. Hi,
    I tried splistcp, but I'm not able to copy all content of library. I tried following:
    splistcp-1.1.0.exe --single-list http://old.sharepoint/Projects/2009/ http://new.sharepoint/test/project_library
    but following alert is displayed:

    ** Something has caused this to fail completely:
    Action could not be finished.

    And only one file/folder from source library is copyied.
    What's wrong?
    Thanks

    ReplyDelete
  6. Thanks for the nice post.
    Can you help me little ?
    Actually my exercise is that i want to add file programmatically to Document Library then i have to set permission for the particular user while uploading document.I have to programming in EventReceiver how can i ? i dont have any idea.Can you please please help me ??

    ReplyDelete
  7. Hi Pushkar,

    I'm not too certain about the permissions side but I'd suggest configuring permissions at the site or document library if possible and allowing items to inherit those permissions.

    ReplyDelete
  8. Hi ,

    I am developing file management system for which i have to manage version control, for version control
    1)- first checkout the file by user
    2)- then user will upload same file after editing
    3)- upon uploading same file it will check in that particular file

    problem:
    File version history contains more than one history because i am uploading and then checkin file. is there any way through which i can check in file upon uploading.

    ReplyDelete
  9. Thanks Michael,
    Great Line of Code you share with us.

    ReplyDelete

Spam comments will be deleted

Note: only a member of this blog may post a comment.