Stefan Goßner

Senior Escalation Engineer for SharePoint (WSS, SPS, MOSS, SP2010) and MCMS

Deep Dive into the SharePoint Content Deployment and Migration API - Part 4

Deep Dive into the SharePoint Content Deployment and Migration API - Part 4

  • Comments 73
  • Likes

[Part 1 - Part 2 - Part 3 - Part 4 - Part 5 - Part 6 - Part 7]

Advanced content deployment scenarios

Till now we have covered export and import of

  • single list items or documents
  • containers like folders, lists or webs
  • complete site collection

What we haven't covered is how to use this method to move items. Or how to control the which items from the export package need to get imported into the database.

Let's look on each of these topics separately.


1) Moving content using Content Deployment and Migration API

Export and import allows us to create a copy of items or containers but it does not automatically allow us to move content. But copying the content and then deleting the original content is very similar to a real move operation. the only problem here is that links pointing to the moved content will still point to the original content. So these would have to be adjusted.

And this is indeed what the Move operation in Site Manager does! It copies the content using the Content Deployment and Migration API, adjusts all links to point to the new content and then delete the original content.

Let's now look on a sample how this can be achieved by looking at a sample code for the move of a single image from an image library.

First step is to export the ListItem using the Content Deployment and Migration API as demonstrated in part 2:

// export an image from the Images Library

SPSite site = new SPSite("http://localhost:2000");
SPWeb web = site.OpenWeb("/SourceSubWeb");
SPList list = web.Lists["Images"];
SPListItem listItem = list.Items[0];

SPExportObject exportObject = new SPExportObject();
exportObject.Id = listItem.UniqueId;
exportObject.Type = SPDeploymentObjectType.ListItem;

SPExportSettings exportSettings = new SPExportSettings();
exportSettings.ExportObjects.Add(exportObject);
exportSettings.FileLocation = @"c:\export";
exportSettings.FileCompression = false;
exportSettings.SiteUrl = "http://localhost:2000";

SPExport export = new SPExport(exportSettings);
export.Run();

// cleanup
web.Dispose();
site.Dispose();

The next step is to import the image from the export into a the desired new location similar to the steps described in part 3:

// import the image into the desired destination library

SPImportSettings importSettings = new SPImportSettings();
importSettings.SiteUrl = "http://localhost:2000";
importSettings.FileLocation = @"c:\export";
importSettings.FileCompression = false;
importSettings.RetainObjectIdentity = false;

SPImport import = new SPImport(importSettings);

EventHandler<SPDeploymentEventArgs> startedEventHandler = new EventHandler<SPDeploymentEventArgs>(OnStarted);
import.Started += startedEventHandler;

EventHandler<SPObjectImportedEventArgs> importedEventHandler = new EventHandler<SPObjectImportedEventArgs>(OnImported);
import.ObjectImported += importedEventHandler;

import.Run();

...

// event handler to assign a new parent to the orphaned image in the package

static
 void OnStarted(object sender, SPDeploymentEventArgs args)
{
   SPSite site = new SPSite("http://localhost:2000");
   SPWeb web = site.OpenWeb("/MyWeb");
   SPList list = web.Lists["Images"];

   SPImportObjectCollection rootObjects = args.RootObjects;
   foreach (SPImportObject io in rootObjects)
   {
      io.TargetParentUrl = list.RootFolder.ServerRelativeUrl;
   }

   web.Dispose();
   site.Dispose(); 
}

If you look at the code above you will see that there is one additional event handler registered with the import object. This event handler will fire every time a new object has finished importing. We will now use this event handler to finish the move operation by changing all links pointing to the original image to the new image and afterwards deleting the original image.

Lets look into the details.

The event handler gets the Url of the exported object and the Url of the imported object in the SPObjectImportedEventArgs:

  • SourceUrl
  • TargetUrl

Based on this information the following code will now give us the SPListItem object of the original image from the SourceUrl property.

string url = eventArgs.SourceUrl;
SPSite site = new SPSite(import.Settings.SiteUrl);
SPWeb web = site.OpenWeb(url, false);
SPListItem li = web.GetListItem(url); 

As next step we retrieve the number of backward links pointing to the original image. A backward link means that another object points to this object. A forward link on the other hand would point from the current object to a different object. Using the BackwardLinks collection we are able to identify all objects that a point to the exported object.

To adjust all links we need to follow all backward links pointing to the original object to adjust the links to point to the new object we get the links using the following code:

int count = li.BackwardLinks.Count; 
for (int i = count - 1; i >= 0; i--) 

   SPLink link = li.BackwardLinks[i]; 
   ...

Now we need to get the objects that are pointing to our source item by following the ServerRelativeUrl in each backward link:

SPLink link = li.BackwardLinks[i];
 
using (SPWeb rweb = linkSite.OpenWeb(link.ServerRelativeUrl, false)) 

   object o = rweb.GetObject(link.ServerRelativeUrl);
   ...
}

Now we known the object. The next step is to adjust the forward link. But to adjust the link we first have to cast the retrieved object to it's actually SharePoint type.

There are exactly two different object types that can create a link to our image: SPListItem and SPFile as these are the only object types that implement the ForwardLink property. And these objects also implement a method to adjust the forward link by replacing the link to the exported object with the link of the imported object:

object o = rweb.GetObject(link.ServerRelativeUrl);
if (o is SPFile)
{
   SPFile f = o as SPFile;
   f.ReplaceLink(eventArgs.SourceUrl, eventArgs.TargetUrl);
}
if (o is SPListItem)
{
   SPListItem l = o as SPListItem;
   l.ReplaceLink(eventArgs.SourceUrl, eventArgs.TargetUrl);
}

After we have successfully adjusted all links we can then savely delete the source image.

Here is the complete code for our event handler:

static void OnImported(object sender, SPObjectImportedEventArgs eventArgs)
{
   SPImport import = sender as SPImport;

   string url = eventArgs.SourceUrl;
   SPSite site = new SPSite(import.Settings.SiteUrl);
   SPWeb web = site.OpenWeb(url, false);
   SPListItem li = web.GetListItem(url);

   int count = li.BackwardLinks.Count;
   for (int i = count - 1; i >= 0; i--)
   {
      SPLink link = li.BackwardLinks[i]; 
      using (SPWeb rweb = site.OpenWeb(link.ServerRelativeUrl, false)) 
      { 
         object o = rweb.GetObject(link.ServerRelativeUrl); 
         if (o is SPFile) 
         { 
            SPFile f = o as SPFile; 
            f.ReplaceLink(eventArgs.SourceUrl, eventArgs.TargetUrl); 
         } 
         if (o is SPListItem) 
         { 
            SPListItem l = o as SPListItem; 
            l.ReplaceLink(eventArgs.SourceUrl, eventArgs.TargetUrl); 
         }
      }
   }
   li.Delete();

   web.dispose();
   site.dispose();
}

 

You can try this sample by creating a publishing site, create a sub web and upload an image to the "Images" library. Then create a page based on a page layout that has a Image Field and reference the uploaded image in this field. Now use the code shown above to move the image to the "Images" library in a different web.


2) How to control which of the objects in the export package need to be imported

The Content Deployment and Migration API itself does not provide a possibility to select specific objects for import. It will always import all objects included in the export package. In order to control which objects to import we again need to intercept the import using the "Started" event handler and remove all objects from the package that should not be imported.

This can be done using code similar to the following:

static void OnStarted(object sender, SPDeploymentEventArgs args) 

   
// assign new parents to the root objects if required.
   ...
   
// get the information about the location of the manifest file after decompression (if required)

   string tempFileLocation = args.TempDirectoryPath;
   
   // now we need get all manifest files from the SystemData.xml file in the tempdirectory path
   StringCollection manifestFilenames = GetManifestFilenamesFromSystemDataXmlFile(args.TempDirectoryPath + @"\SystemData.xml"); 

   // now we can manipulate the content of each of the manifest files and (e.g.) remove objects from the manifest.
   foreach (string manifestFilename in manifestFilenames)
   {
       DoTheRequiredManipulationsToTheManifestFiles(manifestFilename);
   }

The SystemData.xml file and the Manifest.xml files can be manipulated using normal Xml operations available in .Net framework.

Important to know when planning to manipulate the Manifest.xml files that each individual exported objects is reflected as a SPObject node. The SPObject nodes are sequentially serialized inside the manifest file. That means that subwebs are listed behind their parent webs but not as child xml nodes.

Now I see the following question coming up: Is it allowed to manipulate the Manifest.xml files?

The answer is yes! The xml schema of all the xml files in a content migration pack is officially documented and can (e.g.) be used by 3rd party companies to migrate content to WSS or MOSS:
http://msdn2.microsoft.com/en-us/library/bb249989.aspx

Comments
  • Stefan is back! J Stefan is in Redmond this week, and we had a chance to catch up! In fact, I had dinner

  • Stefan Goßner taucht in einer vierteiligen Serie in die Tiefen der SharePoint Deployment und Migration

  • Fantastic, incredibly detailed, content on content deployment by Stefan

  • [via Stefan Gossner ] This past week I was in Redmond teaching my WCM401 development class in an open

  • Body: Great series of posts by Stefan on how to use the Content Migration API. Very timely for me as

  • 今天凌晨加班的时候偶然翻到 Stefan Gossner 的这几篇文章,强烈推荐给大家: Deep Dive into the SharePoint Content Deployment and Migration

  • Grate post, thanks.

    But, what if my manifast.xml files weighs over 200 MB?

  • Hi Yair,

    it shouldn't matter. But if you are concerned about this use the FileMaxSize export property.

    This also controls the size of the generated manifest.

    So you will end up with multiple manifest files then.

    Cheers,

    Stefan

  • Hi,

    Your post was the best on content deployment with MOSS that I found on the internet. Better that MSDN documentation. :)

    I wonder if you don't want to extend this article, with a part 5, where you can explain which of these features are available and work in websites with variations.

    Congratulations!

  • Great set of articles by Stefan Goßner : Deep Dive Into the SharePoint Content Deployment...

  • MOSS / SharePoint Content Deployment

  • Hi, Stefan!

    Can you explain why I receive some error messages on trying to migrate content between two MOSS machines. First machine was installed on english SKU, with 3 variations: english, serbian-latin and serbian-cyrillic. On that installation I installed language pack for serbian-latin when it became available. Second machine (destination) is empty moss with english sku and serbian language pack.

    Here are error details:

    Could not find language Serbian (Latin, Serbia). at Microsoft.SharePoint.Deployment.ImportRequirementsManager.VerifyInstalledLanguage(String id, String name) at Microsoft.SharePoint.Deployment.ImportRequirementsManager.VerifyLanguage(SPRequirementObject reqObj) at Microsoft.SharePoint.Deployment.ImportRequirementsManager.Validate(SPRequirementObject reqObj) at Microsoft.SharePoint.Deployment.ImportRequirementsManager.DeserializeAndValidate() at Microsoft.SharePoint.Deployment.SPImport.VerifyRequirements() at Microsoft.SharePoint.Deployment.SPImport.Run()

    Thanks!

    Dragan

  • Hi Dragan,

    you need to install the same language pack on both machines.

    Cheers,

    Stefan

  • Hi Stefan,

    Me team is in a fix. We have some documents in a folder structure in a Content Management Application called Plumtree. Some of these Word documents have hyperlinks to eachother.

    The requirement is for us to move these files and the folder structure as it is to a document library in sharepoint- this we are able to accomplish.

    The next phase of the requirement was to update the hyperlinks in the documents with the new sharepoint URL. We are able to accomplish this in most cases using the SPfile.ReplaceLink function. But there are instances where this fails.

    Eg: OLD URL: www.google.com?id=123

    NEW URL: www.yahoo.com

    It does nothing.

    Is there something we might be doing wrong.

    It works fine in cases where :

    OLD URL: www.google.com/games.html

    NEW URL: www.yahoo.com

  • Hi Nita,

    please open a support case for this.

    This needs to be analyzed in more details.

    Cheers,

    Stefan

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
Raw Html Fix