SharePoint Taxonomy Field Internal

Overview


This post describes SharePoint Taxonomy field.



Taxonomy FieldValue class has three important properties (at least, for now):

Label (string): it is a Value (string) property of a Label selected by user from Labels property of a Term object.

TermGuid (string, not Guid): it is an Id (Guid) property of a Term (inherited from TaxonomyItem).

WssId (int): what is it and where its value comes from?

When you assign a value for a TaxonomyField, an item is created for the selected Term (or if there is already an item for the term, that item starts to be used)  in a hidden list called TaxonomyHiddenList in the root web of the site. The WssId is an Id of this item in the TaxonomyHiddenList .

To track the call chain of this process, see the following internal or private methods of TaxonomyField (if you don’t like boring deep technical content, you can skip this section):

The ValidateTaxonomyFieldValue is supposed for field value validation. First, if the value is not yet resolved, it tries to resolve it using the GetOrResolveUnvalidatedTaxonomyFieldvalue method. Both of these methods call the AddTaxonomyGuidToWss method, that calls for its turn the AddTaxonomyGuidToWssCore method with elevated privileges. This method calls first the GetLookupList method to get the TaxonomyHiddenList for the current site through the GetLookupListHelper method. There is a private static class of type ThreadSafeDictionary<Guid, Guid> called  lookupListIDs to help this lookup process and make it faster. After creating a new item in the hidden list, the  AddTaxonomyGuidToWssCore method returns with the ID of the new item.


What is WssId?


The first part ([WssId]) can be obtained from a hidden list at the root of the site collection called “TaxonomyHiddenList”.  You can browse it on http://sharepointurl/Lists/TaxonomyHiddenList.  The “ID” field (after you enable it in the default view of this list) is the WssId that you can use.

However, Wss Id values are available only for terms you have already used.  If you have a fresh term (i.e., a term defined in the managed metadata service app, but not used yet in the site collection), you will not be able to find that term in this hidden list.  That is why you will not be able to add this new term as a value for a taxonomy field in your lists.  With help of server object model we can use the TaxonomyField object to set this value.

How to get the WSSID of a taxonomy field


We’ve found two alternatives.

The first alternative – trivial one – static GetWssIdsOfTerm method of the TaxonomyField class:

int[] wssIds = TaxonomyField.GetWssIdsOfTerm(site, term.TermStore.Id, term.TermSet.Id, term.Id, false, 1);

wssIds[0] will contain the ID we need.

The other way is a bit more complicated but illustrates how to work with the TaxonomyHiddenList list:

public static int GetWssIdByTermId(SPWeb rootWeb, Guid termId)
{
       int result = -1;
       if (rootWeb.Properties.ContainsKey("TaxonomyHiddenList"))
       {
              Guid taxonomyHiddenListId = new Guid(rootWeb.Properties["TaxonomyHiddenList"]);
              SPList taxonomyHiddenList = rootWeb.Lists[taxonomyHiddenListId];
              SPQuery query = new SPQuery();
              // we might have included the IdForTermSet in the query but we assume that Guid is really unique
              // so there should not be temrs in other terms sets having the same ID
              query.Query = String.Format(@"<Where><Eq><FieldRef Name='IdForTerm' /><Value Type='Text'>{0}</Value></Eq></Where>", termId);

              SPListItemCollection items = taxonomyHiddenList.GetItems(query);
              if (items.Count == 1)
              {
                      result = int.Parse(items[0]["ID"].ToString());
              }
       }
       return result;
}

How to create WssId for terms that are not yet referenced from the site


The lookup item for the term and the corresponding WssId is created on the site through the private AddTaxonomyGuidToWss method.

The following method – having the same name and signature as the original one – illustrates this call:

private static int AddTaxonomyGuidToWss(SPSite site, Term term, bool isKeywordField)
{
       int result = -1;

       Type taxonomyFieldType = typeof(TaxonomyField);
       MethodInfo mi_AddTaxonomyGuidToWss = taxonomyFieldType.GetMethod("AddTaxonomyGuidToWss",
                      BindingFlags.NonPublic | BindingFlags.Static, null,
                      new Type[3] { typeof(SPSite), typeof(Term), typeof(bool) },
                      null
                      );
       if (mi_AddTaxonomyGuidToWss != null)
       {
              result = (int)mi_AddTaxonomyGuidToWss.Invoke(null, new object[3] { site, term, isKeywordField });
       }

       return result;
}


First we should check for the WssId of a term (assumed as not being in use on the site), then create the WssId and then try to display the value of the WssId again.

Taxonomy Field and SharePoint Workflow


SharePoint workflow uses different formats to get and set managed metadata field values. When retrieving a managed metadata field value, SharePoint returns the format “VAL|GUID”. When setting a value, the format "WSSID;#VAL|GUID" is required instead, where WSSID is the ID of the associated row in the hidden taxonomy list.

How to get "WSSID;#VAL|GUID" string from List Item Field


Use Get assignable value from taxonomy field activity from Virto Workflow Activities Kit. The activity Returns assignable value from taxonomy field in this format: "WSSID;#VAL|GUID".




Activity supports managed metadata fields that accept multiple values.

Then you can execute any Update activity.

 If you need to update managed metadata fields that accept multiple values, you should use [TaxonomyFielName]_0 column.

How to get "WSSID;#VAL|GUID" string for a Term


Use Resolve term internal value from managed metadata activity from Virto Workflow Activities Kit. This activity resolves the internal values of a taxonomy field in this format: "WSSID;#VAL|GUID".




Activity creates WssId automatically for terms that are not referenced yet from the site.

Taxonomy Field and SharePoint Web Service


When you try to update taxonomy field through web services, you might be surprised a little.
It looks like specifying the taxonomy field value this way directly to the taxonomy field isn’t possible through the web services.  This means that the code below wouldn’t work (when I say wouldn’t work, I mean you won’t see any exceptions but the taxonomy field will not be updated).

   1: Lists list = new Lists();
   2: list.Url = siteUrl + "/_vti_bin/lists.asmx";
   3: list.Credentials = CredentialCache.DefaultCredentials;
   4: 
   5: XmlNode ndListView = list.GetListAndView(listName, "");
   6: string strListID = ndListView.ChildNodes[0].Attributes["Name"].Value;
   7: string strViewID = ndListView.ChildNodes[1].Attributes["Name"].Value;
   8: 
   9: XmlDocument doc = new XmlDocument();
  10: XmlElement batchElement = doc.CreateElement("Batch");
  11: batchElement.SetAttribute("OnError", "Return");
  12: batchElement.SetAttribute("ListVersion", "1");
  13: batchElement.SetAttribute("ViewName", strViewID);
  14: 
  15: batchElement.InnerXml = "<Method ID='1' Cmd='Update'>" +
  16:     "<Field Name='ID'>3</Field>" +
  17:     "<Field Name='Title'>Modified through lists.asmx web service 1</Field>" +
  18:     "<Field Name='Country'>3;#Germany</Field>" +
  19:     "<Field Name='Continent'>2;#Northamerica|d511f3c7-377f-480f-aff6-beebecd3c675</Field></Method>";
  20:    
  21:     XmlNode ndResult;
  22:     try
  23:     {
  24:         ndResult = list.UpdateListItems(strListID, batchElement);
  25:         richTextBox1.Text = ndResult.OuterXml;
  26:     }
  27:     catch (SoapServerException soapException)
  28:     {
  29:         richTextBox1.Text = "Error: " + soapException.Message + Environment.NewLine +
  30:                     "Stack Trace: " + soapException.StackTrace;
  31:     }

There’s another hidden field of “Note” type that actually needs to be updated in order to update the taxonomy field through lists.asmx web service.  Running a small Windows PowerShell script to fetch all the fields from the list will show you that hidden taxonomy field (one of every single taxonomy fields you have in the list).

   1: PS C:\Windows\System32\inetsrv> $web = get-spweb http://localhost:100
   2: PS C:\Windows\System32\inetsrv> $list = $web.Lists["ManagedMetaData"]
   3: PS C:\Windows\System32\inetsrv> foreach($fld in $list.Fields){$fld.Title + " :: " + $fld.InternalName + " :: " + $fld.Type}

As a result, you should see the hidden taxonomy field of “Note” type corresponding to the field you are trying to update.  In my case, here’s a part of the output:

HTML File Link :: xd_ProgID :: Text
Is Signed :: xd_Signature :: Boolean
Country :: Country :: Lookup
Continent :: Continent :: Invalid
Continent_0 :: ContinentTaxHTField0 :: Note
Taxonomy Catch All Column :: TaxCatchAll :: Lookup

Yep, you can find this out using OM code as well.  But Windows PowerShell is simply powerful, neat and I wanted to brag about knowing it a little bit.
Then, all I had to do was to use “ContinentTaxHTField0” (this is the internal name of this field) instead of “Continent” in my web service code.  Here’s the correct code:

   1: Lists list = new Lists();
   2: list.Url = siteUrl + "/_vti_bin/lists.asmx";
   3: list.Credentials = CredentialCache.DefaultCredentials;
   4: 
   5: XmlNode ndListView = list.GetListAndView(listName, "");
   6: string strListID = ndListView.ChildNodes[0].Attributes["Name"].Value;
   7: string strViewID = ndListView.ChildNodes[1].Attributes["Name"].Value;
   8: 
   9: XmlDocument doc = new XmlDocument();
  10: XmlElement batchElement = doc.CreateElement("Batch");
  11: batchElement.SetAttribute("OnError", "Return");
  12: batchElement.SetAttribute("ListVersion", "1");
  13: batchElement.SetAttribute("ViewName", strViewID);
  14: 
  15: batchElement.InnerXml = "<Method ID='1' Cmd='Update'>" +
  16:     "<Field Name='ID'>3</Field>" +
  17:     "<Field Name='Title'>Modified through lists.asmx web service 1</Field>" +
  18:     "<Field Name='Country'>3;#Germany</Field>" +               
  19:     "<Field Name='ContinentTaxHTField0'>2;#Northamerica|d511f3c7-377f-480f-aff6-beebecd3c675</Field></Method>";
  20:            
  21:     XmlNode ndResult;
  22: 
  23:     try
  24:     {           
  25:         ndResult = list.UpdateListItems(strListID, batchElement);
  26:         richTextBox1.Text = ndResult.OuterXml;
  27:     }
  28:     catch (SoapServerException soapException)
  29:     {
  30:         richTextBox1.Text = "Error: " + soapException.Message + Environment.NewLine +
  31:             "Stack Trace: " + soapException.StackTrace;
  32:     }


This works like charm and the update is done successfully.

Taxonomy Field and Silverlight Client Object Model


Unfortunately, you will not be able to work with taxonomy fields with any of the client object models.

References