zaterdag 1 augustus 2009

Content types and site columns as a feature ... with (multi) lookup fields

It's always nice and clean when creating your custom content types and site columns in a SharePoint feature. I explained how to do this very fast (couple of minutes) in my previous post.
But when you also want to inlude (multi)lookup fields, things become more complicated. Lookup fields get their data from a list and therefore, when creating these fields, they are linked to the GUID of the source list. Because you never know what the GUID of a particular list will be, you can not just create a CAML xml definition of this field and install in where you want.

To solve this, we need to write a feature receiver which will execute some code just after activation of the feature. In this code we will create all the lists we need for our lookup fields and hook each lookup field to its correct source list.

When we want to use the method of the previous post to create a definition for the lookup fields, that's OK. But make sure you delete the 'List' property because we will set it later on with code. If this property is filled in before we run the code, it will crash telling that this property is read only.

Creating the feature receiver
In your project, create a new class library: FeatureReceiver.cs.

I will not post the code in total because it will not be nicely formatted. Therefore, I will post little pieces each time and explain each part:

Namespace: MyNameSpace
Class name: FeatureReceiver
Inherit from: Microsoft.SharePoint.SPFeatureReceiver

First make some global variables:
// Holds all the inner names of our custom Lookup fields
private ArrayList _lookupFieldIds = new ArrayList();
// Holds the relation between a lookup field and the list it will be linked to
private Hashtable _LookupFieldListRelation = new Hashtable();
// Holds the relation between a lookup field and the column where the information is coming from
private Hashtable _lookupShowFieldRelation = new Hashtable();


In the constructor, fill the arraylist and the two hash tables:

public FeatureReceiver()
{

// Fill _lookupFieldIds
_lookupFieldIds.Add("GCMyDivision");
_lookupFieldIds.Add("GCLevelofConfidentiality");

// Fill _LookupFieldListRelation
_LookupFieldListRelation.Add("GCMyDivision", "Divisions");
_LookupFieldListRelation.Add("GCLevelofConfidentiality", "Confidential");

// Fill _lookupShowFieldRelation
_lookupShowFieldRelation.Add("GCMyDivision", "Title");
_lookupShowFieldRelation.Add("GCLevelofConfidentiality", "Title");

}

Because we are inheriting from 'Microsoft.SharePoint.SPFeatureReceiver', we must override four functions:

public override void FeatureInstalled(SPFeatureReceiverProperties properties){ }
public override void FeatureUninstalling(SPFeatureReceiverProperties properties){ }
public override void FeatureActivated(SPFeatureReceiverProperties properties){ }
public override void FeatureDeactivating(Microsoft.SharePoint.SPFeatureReceiverProperties properties){}

We will only write code in the FeatureActivated and FeatureDeactivating (optional) function.

Use this code in the FeatureActivated override:

// Use RunWithElevatedPrivileges so we are sure we are running the code with enough rights to do all the stuff we want. Btw: in a next post, I will explain a better way to accomplish this.
SPSecurity.RunWithElevatedPrivileges(delegate()
{

try
{

// Get a reference to the parent. Notice that this is dependant on the scope of your feature. In this example, my feature scope is 'Site' and therefore, also the parent is a SPSite object. If the scope would be 'Web' for example, the parent would be a SPWeb object.
SPSite siteCollection = (SPSite)properties.Feature.Parent;
SPWeb currentWeb = siteCollection.RootWeb;

// Get a reference to the site columns
SPFieldLookup field = null;
SPFieldCollection fieldCollection = currentWeb.Fields;

String listName = default(String);

Guid listGuid = new Guid();

// Now for each lookup field we speciefied, try to find it in the site, create the correct list and hook them up
for (int i = 0; i < _lookupFieldIds.Count; i++)
{

try
{

listName = (String)_LookupFieldListRelation[(String)_lookupFieldIds[i]]; // Get the list name for the custom list we will create

listGuid = currentWeb.Lists.Add(listName, listName, SPListTemplateType.GenericList); // Create custom (=generic) list and retrieve the guid. You can of course also use other predefined list templates

field = (SPFieldLookup)fieldCollection[(String)_lookupFieldIds[i]];

// Hook up the list and the column. First specify the LookupWebId. This is always the same web as the lookup is defined on. Then provide the source list GUID and the LookupField
field.LookupWebId = currentWeb.ID;
field.LookupList = listGuid.ToString();
field.LookupField = (String)_lookupShowFieldRelation[(String)_lookupFieldIds[i]];

// Commit our changes
currentWeb.AllowUnsafeUpdates = true;
field.Update();
currentWeb.AllowUnsafeUpdates = false;

}
catch (Exception ex)
{

// Problem while creating custom list or updating field properties. Optionally, you can do some error logging here.

}

}

}
catch(Exception ex)
{

// Problem while fetching feature property data. Optionally, you can do some error logging here.

}

});


Optionally, you can also put this code in the 'FeatureDeactivating' function. It will delete the custom lists we created when the feature was activated. This of course also means the destruction of all lookup data.

// Run code under system account (Site collection admin)
SPSecurity.RunWithElevatedPrivileges(delegate()
{

// Reference to parent (scope dependant!)
SPSite siteCollection = (SPSite)properties.Feature.Parent;
SPWeb currentWeb = siteCollection.RootWeb;

String listName = default(String);
Guid listGuid = new Guid();

// For each lookup id, delete the source list
for (int i = 0; i < _lookupFieldIds.Count; i++)
{

try
{

listName = (String)_LookupFieldListRelation[(String)_lookupFieldIds[i]];

listGuid = currentWeb.Lists[listName].ID;

currentWeb.Lists.Delete(listGuid);

}
catch (Exception ex)
{

// Problem while deleting custom list. Optional error logging here

}

}

});

Hooking the feature receiver to the site column feature
Now you must of course link this feature receiver with the actual feature definition. My feature.xml looks like this:


Id="{4C0D8C63-10BF-4219-843A-FCAA1909E0E1}"
Title="Site Columns feature"
Description="Creation of all custom site columns."
Version="1.0.0.0"
Scope="Site"
Hidden="FALSE"
ReceiverAssembly="Getronics.ICTO.SharePoint.ContentTypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f443cebe7d24f267"
ReceiverClass="MyNameSpace.FeatureReceiver"
>





I provide a new GUID which will identify the feature within SharePoint, a title, a description, a scope (Web, Site, WebApplication or Farm) and then two important feature receiver properties:

ReceiverAssembly="ContentTypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f443cebe7d24f267"
ContentTypes: the name of my assembly and typicaly the name of my project
PublicKeyToken: specified by your pfx file, used to sign your assembly and make it strong named so it can be fully trusted and put into the GAC.

ReceiverClass="MyNameSpace.FeatureReceiver"
Defined which class to actually use: Namespace combined with the class name.

If you create a nice wsp file for this complete feature, it will do just fine.

Geen opmerkingen:

Een reactie posten