Sunday, March 13, 2011

How to create lookup fields in CAML that works

For my first post on this blog I will demonstrate how to avoid the most common problem with deploying SharePoint lookup fields.

Basically, a lookup field is defined using CAML just like any other SharePoint field. However, there is a problem: You need to add a reference to the lookup list. There are several ways to achieve this. Below, I have outlined three standard ways to do this, and a fourth way, my preferred, that handles the problems inherent in each of the other three solutions.

First method: CAML using the list URL

A standard way to configure the lookup field with CAML is to insert the lookup list URL:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Field
    ID="{68D0555E-0A41-4CBB-BEFF-663FCD0B1DBC}"
    DisplayName="My Lookup Field"
    Name="MyLookupField"
    Group="My Fields"
    Type="Lookup"
    List="Lists/MyLookupList"
    ShowField="Title"/>
</Elements>

This is fine, as long as you only want to use the lookup field on a single web. If you are creating a lookup field that will be used across an entire site collection, this approach will not work, since the field will always try to reference a list on the local web, where that list may not exist.

Second method: CAML using list and web IDs

Another way to achieve the same result is to use the lookup list ID:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Field
    ID="{68D0555E-0A41-4CBB-BEFF-663FCD0B1DBC}"
    Name="MyLookupField"
    DisplayName="My Lookup Field"
    Group="My Fields"
    Type="Lookup"
    List="{5421D53F-8B62-4618-B3C3-1A331D4024CF}"
    ShowField="Title"/>
</Elements>

The problem here is that we do not necessarily know the list ID when the CAML for the field is written - for example if the site the field and list exist on has not yet been created.

Third method: Programmatically

A third way to create the lookup field is purely programmatically, typically in a feature receiver.

web.Fields.AddLookup(
  strDisplayName,
  lookupListId,
  lookupWebId,
  bRequired);
web.Update();

This requires you to also add the field programmatically to lists, views and content types, with the added challenge of rearranging the fields to get the desired order.

Fourth method: CAML and feature receiver

My preferred way to handle lookup fields combines the best parts of method 2 and 3 above. It goes like this: Create a lookup field in CAML, using an 'empty' ID for the list, then update this value, and the field WebId, programmatically in the feature receiver:

CAML:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Field
    ID="{68D0555E-0A41-4CBB-BEFF-663FCD0B1DBC}"
    Name="MyLookupField"
    DisplayName="My Lookup Field"
    Group="My Fields"
    Type="Lookup"
    List="{00000000-0000-0000-0000-000000000000}"
    ShowField="Title"/>
</Elements>

Feature Receiver:

public override void FeatureActivated(
  SPFeatureReceiverProperties properties)
{
  var web = ((SPSite)properties.Feature.Parent).RootWeb;
  var field = (SPFieldLookup)web
    .Fields
    .GetFieldByInternalName("MyLookupField");
  var list = web.Lists["MyLookupList"];
  field.SchemaXml = field.SchemaXml.Replace(
    "List=\"{00000000-0000-0000-0000-000000000000}\"",
    string.Format(
      "List=\"{0}\"",
      list.ID.ToString("B")));
  field.LookupWebId = list.ParentWeb.ID;
  field.Update(true);
}

The advantage of this is that you will use the correct lookup list and web IDs, without knowing these beforehand. And, since the field is defined in CAML, you may include it straightaway in list templates and content types, eliminating the need to programmatically rearrange field order:

List Template (schema.xml):

<?xml version="1.0" encoding="utf-8"?>
<List 
  xmlns:ows="Microsoft SharePoint"
  Title="My List"
  Url="Lists/MyList"
  BaseType="0">
  <MetaData>
    <Fields>
      <Field
        ID="{68D0555E-0A41-4CBB-BEFF-663FCD0B1DBC}"
        Name="MyLookupField"
        Type="Lookup"/>
    </Fields>
    <Views>
      ...
    </Views>
    <Forms>
      ...
    </Forms>
  </MetaData>
</List>

Content Type:

<?xml version="1.0" encoding="utf-8" ?>
<Elements
  xmlns="http://schemas.microsoft.com/sharepoint/">
  <ContentType
    ID="0x010012BCCE07B75A42b6896061693D823F0B"
    Name="My Content Type"
    Group="My Content Types">
    <FieldRefs>
      <FieldRef
        ID="{68D0555E-0A41-4CBB-BEFF-663FCD0B1DBC}"
        Name="MyLookupField"/>
    </FieldRefs>
  </ContentType>
</Elements>

This approach ensures that the lookup field will always target the correct list, no matter what web it is used on (inside the same site collection, that is).

4 comments:

  1. i get this error message when deploying:
    Error 5 Error occurred in deployment step 'Activate Features': The field specified with name PhoneLookup and ID {00000000-0000-0000-0000-000000000000} is not accessible or does not exist.
    0 0 StockSystem

    ReplyDelete