Here’s another provisioning gotcha. I had some problems provisioning a site column which pointed to a list I was also provisioning through a feature. Below are some code examples of how I implemented a quick solution.
Say you want to create a new list instance in your feature…
<Elements ...>
<ListInstance Id="{A6E8895F-6C4D-4834-9769-CBA700B2356A}"
FeatureId="{00bfea71-de22-43b2-a848-c05709900100}"
TemplateType="100"
Title="Campus Locations"
Description="Campus Locations List"
Url="CampusLocations"
OnQuickLaunch="false" />
</Elements>
Now you want to create a site column which will point to the above list in the same feature:
<Field ID="{E9635D9A-3D47-45b3-B718-CFA54CF0FE50}"
SourceID="{8c066b26-5a3e-4e1b-85ec-7e584cf178d7}"
Name="Campus"
DisplayName="Campus"
StaticName="Campus"
Description="The campus or campuses to which this item relates."
Group="Custom Site Columns"
Type="LookupMulti"
List="{A6E8895F-6C4D-4834-9769-CBA700B2356A}"
ShowField="Title"
Required="FALSE"
Mult="TRUE"/>
You would assume that putting the GUID of the list in the List attribute of the Field element would hook the field to the list, right? Wrong. SharePoint re-creates the GUID of the lists you provision through <ListInstance …>, so any reference to that hard-coded GUID will fail. Hmm…
We can repair the reference to the list through a feature receiver once the column and list have been provisioned. It take a little prying, but it’s possible. I found a reference on how to do this on CodePlex, but it included a dependency on the feature manifest file, and I didn’t like that. The code below uses the XML in the database and recreates a site column pointing to the correct list, based on a string reference you pass in through the ‘List’ attribute of the <Field …> element:
public class FeatureReceiver : SPFeatureReceiver
{
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
RepairLookupColumnReferences(properties);
}public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
CleanupLookupColumns(properties);
}
public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
}
public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
}
private void RepairLookupColumnReferences(SPFeatureReceiverProperties properties)
{
// feature is scoped at Site, so the parent is type SPSite rather than SPWeb..
using (SPSite site = properties.Feature.Parent as SPSite)
{
SPWeb currentWeb = null;
Guid gRootWebId = Guid.Empty;
if (site != null)
{
currentWeb = site.RootWeb;
gRootWebId = currentWeb.ID;
}
else
{
currentWeb = properties.Feature.Parent as SPWeb;
gRootWebId = currentWeb.Site.RootWeb.ID;
}
using (currentWeb)
{
// read details from CAML..
string sFieldElement = null; string sColumnName = null; string sListName = null;
SPElementDefinitionCollection coll =
properties.Feature.Definition.GetElementDefinitions(System.Globalization.CultureInfo.CurrentCulture);
foreach (SPElementDefinition def in coll)
{
XmlNode node = def.XmlDefinition;
XmlNodeReader nReader = new XmlNodeReader(node);
while (nReader.Read())
{
if (nReader.LocalName == "Field")
{
//check to see if this is a lookup field
if (nReader.MoveToAttribute("Type"))
{
string type = nReader.Value;
nReader.MoveToElement();
if (type.ToLower() != "lookup" && type.ToLower() != "lookupmulti")
continue;
}
if (nReader.MoveToAttribute("Name"))
{
sColumnName = nReader.Value;
nReader.MoveToElement();
}
if (nReader.MoveToAttribute("List"))
{
sListName = nReader.Value;
nReader.MoveToElement();
}
sFieldElement = nReader.ReadOuterXml();
// now get reference to list, fix up CAML and create column.. SPList referencedList = currentWeb.Site.RootWeb.Lists[sListName];
string sFinalCaml = replaceListGuidString(sFieldElement, sListName, referencedList); //createLookupColumn(currentWeb, sFinalCaml, sListName); //CORRECTED ABOVE BECAUSE LIST NAME WAS BEING PASSED IN INTEAD OF COLUMN NAME createLookupColumn(currentWeb, sFinalCaml, sColumnName); } }
nReader.Close();
}
} } }
private string replaceListGuidString(string sFieldElement, string sListName, SPList referencedList)
{
string sListWithName = string.Format("List="{0}"", sListName);
string sListWithGuid = string.Format("List="{{{0}}}"", referencedList.ID);
return sFieldElement.Replace(sListWithName, sListWithGuid);
}
///
/// Attempt to delete the column. Note that this will fail if the column is in use,
/// i.e. it is used in a content type or list. I prefer to not catch the exception
/// (though it may be useful to add extra logging), hence feature deactivation/re-activation
/// will fail. This effectively means this feature cannot be deactivated whilst the column
/// is in use.
///
///
Column to delete. private void attemptColumnDelete(SPWeb web, string sColumnName)
{
SPFieldLookup lookupColumn = null;
// we don't care if there was an exception retrieving the field from the collection,
// since if it's not there we can't delete it anyway..
try
{
lookupColumn = web.Fields.GetFieldByInternalName(sColumnName) as SPFieldLookup;
}
catch (ArgumentException argExc)
{
}
if (lookupColumn != null)
{
try
{
lookupColumn.Delete();
}
catch (SPException SpExc)
{
// consider logging full explanation..
throw; } } }
private void createLookupColumn(SPWeb web, string sColumnDefinitionXml, string sColumnName)
{
// delete the column if it exists already and is not yet in use..
attemptColumnDelete(web, sColumnName);
SPFieldLookup lookupColumn = null;
// now create the column from the CAML definition.. string sCreatedColName = web.Fields.AddFieldAsXml(sColumnDefinitionXml);
// also set LookupWebId so column can be used in webs other than web which hosts list.. //lookupColumn = web.Fields[sCreatedColName] as SPFieldLookup; //CORRECTED ABOVE LINE: lookupColumn = web.Fields.GetFieldByInternalName(sCreatedColName) as SPFieldLookup; lookupColumn.LookupWebId = web.Site.RootWeb.ID; lookupColumn.Update(); }
private void CleanupLookupColumns(SPFeatureReceiverProperties properties)
{
// delete the column if it exists already and is not yet in use..
// feature is scoped at Site, so the parent is type SPSite rather than SPWeb..
using (SPSite site = properties.Feature.Parent as SPSite)
{
SPWeb currentWeb = null;
Guid gRootWebId = Guid.Empty;
if (site != null)
{
currentWeb = site.RootWeb;
gRootWebId = currentWeb.ID;
}
else
{
currentWeb = properties.Feature.Parent as SPWeb;
gRootWebId = currentWeb.Site.RootWeb.ID;
}
using (currentWeb)
{
string sColumnName = null;
SPElementDefinitionCollection coll =
properties.Feature.Definition.GetElementDefinitions(System.Globalization.CultureInfo.CurrentCulture);
foreach (SPElementDefinition def in coll)
{
XmlNode node = def.XmlDefinition;
XmlNodeReader nReader = new XmlNodeReader(node);
while (nReader.Read())
{
if (nReader.LocalName == "Field")
{
//check to see if this is a lookup field
if (nReader.MoveToAttribute("Type"))
{
string type = nReader.Value;
nReader.MoveToElement();
if (type.ToLower() != "lookup" && type.ToLower() != "lookupmulti")
continue;
}
if (nReader.MoveToAttribute("Name"))
{
sColumnName = nReader.Value;
nReader.MoveToElement();
}
attemptColumnDelete(currentWeb, sColumnName); } }
nReader.Close(); } } } } }
7 Comments
Please use the name of the list insted of the GUID .
e.g. List =”Lists/{yourListName}”
Good Luck.
You are a savior, Abdallah Mossad!
Thank you!
I’ve seen a few blogs on this. But as you said, the List value in the Field element should actually match the Url field (not the Id nor Title) of the ListInstance.
Pingback: Definir Lookup Site Columns desde una Feature - Mario Cortés Flores
Hi. I’m an administrator who needs this. How do I use this code?
Hi Shrike! You’ll have to create a SharePoint feature with a feature receive set up as the code I posted above. A quick google shows a couple of pages showing how to create a feature and the wire up code to run when the feature has been activated. Hope that helps a little!
Hi Marc
Great thanks for the prompt response!!
I’ll do my homework with your advice when I get back to business in 1 week. Perhaps I’d ask again if I get stuck at some point. All the best.
No problem. Let me know if I can help you at all.