This is the second article in a series aimed at explaining the process of creating a MOSS site using SharePoint features. For the full series contents, see my introduction.
Last time we looked at the process of creating some SharePoint lists using VSeWSS. Sure, creating a list is a simple end-user task using the SharePoint UI, but in some scenarios such as when your site is a highly controlled internet/WCM site (or generally anywhere where we have multiple environments for dev/QA/staging/production) this deployment technique doesn't really cut it. Instead we probably want to use something more automated and repeatable than recreating such site artifacts manually each time. SharePoint 2007 supports this with Features.
Today we'll look at creating site columns which get their data from lists (lookup columns). This is a fairly common set-up, used for things like assigning metadata to a page or performing some other classification using a restricted set of values. Often the user would select the appropriate value using a dropdown shown when creating/editing a page.
Note that a similar method is to define a site column with several CHOICE elements, as below:-
However, this doesn't offer the same functionality as retrieving the values from a list. Consider the following:-
- lists can have multiple columns whereas a choice element is effectively just one column
- lists can have item-level permissions
- lists can have events, workflow, versioning etc etc.
So it's clear many scenarios are better served from a site column which gets it's data from a list.
Now, when creating site artifacts as a feature, the developer will typically construct the definition in CAML, or allow VSeWSS to do this for him/her. Sometimes however, it's just not possible to do what you want with CAML. In these cases, the solution is to use a feature receiver. This is a class in compiled code (hopefully you still remember this ;-)) in which you override some methods and use the SharePoint API to define what should happen when the feature gets activated.
So why is it not possible to create a lookup column with CAML? After all, the following fragment successfully creates a site column which gets it's data from the list with the GUID specified in the 'List' attribute:-
<field id="{ae8e6ab8-b151-4fa4-8694-3cd24ad3b1bc}" type="Lookup" displayname="Locations" required="FALSE" list="{853CEC87-259E-47CA-97A7-42630F882FB7}" showfield="LinkTitleNoMenu" unlimitedlengthindocumentlibrary="FALSE" group="COB Metadata" sourceid="{8c066b26-5a3e-4e1b-85ec-7e584cf178d7}" staticname="Locations" name="Locations"> The answer is because list GUIDs are not deterministic. When a list gets created, whether through the UI, CAML or the API, it's GUID is assigned by SharePoint. There is no way to create a list with a GUID you have assigned. And if a list gets a new GUID each time it's created, this means it will have a different GUID in each of your dev/QA/staging/production environments. If this is your scenario suddenly that CAML fragment isn't so useful. Using this technique I would have to update the list GUID in my site column's <field> element and rebuild my site column feature every time the list got deployed to a new environment (or even redeployed). Clearly, this isn't pretty since, in addition to the extra effort, you're no longer deploying the exact same thing to live that has been tested in staging.
Hence I'd suggest any artifacts which reference a list should not refer to it in a declarative CAML. A better idea is to dynamically retrieve the list's GUID using the API (i.e. in a feature receiver), and create your site column in code. Briefly, the technique I use is this:
- define site column in CAML using the fragment above
- in my feature receiver, read this XML into memory
- replace the list GUID with the real value retrieved from the API
- create the site column using SPWeb.Fields.AddFieldAsXml(sXml) where sXml is the XML <field> element as a string
So this is kind of a cross between creating the site column in CAML and creating it in code. This gives a certain amount of flexibility since other properties of the column can be changed without having to recompile the code (simply change the value in the CAML, next time the feature runs it will read the modified values).
Note one other thing you are likely to need to do is to set the LookupWebId property on the column. This allows your column to be used in different sites in your site collection, yet still correctly reference the same list in your (for example) root site.
I'll post some sample code to do this in forthcoming post.
Assuming your CAML and the code to find the ID of your list was valid, you should see that you now have a site column which gets it's data from the list you specified when the feature is activated:-
One thing to note is that it's not really possible to delete the site column when the feature is deactivated. Generally, tidying up in this way is something you should do, but a site column cannot be deleted when it is has been provisioned on a list and has data. In this case, it's valid to not do any work on feature deactivation.
We're now well on our way to having page layouts which use content types with lookup columns. Phew! Next time,
creating content types as a feature.