Working with the Taxonomy in CSOM

Building on my previous post, once you’re connected to your SharePoint server, you might need to do a few things – like set up Term Groups and Sets in the Taxonomy service. You’ll need your application to reference the Microsoft.SharePoint.Client.Taxonomy assembly.

To get the Term Store you can use the following (which I found hidden in a Microsoft example program):

TaxonomySession taxonomySession = TaxonomySession.GetTaxonomySession(clientContext);
taxonomySession.UpdateCache();
TermStore termStore = taxonomySession.GetDefaultSiteCollectionTermStore();
clientContext.Load(termStore,
	termStoreArg => termStoreArg.WorkingLanguage,
	termStoreArg => termStoreArg.Id,
	termStoreArg => termStoreArg.Groups.Include(
		groupArg => groupArg.Id,
		groupArg => groupArg.Name
	)
);
clientContext.ExecuteQuery();

That gets the term store. Now you can get or create a Term Group in it:

//Requires you know the GUID of your TermGroup, and the Name.
//Will create a TermGroup with that GUID and Name if needed.
TermGroup termGroup = termStore.Groups.ToList().FirstOrDefault(g => g.Id == termGroupId);
if(termGroup == null)
{
	// Term Group Doesn't Exist - Create it.
	termGroup = termStore.CreateGroup(name, GroupGuid);
}
ctx.Load(termGroup);
ctx.ExecuteQuery();

Next, you can get or create a Term Set:

//Requires you know the GUID of your Term Set, and the Name.
TermSet termSet = termStore.GetTermSet(termSetId);
ctx.Load(termSet);
ctx.ExecuteQuery();

if (termSet.ServerObjectIsNull.Value)
{
	//Create the Term Set
	termSet = termGroup.CreateTermSet(name, termSetId, termStore.WorkingLanguage);
	ctx.Load(termSet);
	ctx.ExecuteQuery();
}

And now you’re away. Want to get all the terms in a term set?

TermCollection terms = termSet.GetAllTerms();
clientContext.Load(terms);
clientContext.ExecuteQuery();

Edit: What if you want to get a term or terms, but not all of them? Well… I’ve not managed to get that working. I can’t get getTerms()¬†to work, no matter what I pass in, and I can’t find an example of using it. getTerm() does seem to work, but you need to know the GUID already, so you probably have all the other details anyway…

How about creating a Managed Metadata column? Well, this is a bit of a faff – you can’t create a field of a particular type directly through the CSOM, but rather you have to create the XML for a field of the correct type, and add that in. Unfortunately, the definition for managed metadata fields is particularly long – hence the long line on the string.Format() line:

internal static Field CreateTaxonomyField(ClientContext ctx, Web web, TermStore termStore, TermSet termSet,  string fieldName, bool isMulti, bool required)
{
	Guid txtFieldId = Guid.NewGuid();
	Guid taxFieldId = Guid.NewGuid();
          //Single valued, or multiple choice?
	string txType = isMulti ? "TaxonomyFieldTypeMulti" : "TaxonomyFieldType";
          //If it's single value, index it.
	string mult = isMulti ? "Mult=\"TRUE\"" : "Indexed=\"TRUE\"";

	string taxField =  string.Format("<Field Type=\"{0}\" DisplayName=\"{1}\" ID=\"{8}\" ShowField=\"Term1033\" Required=\"{2}\" EnforceUniqueValues=\"FALSE\" {3} Sortable=\"FALSE\" Name=\"{4}\"  Group=\"My Group\"><Default/><Customization><ArrayOfProperty><Property><Name>SspId</Name><Value xmlns:q1=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q1:string\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">{5}</Value></Property><Property><Name>GroupId</Name></Property><Property><Name>TermSetId</Name><Value xmlns:q2=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q2:string\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">{6}</Value></Property><Property><Name>AnchorId</Name><Value xmlns:q3=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q3:string\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">00000000-0000-0000-0000-000000000000</Value></Property><Property><Name>UserCreated</Name><Value xmlns:q4=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q4:boolean\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">false</Value></Property><Property><Name>Open</Name><Value xmlns:q5=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q5:boolean\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">false</Value></Property><Property><Name>TextField</Name><Value xmlns:q6=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q6:string\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">{7}</Value></Property><Property><Name>IsPathRendered</Name><Value xmlns:q7=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q7:boolean\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">true</Value></Property><Property><Name>IsKeyword</Name><Value xmlns:q8=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q8:boolean\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">false</Value></Property><Property><Name>TargetTemplate</Name></Property><Property><Name>CreateValuesInEditForm</Name><Value xmlns:q9=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q9:boolean\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">false</Value></Property><Property><Name>FilterAssemblyStrongName</Name><Value xmlns:q10=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q10:string\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">Microsoft.SharePoint.Taxonomy, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Value></Property><Property><Name>FilterClassName</Name><Value xmlns:q11=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q11:string\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">Microsoft.SharePoint.Taxonomy.TaxonomyField</Value></Property><Property><Name>FilterMethodName</Name><Value xmlns:q12=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q12:string\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">GetFilteringHtml</Value></Property><Property><Name>FilterJavascriptProperty</Name><Value xmlns:q13=\"http://www.w3.org/2001/XMLSchema\" p4:type=\"q13:string\" xmlns:p4=\"http://www.w3.org/2001/XMLSchema-instance\">FilteringJavascript</Value></Property></ArrayOfProperty></Customization></Field>",
		txType,
		fieldName,
		required.ToString().ToUpper(),
		mult,
		fieldName.Replace(" ",""),
		termStore.Id.ToString("D"),
		termSet.Id.ToString("D"),
		txtFieldId.ToString("B"),
		taxFieldId.ToString("B")
	);

	Field f = web.Fields.AddFieldAsXml(taxField, true, AddFieldOptions.AddFieldInternalNameHint);
	ctx.Load(f);
	ctx.ExecuteQuery();
	return f;
}

Yeah, that’s a lot of XML. Note that it references the Version 16 assembly for Filters – Microsoft.SharePoint.Taxonomy, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c – that interested me. But that should create a new Managed Metadata Site Column. Add it to a content type and use as you wish.

To populate such a field, simply set your ListItem’s field with a string in the correct format:

item["SingleValued"] = "-1;#TermName|00000000-0000-0000-0000-000000000000";
item["MultiValued"] = "-1;#TermName|00000000-0000-0000-0000-000000000000;#-1;#AnotherTermName|00000000-0000-0000-0000-000000000000";

… where 00000000-0000-0000-0000-000000000000 is the term’s GUID.

2 thoughts on “Working with the Taxonomy in CSOM

  1. Andy:

    Good info, I picked up a few new things. I’ve been weeding through the little bits and pieces available for the CSOM taxonomy and it’s an exercise in frustration. I’m writing a console app to import a bunch of terms from various sources in our on-premises SP to push our users to a more structured approach.

    I needed a way to check to see if a term already existed before trying to create it and I figured out that terms can be searched with GetTerms().

    //after you have the TermSet
    //search for the Term to see if it exists
    LabelMatchInformation termQuery = new LabelMatchInformation(ctx)
    {
    TermLabel = “Some Term”,
    //determines if only terms available for tagging are returned
    TrimUnavailable = false
    };
    var matchingTerms = termSet.GetTerms(termQuery);
    ctx.Load(matchingTerms);
    ctx.ExecuteQuery();

    if(matchingTerms.Count > 0)
    {
    //do something with the terms
    Term term = matchingTerms[0];
    }
    else
    {
    //Term doesn’t exist, create it
    Term term = termSet.CreateTerm(“Some Term”, 1033, Guid.NewGuid());
    termStore.CommitAll();
    ctx.ExecuteQuery();
    }

    I knew for a fact that only one term would get returned from my test set, but in the future I might want to iterate over the returned terms. Viewing the definition of LabelMatchInformation in Visual Studio showed me there are several more properties for it. The enum for StringMatchOption (startsWith, exactMatch) looks like it could be pretty useful.

    -Shelly

  2. Thanks Andy and Shelly. Shelly, you just provided me with the exact piece of work I needed.Thanks much.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>