It took a while, but in the end it turns out it can be done. Here I'm using Simple OData Client and a Service Reference to https://<host>/_vti_bin/TaxonomyClientService.asmx
called WsSpTaxonomyService
:
public Guid? GetTermGuid(string siteUrl, string userName, string password, string domain,
string listName, string fieldInternalName, string termLabel)
{
// Retrieve SspId and TermSetId
GetManagedMetadataFieldDetails(siteUrl, userName, password, domain, listName, fieldInternalName, out var sspId, out var termSetId);
// Now that we have SspId and TermSetId, we can invoke the SOAP service to obtain the item GUID
var termGuid = GetTermInfoWS(siteUrl, userName, password, domain, termLabel, sspId, termSetId);
return termGuid;
}
private static void GetManagedMetadataFieldDetails(string siteUrl, string userName, string password, string domain,
string listName, string fieldInternalName, out Guid sspId, out Guid termSetId)
{
// These attributes could be retrieved using REST with a single call to https://<host>/_api/Lists/getbytitle('<list name>')/fields/getbytitle('<field>')?$select=SspId,TermSetId
// Unfortunately Simple OData Client does not support this invocation, we need to split it into two separate calls
var spSiteUrl = !siteUrl.EndsWith("/") ? siteUrl + "/" : siteUrl;
var settings = new ODataClientSettings(new Uri(new Uri(spSiteUrl), "_api/"));
settings.Credentials = new NetworkCredential(userName, password, domain);
settings.PayloadFormat = ODataPayloadFormat.Json; // Request JSON Light
// SP does not accept the apostrophe encoding in function calls, force URL decoding
settings.BeforeRequest += (requestMessage) => {
string url = System.Web.HttpUtility.UrlDecode(requestMessage.RequestUri.AbsoluteUri);
requestMessage.RequestUri = new Uri(url);
};
var client = new ODataClient(settings);
// 1. Retrieve list GUID
// https://<host>/_api/Lists/getbytitle('<list name>')
var listEntity = client
.For("Lists")
.Function("getByTitle")
.Set(new { title = listName })
.FindEntryAsync()
.Result;
var listGuid = (Guid)listEntity["Id"];
// 2. Retrieve field details
// https://<host>/_api/Lists(guid'<guid>')/Fields?$filter=InternalName eq '<field>'
var fieldsEntity = client
.For("Lists")
.Key(listGuid)
.NavigateTo("Fields")
.Filter("InternalName eq '" + fieldInternalName + "'")
.FindEntryAsync()
.Result;
sspId = (Guid)fieldsEntity["SspId"];
termSetId = (Guid)fieldsEntity["TermSetId"];
}
private static Guid? GetTermInfoWS(string siteUrl, string userName, string password, string domain,
string termLabel, Guid sspId, Guid termSetId)
{
var spSiteUrl = !siteUrl.EndsWith("/") ? siteUrl + "/" : siteUrl;
var serviceUrl = new Uri(new Uri(spSiteUrl), "_vti_bin/TaxonomyClientService.asmx").AbsoluteUri;
WsSpTaxonomyService service = new WsSpTaxonomyService();
service.ClientCredentials.Windows.ClientCredential.UserName = userName;
service.ClientCredentials.Windows.ClientCredential.Password = password;
service.ClientCredentials.Windows.ClientCredential.Domain = domain;
var sXml = service.GetTermsByLabel(termLabel, CultureInfo.GetCultureInfo("en-US").LCID, StringMatchOption.ExactMatch, 1000,
string.Format("<termSetId>{0}</termSetId>", termSetId.ToString("D")), false);
XElement schemaRoot = XElement.Parse(sXml);
var objTerm = schemaRoot.Descendants("T").SingleOrDefault(
e => e.Element("LS").Element("TL").Attribute("a32").Value.Equals(termLabel));
Guid? termGuid = objTerm != null ? new Guid(objTerm.Attribute("a9").Value) : (Guid?)null;
return termGuid;
}
Please note that this code assumes that the service reference points to the correct site URL and that locale for the term label is US English. It can be easily generalized further, but I really need a drink now.