SharePoint Programmatically: Provisioning Lookup Fields

,

Provisioning Content Type's fields is quite straight forward when using Solutions: using Element Manifests you can define your own Content Types and their properties among which fields. Unfortunately there is one serious con to the solution Microsoft has offered: you simply cannot provision any Lookup Field using the Element Manifest.

Lookup Fields obtain their values from an existing list. Each Lookup Field is being linked to its list using the list's ID. As the ID's are being generated after creating the instances there is no way to provision a Lookup Field linked to a newly created list during Solution deployment. That's when Feature Receivers are useful. Feature Receivers are nothing more than custom code you can attach to events triggered by a feature. Provisioning a custom field programmatically works exactly the same as doing it using the GUI: first you need to create a Site Column and then you need to attach it to the Content Type of your choice.

Assuming you have a solution with a feature called ContentDefinition responsible for deploying List Templates, provisioning List Instances and creating Site Columns and Content Types. It would look something close to:

<Feature Id="GUID"
    Title="Content Definition"
    Description="Configures content definition"
    Version="1.0.0.0"
    Scope="Site"
    Hidden="FALSE"
    xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="SiteColumns\SiteColumns.xml"/>
    <ElementManifest Location="ContentTypes\ContentTypes.xml"/>

    <!-- Lists -->

    <!-- Custom List -->
    <ElementFile Location="CustomList\schema.xml"/>
    <ElementFile Location="CustomList\AllItems.aspx"/>
    <ElementFile Location="CustomList\DispForm.aspx"/>
    <ElementFile Location="CustomList\EditForm.aspx"/>
    <ElementFile Location="CustomList\NewForm.aspx"/>
    <ElementManifest Location="CustomList\Elements.xml"/>
  </ElementManifests>
</Feature>

First of all you provision your Site Columns and Content Types. Then you deploy a custom List Definition and using Elements.xml you create a new instance of that list.

To make use of Feature Receivers you need to extend the Feature element with two attributes: ReceiverClass en ReceiverAssembly:

<Feature Id="GUID"
    Title="Content Definition"
    Description="Configures content definition"
    Version="1.0.0.0"
    Scope="Site"
    Hidden="FALSE"
    xmlns="http://schemas.microsoft.com/sharepoint/"
    ReceiverAssembly="Assembly, Version=1.0.0.0, Culture=neutral,
    PublicKeyToken=keytoken"
    ReceiverClass="Assembly.ContentDefinitionFeatureReceiver">

Now you can proceed to create the ContentDefinitionFeatureReceiver class. First of all inherit from the Microsoft.SharePoint.SPFeatureReceiver class. Then override the event you want to use: as for Fields provisioning you should use the FeatureActivated synchronous event.

As I have already mentioned you need to complete two steps in order to provision a Lookup Field: you have to create a new Site Column and then add it to the desired Content Type. The best approach is to create separate methods to complete both tasks as you might need to provision multiple Lookup Fields.

public static SPFieldLookup CreateLookupField(string fieldName,
  string group, bool required, bool allowMultipleValues, SPWeb w,
  SPList lookupList, string lookupField)
{
  w.Fields.AddLookup(fieldName, lookupList.ID, lookupList.ParentWeb.ID, required);
  SPFieldLookup lookup = (SPFieldLookup)w.Fields[fieldName];
  lookup.AllowMultipleValues = allowMultipleValues;
  lookup.LookupField = lookupField;
  lookup.Group = group;
  lookup.Update(true);
  return lookup;
}

You can use this method like this:

SPFieldLookup lookup = CreateLookupField("CustomLookup",
  "Custom fields", false, false, SPContext.Current.Site.RootWeb,
  SPContext.Current.Site.AllWebs["MyWeb"].Lists["CustomList"],
  "Title");

The next step is to link the created Lookup Field to the existing Content Type:

public static void LinkFieldToContentType(SPWeb web,
  string contentType, SPField field)
{
  SPContentType ct = web.ContentTypes[contentType];
  ct.FieldLinks.Add(new SPFieldLink(field));
  ct.Update();
}

As we have just created a method returning the created Lookup Field all we have to do is to call the LinkFieldToContentType method with the created field passed as parameter:

LinkFieldToContentType(SPContext.Current.Site.RootWeb,
  "MyContentType", (SPField)lookup);

It is important you pass the root web as the web parameter: it's the web containing the definitions of your Content Types.

And that's all. As both of the methods are static you could incorporate them in a Utility class and make use of them in any of your receiver classes.

Possibly related posts

9 Responses to “SharePoint Programmatically: Provisioning Lookup Fields”

  1. Raul Queiroga Says:

    Great post, imagine now one wants to use that content type in a new list definition and that lookup column in one of the list's views. In the views part of the list schema.xml on has to repeat the content types fields, and in the lookup case it has to have the referenced list ID…having in mind one creates a solution package to aggregate all the features, how caw we set the schema.xml's lookup column referenced list ID with the ID of the recently created list?

  2. Waldek Mastykarz Says:

    That's challenging indeed: because you add the lookup field programmatically, it's not in the Content Type while creating the schema.xml. I guess I'd go for programmatically attaching the Content Type to the list.

  3. Pedro Gomes Says:

    That's not quite true. You can provision list lookup fields using only the Schema file.

    The trick is that you can provide the list virtual path in the List attribute. Sharepoint will look for the list and get the ID for you

  4. Waldek Mastykarz Says:

    @Pedro Gomes: Are you sure? I just gave it yet another shot: although I was able to provision the field without any errors there was no connectivity to the Lookup List. Are you sure that you didn't use the cached version of the Field definition as described in comments here: http://blogs.msdn.com/joshuag/archive/2008/03/14/add-sharepoint-lookup-column-declaratively-through-caml-xml.aspx? I'm really curious in your approach.

  5. Michhes Says:

    Hi Waldek,

    > Are you sure?

    Yep, Pedro's right. You need to ensure the value supplied to the List attribute matches the List attribute on the field definition, otherwise the lookups won't populate (as per Josh's post at the above URL).

    Fields.xml:

    -Michhes

  6. Michhes Says:

    Hopefully this pastes better ;-)

    Fields.xml:

    <Field
    DisplayName="Indent Level"
    Description=""
    Group="My Cols"
    ID="{05ba7786-ec2b-4d40-bf7c-7fb98d823e1f}"
    ****List="Indent Levels"*****
    Name="MyLookup"
    Required="TRUE"
    ShowField="AgendaItemIndentLevel"
    Type="Lookup"
    UnlimitedLengthInDocumentLibrary="FALSE"
    />

    Schema.xml:

    <Field
    DisplayName="Indent Level"
    Description=""
    Group="My Cols"
    ID="{05ba7786-ec2b-4d40-bf7c-7fb98d823e1f}"
    ****List="Indent Levels"****
    Name="MyLookup"
    Required="TRUE"
    ShowField="AgendaItemIndentLevel"
    Type="Lookup"
    UnlimitedLengthInDocumentLibrary="FALSE"
    />

  7. Kelly Ford Says:

    Thanks so much for this bit of code. It has saved me loads of time.

  8. Waldek Mastykarz Says:

    @Kelly: You're welcome. Great I could help :)

  9. Marc Baye Says:

    Pedro and Michhes are nearly right ;)
    Lookups can be provisioned in the schema.xml using the URL atribute of the list where it should be linked, using the "Lists/ListName" form, not only the "ListName".
    Then, when you CREATE the instances using that list template, the lookup fiels will be linked correctly, but:
    - Only those lookups that are declared in the schema when you create the list instance, future additions to the schema.xml will create new fields on the list instances but won't be correctly linked! :(
    - The fields instantiated with this way won't upgrade if any change is made in its declaration in the original schema.xml. i.ex. if you change the format, the display name, or so on, in the schema.xml, other filds will change normally, but these lookups won't. (it's like they were customized, but without any versioning activated).

    And… I tested it on the SharePoint 2010 beta, so… no good news :(

    The programmatically way to link lookups will work always, of course, but will customize the fields too and they won't upgrade when the schema.xml changes.

Leave a Reply

Security Code:

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS
Copyright © 2007 - 2010 Waldek Mastykarz

Creative Commons License