Monday, December 20, 2010

Synchronize Lists in two different site collections

We had a requirement to keep 4 lists in synch. There is a list of sales regions with the name of the sales representative, sales manager, etc for each region. The same list is used by several different sites. There was a master list and 3 child lists. Every time the master list is updated, the children lists should be updated.

The code is configurable, so additional child lists can be easily added by updating an item in a SharePoint configuration list.

For this to work, each of the child lists should begin with the same content.

I created an ItemEventReceiver. It will fire on the following events:

ItemAdded
ItemUpdated
ItemDeleting

I created a list called Configuration in the main source site. In that list I created 3 columns: Title, Value and Description





As shown above, I created 3 list items:

SynchSourceList
SynchDestLists
SynchDestSites

Each of the destination lists have the same name, so I didn't need to list 3 separate destination list names. These three items will be read by the Item Event Receiver.

This is the code for the ItemAdded Event:

public override void ItemAdded(SPItemEventProperties properties)
{
// copy the list item to the other lists

try
{
this.DisableEventFiring();
SPListItem SourceItem = properties.ListItem;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
string DestURL = getConfig.getConfigValue(properties.WebUrl, "SynchDestSites");
string DestList = getConfig.getConfigValue(properties.WebUrl, "SynchDestLists");
string[] sites = DestURL.Split(';');
// foreach site in list
foreach (string siteName in sites)
{
using (SPSite site = new SPSite(siteName))
{
using (SPWeb destWeb = site.OpenWeb())
{

SPList SPDestList = destWeb.GetList(destWeb.ServerRelativeUrl.TrimEnd('/') + "/Lists/" + DestList);
SPListItem spListItem = SourceItem;
SPListItem newDestItem = SPDestList.Items.Add();
foreach (SPField field in spListItem.Fields)
{
if (!field.ReadOnlyField &&
field.Type != SPFieldType.Invalid &&
field.Type != SPFieldType.WorkflowStatus &&
field.Type != SPFieldType.File &&
field.Type != SPFieldType.Attachments &&
field.Type != SPFieldType.Computed &&
field.Type != SPFieldType.Lookup)
{
newDestItem[field.InternalName] = spListItem[field.InternalName];
}
}
newDestItem.Update();
}
}
}
});
base.ItemAdded(properties);
}
catch(Exception ex)
{

}
finally
{
this.EnableEventFiring();

}


}


The ItemAdded Event calls GetConfigValue which is listed here:

class getConfig
{
public static string getConfigValue(string webURL, string listItem)
{
// get the destURL and destList from the Configuration List
string retValue;
try
{
SPSite thisSite = new SPSite(webURL);
SPWeb thisWeb = thisSite.OpenWeb();
SPList configList = thisWeb.GetList(thisWeb.ServerRelativeUrl.TrimEnd('/') + "/Lists/Configuration");

SPQuery query = new SPQuery();


query.ViewFields =
@"";
query.Query = @"" + listItem +
@"
";
SPListItemCollection items = configList.GetItems(query);

// just get the first item
SPItem firstItem = items[0];
retValue = firstItem["Value"].ToString();

if (thisWeb != null)
thisWeb.Dispose();

if (thisSite != null)
thisSite.Dispose();


}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("RegistrationListEventReceiver.ItemAdded() exception in getConfigValue" + ex.ToString());
retValue = "Error";
}

return retValue;

}

}


I am also capturing the ItemDeleting Event:


public override void ItemDeleting(SPItemEventProperties properties)
{
// remove the list item from the other lists
// find the item in the other list with the same ID and delete it

string key;
try
{
this.DisableEventFiring();
SPListItem SourceItem = properties.ListItem;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
key = SourceItem["RecordID"].ToString();

// do a query on the destination list and get the item with the same key
string DestURL = getConfig.getConfigValue(properties.WebUrl, "SynchDestSites");
string DestList = getConfig.getConfigValue(properties.WebUrl, "SynchDestLists");
string[] sites = DestURL.Split(';');

// for each site in list
foreach (string siteName in sites)
{
using (SPSite site = new SPSite(siteName))
{
using (SPWeb destWeb = site.OpenWeb())
{
SPQuery qry = new SPQuery();
qry.ViewFields = @"";
qry.Query = @"" + key + "";
SPList SPDestList = destWeb.GetList(destWeb.ServerRelativeUrl.TrimEnd('/') + "/Lists/" + DestList);
SPListItemCollection items = SPDestList.GetItems(qry);
if (items.Count > 0)
{
for (int i = items.Count - 1; i >= 0; i--)
{
items.Delete(i);
}
}
}
}
}
}
);
base.ItemDeleting(properties);

}
catch (Exception ex)
{

}
finally
{
this.EnableEventFiring();

}

}

and the ItemUpdated Event:

public override void ItemUpdated(SPItemEventProperties properties)
{
//update the list item in the other list(s)
string key;
try
{
this.DisableEventFiring();
SPListItem SourceItem = properties.ListItem;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
key = SourceItem["RecordID"].ToString();

// do a query on the destination list and get the item with the same key
string DestURL = getConfig.getConfigValue(properties.WebUrl, "SynchDestSites");
string DestList = getConfig.getConfigValue(properties.WebUrl, "SynchDestLists");
string[] sites = DestURL.Split(';');
// for each site in list
foreach (string siteName in sites)
{
using (SPSite site = new SPSite(siteName))
{
using (SPWeb destWeb = site.OpenWeb())
{
SPQuery qry = new SPQuery();
qry.ViewFields = @"";
qry.Query = @"" + key + "";
SPList SPDestList = destWeb.GetList(destWeb.ServerRelativeUrl.TrimEnd('/') + "/Lists/" + DestList);
SPListItemCollection items = SPDestList.GetItems(qry);
foreach (SPListItem item in items)
{
//update the fields from the source item
foreach (SPField field in SourceItem.Fields)
{
if (!field.ReadOnlyField &&
field.Type != SPFieldType.Invalid &&
field.Type != SPFieldType.WorkflowStatus &&
field.Type != SPFieldType.File &&
field.Type != SPFieldType.Attachments &&
field.Type != SPFieldType.Computed &&
field.Type != SPFieldType.Lookup)
{
item[field.InternalName] = SourceItem[field.InternalName];

}
}
item.Update();
}

}

}
}
}
);
base.ItemUpdated(properties);
}
catch (Exception ex)
{

}
finally
{
this.EnableEventFiring();

}

}

I also created a Feacture Event Receiver so that when the Feature is Activated, it would associate the Item EventReceivers with the specified sourceList. You can see the code here:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{

try
{
using (SPSite destSite = new SPSite("http://yoursite.org"))
{
using (SPWeb destWeb = destSite.OpenWeb())
{
//make this configurable

string listName;
listName = SynchronizeLists.getConfig.getConfigValue("http://yoursite.org", "SynchSourceList");

destWeb.Lists[listName].EventReceivers.Add(
SPEventReceiverType.ItemAdded,
"SynchronizeLists, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3900cea7f3c6a92a",
"SynchronizeLists.SynchronizeListsItemEventReceiver");
destWeb.Lists[listName].EventReceivers.Add(
SPEventReceiverType.ItemDeleting,
"SynchronizeLists, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3900cea7f3c6a92a",
"SynchronizeLists.SynchronizeListsItemEventReceiver");
destWeb.Lists[listName].EventReceivers.Add(
SPEventReceiverType.ItemUpdated,
"SynchronizeLists, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3900cea7f3c6a92a",
"SynchronizeLists.SynchronizeListsItemEventReceiver");

}
}
}
catch (Exception ex)
{

}
}


There is a FeatureDeactivating event receiver as well:

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
try
{

using (SPWeb web = properties.Feature.Parent as SPWeb)
{

string listName;
listName = getConfig.getConfigValue("http://yoursite.org", "SourceList");
SPList TheList = web.Lists[listName];
//Declare the full assembly name
String AssemblyName = "SynchronizeLists, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3900cea7f3c6a92a";
int i;
for (i = 0; i < TheList.EventReceivers.Count; i++) { if (TheList.EventReceivers[i].Assembly.Equals(AssemblyName)) TheList.EventReceivers[i].Delete(); } //Update the list TheList.Update(); web.Update(); } } catch (Exception ex) { } }

2 comments:

  1. Hi, this is wonderful. Is there anyway you could post the full Visual Studio solution for the project? Thank you - Chris

    ReplyDelete
  2. Sorry I am new to sharepoint, but where do you put the code?

    ReplyDelete