Monday, March 22, 2010

Correcting SharePoint Lookup Columns

As we all know we can provision a custom lookup field using CAML by using the below code

<Field ID="{105620F4-9485-4e57-8184-902E604C3C78}"

Type="Lookup"
Name="CustomerName"
StaticName="Customer Name"
DisplayName="Customer Name"
Required="TRUE"
List="{64DFEC00-AD66-4F5F-AA1F-C2E78CE6680C}"
ShowField="Title"
Group="MTD Fax Log custom Fields"
PrependId="TRUE">
</Field>

If we observe the above code more closely, there is a property called "List" like this
List="{64DFEC00-AD66-4F5F-AA1F-C2E78CE6680C}"

The GUID which is present in the list property is the SharePoint Lookup list GUID. Many say that there is no need to give the GUID of the SharePoint lookup list, instead it can be used like this

List="Lists/Customers"

But that did not really work for me. Many say that before creating a sharePoint lookup field with a lookup list, that lookup list should actually exist. I have even tried this way, But I was unsucessful. As per me, I would say that SharePoint is not that intelligent to figureout if the lookup list actually exist then automatically set "List" property to that list, otherwise don't provision this lookup column and report error. It should be capable of doing this atleast in the future versions.

Now, how did I resolve this issue then? My problem is slightly different. As we all know that site columns always exists in the site collection level, then is it mandatory for the lookup list to exist at the site collection level? I want this sharepoint lookup column which is present at the site collection level to use a SharePoint lookup list which is present in one of the subsites in that site collection? Is it really possible? Yes it is possible.

Solution is that we

  1. Deploy SharePoint custom Lookup field and related content type which uses this site column, as one feature at the site collection level, with a dummy list GUID.
  2. Then we create another feature at the sub site level, in which there is a list definition which uses the site content type with the SharePoint custom Lookup site column in that, as another feature.

So when we activate the second feature then the pre-requisite is that the first feature at the site collection level should be already activated. So how do I check this before activating my sub site feature?

Check this post for more details.

If the parent site feature is successfully active, then while activating the sub site feature I will execute SharePoint object model code in the "Feature handler's Feature activated method" to correct the SharePoint custom lookup column List GUID like this.

class FeatureReceiver : SPFeatureReceiver

{
const string customersListURL = "/Lists/Customers";
const string customersLookupColumnGUID = "105620F4-9485-4e57-8184-902E604C3C78";
const string customersListTitle = "Customers";
const string customersListDescription = "List to hold Customer details";
const string customersListTemplateName = "Customers Profile Custom List";
const string parentFeatureID = "07FAC810-B8B7-4501-B905-372236479BA5";
Guid childFeatureID = new Guid("2f53989d-5799-4f65-bce8-0e1e7c1ae6c4");

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPWeb web = SPContext.Current.Web;
web.AllowUnsafeUpdates = true;
SPSite site = web.Site;
SPWeb siteRootWeb = web.Site.RootWeb;
siteRootWeb.AllowUnsafeUpdates = true;
try
{
//Check for parent feature(Content Types and custom Fields) activeness
if (checkForParentFeatureActive(site, parentFeatureID))
{
// get a reference to the "Customers" list
SPList customersList = null;
if (checkIfListExist(web.Lists, customersListTitle))
{
if (web.ServerRelativeUrl == "/")
{
customersList = (SPList)web.GetList(customersListURL);
}
else
{
customersList = (SPList)web.GetList(web.ServerRelativeUrl + customersListURL);
}
}
// if the "Customers" list exists
if (customersList != null)
{
//correct customers Lookup Column
ChangeSiteColumnList(siteRootWeb, web, new Guid(customersLookupColumnGUID), customersList.ID);

}
else
{
//Create customer list if it does not exist
Guid guid;

guid = CreateListFromTemplate(web, customersListTitle, customersListTemplateName, customersListDescription);
customersList = web.Lists[guid];
customersList.OnQuickLaunch = true;
customersList.Update();
//correct customers Lookup Column
ChangeSiteColumnList(siteRootWeb, web, new Guid(customersLookupColumnGUID), guid);
}
}
else
{
/*deactivate the current feature and throw a error message to the user saying that
he has to activate the parent feature before activating this feature*/
web.Features.Remove(childFeatureID, true);
SPUtility.TransferToErrorPage("Please activate the parent feature(MTD Fax Log Content Types) on the site collection before activating this feature.");
}
}
catch (System.Threading.ThreadAbortException ex)
{
PortalLog.LogString(ex.StackTrace);
}
catch (System.Exception ex)
{
PortalLog.LogString(ex.StackTrace);
throw new SPException(ex.Message);
}
finally
{
web.Update();
web.AllowUnsafeUpdates = false;
siteRootWeb.Update();
siteRootWeb.AllowUnsafeUpdates = false;
siteRootWeb.Dispose();
site.Dispose();
}
}

private bool ChangeSiteColumnList(SPWeb rootWeb, SPWeb web, Guid siteColumnGuid, Guid lookupListGuid)
{
if (!(rootWeb.Fields[siteColumnGuid] is SPFieldLookup))
return false;
// cast lookup field
SPFieldLookup field = (SPFieldLookup)rootWeb.Fields[siteColumnGuid];
// change field lookup list id
field.SchemaXml = field.SchemaXml.Replace(field.LookupList.ToString(), lookupListGuid.ToString());
// reload field (a msdn article says you need to reload the column after changing the SchemaXml
field = (SPFieldLookup)rootWeb.Fields[siteColumnGuid];
//we need to change the "field.WebID" to the web which contains the lookup lists.
//Otherwise SharePoint tries to find the list on the current web which results in empty lists and
//"List not Found Errors" in the SharePoint log if you use this lookup columns in any subsite.
//if lookup list is in the subsite and site column is in the site collection then "web.ID" is the lookup list subsite id.
field.LookupWebId = web.ID;
field.Update();
return true;
}

private Guid CreateListFromTemplate(SPWeb web, string listTitle, string listTemplateName, string listDescription)
{
// read list template
SPListTemplate listTemplate = web.ListTemplates[listTemplateName];
// create list
return web.Lists.Add(listTitle, listDescription, listTemplate);
}

private bool checkIfListExist(SPListCollection ListCollection, string ListName)
{
bool flag = false;
foreach (SPList list in ListCollection)
{
if (list.Title.Equals(ListName))
{
flag = true;
break;
}
}
return flag;
}
//"Site" is the Root site collection
//"FeatureID" is the parent feature at the sitecollection level
private bool checkForParentFeatureActive(SPSite site, string featureID)
{
//This feature collection contains only features that are active.
SPFeatureCollection featureCollection = site.Features;
foreach (SPFeature feature in featureCollection)

{
if (feature.DefinitionId.ToString().ToLower() == featureID.ToLower())
{
return true;
}
}
return false;
}
Note: Most of the help for Correcting has been copied from this article.
http://msdn.microsoft.com/en-us/library/ms196289.aspx

No comments:

Post a Comment