Adding Metadata navigation and per-location setting programmatically to a list

This turned out to be harder than I thought.

Metadata navigation and per-location views are an  little known, but powerful way of making lists more useful.

It allows you to assign a default view and others views to a folder, content type or field. My need was to allow users to navigate using a Taxonomy field. Dependent on the selected field, I would like to show different fields using a view.

The first part is to add the Metadata navigation.

This is done by creating a hierarchy using MetadataNavigationHierarchy

  var list = SPContext.Current.Web.Lists.TryGetList("MyList");
  var field = list.Fields.TryGetFieldByStaticName("MyField");
  var settings = MetadataNavigationSettings.GetMetadataNavigationSettings(list);
  var hierarchy = settings.FindConfiguredHierarchy(field.Id);

  if (hierarchy == null)
  	hierarchy = new MetadataNavigationHierarchy(field);
  MetadataNavigationSettings.SetMetadataNavigationSettings(list, settings);

The hard part is adding the settings for the per location views.

This can only be done by injecting XML into the settings XML. MetadataNavigationSettings is a wrapper class around an XML snippet that is stored in a hidden property of the root folder of the list.

Have a look at SPList.RootFolder.Properties["client_MOSS_MetadataNavigationSettings"]

The XML Schema is as follows. Haven't found anything documenting this schema on MSDN yet, so this is just taken from my configuration of my list, so may differ on yours

<MetadataNavigationSettings SchemaVersion="1" IsEnabled="True"
<FolderHierarchy HideFoldersNode="True" />
<MetadataField FieldID="a96cea49-ef78-4bfa-8a69-2c49071155fb"
CachedDisplayName="Legal Document Category">
<ViewSettings UniqueNodeId="eadcb112-33af-4700-ad11-8e6afbd800e6">
<View ViewId="4f4d05f4-046f-4bfa-a37c-170847fd4e34"
CachedName="My Custom View" Index="0"
CachedUrl="Store/Forms/My Custom View.aspx" />
<View ViewId="a1ab958f-40d5-4e4a-ac3f-a10bc3cd22d2"
CachedName="My Other Custom View" Index="1"
CachedUrl="Store/Forms/AllItems.aspx" />
<View ViewId="5c9447a3-ff59-419a-92b1-c7f0191d6f82"
CachedName="Not visisible view" Index="-1"
CachedUrl="Store/Forms/Not visisible view.aspx" />
<KeyFilters />
<ManagedIndex IndexID="a96cea49-ef78-4bfa-8a69-2c49071155fb"
IndexFieldID="a96cea49-ef78-4bfa-8a69-2c49071155fb" />
<ViewSettings UniqueNodeId="">
<View ViewId="a1ab958f-40d5-4e4a-ac3f-a10bc3cd22d2"
CachedName="All Documents" Index="0"
CachedUrl="Store/Forms/AllItems.aspx" />

The part I'm interested in here is the ViewSettings and View tag. The UniqueId attribute relates to the GUID of the selected Term GUID. So this will show the views defined using the View tag when the Term is selected in the Metadata navigation.

If a View tag is added with 0 index this will be used as the default view when the term is selected, all other positive numbers will be shown in the order defined as other available views for that Term. Any negatives will not be available (You don't need to add them)

I used the following code to add these nodes programmaticallty using XLinq

var list = SPContext.Current.Web.Lists.TryGetList("MyList");
var view = list.Views.Cast().SingleOrDefault(v => v.Title == "MyView");
var session = new TaxonomySession(SPContext.Current.Site);
var field = list.Fields.TryGetFieldByStaticName("MyField");
var term = session.GetTerm("a96cea49-ef78-4bfa-8a69-2c49071155fb");
var settings = MetadataNavigationSettings.GetMetadataNavigationSettings(list);
var doc = XDocument.Parse(settings.SettingsXml);
var metaDataField = (from f in doc.Descendants("MetadataField")
let fieldId = f.Attribute("FieldID")
where fieldId != null && fieldId.Value == field.Id.ToString()
select f).SingleOrDefault();
if (metaDataField != null)
  var viewSettings = (from v in metaDataField.Elements("ViewSettings")
  let uniqueNodeId = v.Attribute("UniqueNodeId")
    where uniqueNodeId != null && uniqueNodeId.Value == term.Id.ToString()
    select v).SingleOrDefault();
  if (viewSettings == null)
new XElement("ViewSettings",
new XAttribute("UniqueNodeId", term.Id.ToString()),
new XElement("View",
new XAttribute("ViewId", view.ID.ToString()),
new XAttribute("CachedName", view.Title),
new XAttribute("Index", "0"),
new XAttribute("CachedUrl", view.Url)
settings = new MetadataNavigationSettings(doc.ToString());
MetadataNavigationSettings.SetMetadataNavigationSettings(list, settings);