Back to Insights

How to define a LookupField referencing list in another web

Here is the scenario in question. We would like to have a "Students" list provisioned in the root of the web and we would like to have another list in a different sub-web have a lookup field (or multi-lookup) referencing the list. We know that if we want to define a LookupField that references a list in the same web it can be defined as following:

<Field Name="MyStudents" Type="LookupMulti"
 ShowField="Title"
 Mult="TRUE"
 Sortable="FALSE"
 UnlimitedLengthInDocumentLibrary="FALSE"
 ID="{361e6094-4461-4b6a-90db-bb9957e20364}"
 List="Lists/Student" />

"Lists/Student" is the URL to the list in the same web. However, what if we need the lookup field to reference a list in a different web? You can't use a relative URL to another web. You also can't use the list ID since the list instance ID will always get regenerated during provision, (i.e. the provisioned list does not respect the value specified in ID attribute of <ListInstance> in CAML). In my previous projects, we had to stay away from declarative CAML and use an API in the SPFieldCollection.AddLookup method to manually create the fields within a given SPList instance. There is nothing wrong with this approach but it would be more agile to leverage CAML as the switch between field type will be more straightforward (like Choice to Lookup and vice versa or moving the target list to another web).

An approach I discovered is we can have a hybrid between CAML and some custom Feature Receiver code. First of all, the lookup field can be defined as normal in CAML without the List attribute.

<Field Name="MyStudents" Type="LookupMulti"
 ShowField="Title"
 Mult="TRUE"
 Sortable="FALSE"
 UnlimitedLengthInDocumentLibrary="FALSE"
 ID="{361e6094-4461-4b6a-90db-bb9957e20364}" />

It will provision as a site column with no problems. In the other Web feature that actually provisions the Student list in the root web, we can run some FeatureReceiver code to fix the MyStudent field. The following is a generic FeatureReceiver that can fix a bunch of lookup fields:

public class WebFeatureReceiver : SPFeatureReceiver
{
    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {
        SPWeb web = (SPWeb) properties.Feature.Parent;

        foreach (SPFeatureProperty property in properties.Definition.Properties)
        {
            if (property.Name.StartsWith("FixLookupField:", StringComparison.CurrentCultureIgnoreCase))
            {
                string fieldIDText = property.Name.Substring("FixLookupField:".Length);

                Guid fieldID = new Guid(fieldIDText);

                string listUrl = property.Value;

                SPList list = web.GetList(listUrl);

                FixLookupField(web.Site, fieldID, list);
            }
        }
    }

    private void FixLookupField(SPSite site, Guid fieldID, SPList list)
    {
        using (SPWeb web = site.RootWeb)
        {
            SPFieldLookup field = web.Fields[fieldID] as SPFieldLookup;

            if (field != null)
            {
                field.LookupList = list.ID.ToString();
                field.LookupWebId = list.ParentWeb.ID;
                field.Update();
            }
        }
    }
}

It can then be configured in Feature.xml as follows:

<Feature xmlns="href="http://schemas.microsoft.com/sharepoint/"
        ReceiverClass="?.WebFeatureReceiver"
         ReceiverAssembly="?"
         >
  <ElementManifests>
    ?
  </ElementManifests>
  <Properties>
    <Property Key="FixLookupField:{361e6094-4461-4b6a-90db-bb9957e20364}" Value="Lists/MyStudent" />
  </Properties>
</Feature>

Now the site column can be added to any list, even from the UI, and it will still reference to the same target list!

Share