Today I was playing with an Add-In to have an attachment go from Microsoft Outlook straight to SharePoint into a Document Library, but they have to specify the Metadata, and one of the metadata fields is of type “Managed Metadata”. So I had to figure out how to remotely get the choices for the metadata fields – i.e. the terms in the term set that apply only to that column.
So it turns out that you can get the SchemaXml property off of a field in a List and that will return a lot of information to you, like what some of the key ID’s are. Below is an example where I’m getting key information to be able to dynamically display a control for the metadata field in question:
ClientContext spCtx = new ClientContext(webURL);
Web remoteWeb = spCtx.Web;
List docLib = remoteWeb.Lists.GetByTitle(listName);
spCtx.Load(docLib);
spCtx.Load(docLib.Fields);
spCtx.ExecuteQuery();
Control lastControl = null;
foreach (Field field in docLib.Fields)
{
int top = 5;
if (lastControl != null)
{
top = lastControl.Top + lastControl.Height + 5;
}
if (!field.Hidden && !field.ReadOnlyField)
{
if (field.InternalName != "FileLeafRef" &&
field.InternalName != "ContentType" &&
field.Title != "Title")
{
// Title will be same as File Leaf Ref (Name)
Label newLbl = new Label();
newLbl.AutoSize = false;
newLbl.Width = 200;
newLbl.Top = top;
newLbl.Left = 5;
newLbl.Text = field.Title;
pnlControls.Controls.Add(newLbl);
switch (field.TypeDisplayName)
{
case "Managed Metadata":
string xml = field.SchemaXml;
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(xml);
string termSetID = string.Empty;
string anchorID = string.Empty;
string sspID = string.Empty;
XmlNode termSetIDNode = xDoc.SelectSingleNode("//Property/Value[../Name = 'TermSetId']");
if (termSetIDNode != null)
{
termSetID = termSetIDNode.InnerText;
}
XmlNode anchorIDNode = xDoc.SelectSingleNode("//Property/Value[../Name = 'AnchorId']");
if (anchorIDNode != null)
{
anchorID = anchorIDNode.InnerText;
}
XmlNode sspIDNode = xDoc.SelectSingleNode("//Property/Value[../Name = 'SspId']");
if (sspIDNode != null)
{
sspID = sspIDNode.InnerText;
}
ManagedMetadataField mmdFld = new ManagedMetadataField(webURL, sspID, termSetID, anchorID);
mmdFld.Left = 210;
mmdFld.Top = top;
mmdFld.Tag = field.InternalName;
lastControl = mmdFld;
pnlControls.Controls.Add(mmdFld);
break;
default:
TextBox slineOText = new TextBox();
slineOText.Width = 264;
slineOText.Tag = field.InternalName;
slineOText.Left = 210;
slineOText.Top = top;
lastControl = slineOText;
pnlControls.Controls.Add(slineOText);
break;
}
}
}
}
Then, in the user control for the Managed Metadata Field type, I call on the TaxonomyClientService web service to get the terms in the term set. The code is below, however I don’t know what the significance of the <timestamp> piece is yet. Also, I don’t know if those @a9′s and @a45′s and such actually change names depending on ??
List<Term> relevantTerms = new List<Term>();
if (!string.IsNullOrEmpty(termSetID))
{
taxonomyWS.Taxonomywebservice ws = new taxonomyWS.Taxonomywebservice();
ws.Url = webURL + "/_vti_bin/TaxonomyClientService.asmx";
// TODO
ws.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
string oldtimestamp = "<timeStamp>633992461437070000</timeStamp>";
string clientVersion = "<version>3</version>";
string termStoreIds = "<termStoreId>" + sspID + "</termStoreId>";
string termSetIds = "<termSetId>" + termSetID + "</termSetId>";
int lcidEnglish = System.Globalization.CultureInfo.GetCultureInfo("en-US").LCID;
string timeStamp = string.Empty;
string termSet = ws.GetTermSets(termStoreIds, termSetIds, lcidEnglish, oldtimestamp, clientVersion, out timeStamp);
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(termSet);
XmlNodeList relevantTermNodes = null;
if (!string.IsNullOrEmpty(anchorID) && anchorID != "00000000-0000-0000-0000-000000000000")
{
XmlNode anchorNode = xDoc.SelectSingleNode("//T[@a9 = '" + anchorID + "']");
if (anchorNode != null)
{
relevantTermNodes = xDoc.SelectNodes("//T[contains(TMS/TM/@a45,'" + anchorID + "')]");
}
}
if(relevantTermNodes == null)
{
relevantTermNodes = xDoc.SelectNodes("//T");
}
foreach (XmlNode node in relevantTermNodes)
{
string id = string.Empty;
string name = string.Empty;
string path = string.Empty;
XmlNode idNode = node.SelectSingleNode("@a9");
if (idNode != null)
{
id = idNode.InnerText;
}
XmlNode pathNode = node.SelectSingleNode("TMS/TM/@a40");
if (pathNode != null)
{
path = pathNode.InnerText;
}
XmlNodeList nameNodes = node.SelectNodes("LS/TL/@a32");
bool first = true;
List<string> extraNames = new List<string>();
foreach (XmlNode nameNode in nameNodes)
{
if (nameNode != null)
{
if (first)
{
name = nameNode.InnerText;
}
else
{
extraNames.Add(nameNode.InnerText);
}
}
first = false;
}
if (!string.IsNullOrEmpty(id) &&
!string.IsNullOrEmpty(name))
{
Term newTerm = new Term(id, name, path);
if (extraNames.Count > 0)
{
newTerm.AlternateTerms = extraNames;
}
relevantTerms.Add(newTerm);
}
}
}
relevantTerms.Sort();
return relevantTerms;
Note the use here of a custom class called Term and the fact that you can have Alternate Terms.
Finally, when I go to return the final selected value, you have to get the WssID from the hidden list called TaxonomyHiddenList.
public string GetSelectedValue()
{
if(!string.IsNullOrEmpty(txtTermValue.Text))
{
foreach (Term term in terms)
{
if (term.TermName.ToLower() == txtTermValue.Text.ToLower())
{
// Found the right term
return GetWssIDByTermID(term.TermID) + ";#" + term.TermName + "|" + term.TermID;
}
}
}
return null;
}
private string GetWssIDByTermID(string termID)
{
int result = -1;
try
{
ClientContext spCtx = new ClientContext(webURL);
Web web = spCtx.Web;
List hiddenList = web.Lists.GetByTitle("TaxonomyHiddenList");
CamlQuery query = new CamlQuery();
query.ViewXml = "<View><Query><Where><Eq><FieldRef Name='IdForTerm' /><Value Type='Text'>" + termID + "</Value></Eq></Where></Query></View>";
ListItemCollection items = hiddenList.GetItems(query);
spCtx.ExecuteQuery();
if (items != null)
{
spCtx.Load(items);
spCtx.ExecuteQuery();
if (items.Count > 0)
{
result = Convert.ToInt32(items[0]["ID"]);
}
}
}
catch
{
result = -1;
}
return result.ToString();
}