Recently, I was tasked to find out how to update a taxonomy field value in a list item through SharePoint web services (lists.asmx).  Though this sounds quite simple, it turned out to be a good learning.

Taxonomy fields are very similar to lookup fields.  But at the same time, they are not lookup fields.  Internally, the values of a taxonomy fields are stored in this format: WSSID;#VAL|GUID.  The WSSID parameter is a property that uniquely identifies the list item containing the taxonomy field in a list.  More on this later.  You must be aware of ;# notation if you have worked with lookup fields through SharePoint OM.  The other pieces are the actual taxonomy value (got from the managed metadata store or taxonomy store) and its corresponding GUID separated by a pipe character.  So essentially, if you want to update a taxonomy field in a list item, you use the object model code as shown below:

   1: using (SPSite site = new SPSite(siteUrl))
   2: {
   3:     using (SPWeb web = site.OpenWeb())
   4:     {
   5:         SPList list = web.Lists[listName];
   6:         SPListItem item = list.GetItemById(1);
   7:         item["Title"] = "title updated by server object model 1";
   8:         item["Country"] = "1;#India";
   9:         item["Continent"] = "2;#Northamerica|d511f3c7-377f-480f-aff6-beebecd3c675";
  10:         item.Update();
  11:     }
  12: }

A little description on the additional fields that might be of interest: “Country” is a traditional lookup field.  “Continent” is a taxonomy field.  This is all good.  Except that when you try updating this through web services, you might be in for a little surprise!

Looks like specifying the taxonomy field value like this directly to the taxonomy field isn’t possible through web services.  So, the below code 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 type “Note” that actually needs to be updated 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}

In the result, you should see the hidden taxonomy field of type “Note” 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 that I know a little bit of it.

Now, 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!  HTH.