Overview
SharePoint 2013 has introduced Microsoft.SharePoint.Client.Taxonomy namespace for metadata management via CSOM. Since Taxonomy API also allows to manage Keywords, i would like to demonstrate how to get/set Enterprise Keywords field values in this post.
Adding Enterprise Keywords column into List
The first example demonstrates how to add Enterprise Keywords column into List:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static void AddTaxkeywordFieldInList(List list) | |
{ | |
var ctx = list.Context as ClientContext; | |
var taxKeywordField = ctx.Site.RootWeb.Fields.GetByInternalNameOrTitle("TaxKeyword"); | |
var taxKeywordFieldInList = list.Fields.Add(taxKeywordField); | |
ctx.ExecuteQuery(); | |
} |
Setting Enterprise Keywords field value
The below example demonstrates how to set Enterprise Keywords field value:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Microsoft.SharePoint.Client; | |
using Microsoft.SharePoint.Client.Taxonomy; | |
namespace SharePoint.Client.Taxonomy | |
{ | |
/// <summary> | |
/// Enterpise Keyword Manager | |
/// </summary> | |
public class KeywordsManager | |
{ | |
/// <summary> | |
/// Set Enterprise Keyword Value | |
/// </summary> | |
/// <param name="item">List Item</param> | |
/// <param name="values">Keyword values</param> | |
public static void SetTaxKeywordValue(ListItem item,string[] values) | |
{ | |
var ctx = item.Context; | |
var list = item.ParentList; | |
var field = list.Fields.GetByInternalNameOrTitle(TaxKeywordFieldName); | |
var taxKeywordField = ctx.CastTo<TaxonomyField>(field); | |
var keywords = values.Select(value => EnsureKeyword(taxKeywordField, value)).ToList(); | |
taxKeywordField.SetFieldValueByValueCollection(item, new TaxonomyFieldValueCollection(ctx, GetTermsString(keywords), taxKeywordField)); | |
} | |
/// <summary> | |
/// Ensure Keyword | |
/// </summary> | |
/// <param name="taxField"></param> | |
/// <param name="name"></param> | |
/// <returns></returns> | |
private static Term EnsureKeyword(TaxonomyField taxField, string name) | |
{ | |
var ctx = taxField.Context; | |
var taxSession = TaxonomySession.GetTaxonomySession(ctx); | |
var termStore = taxSession.GetDefaultKeywordsTermStore(); | |
var keywords = termStore.KeywordsTermSet.GetAllTerms(); | |
var result = ctx.LoadQuery(keywords.Where(k => k.Name == name)); | |
ctx.ExecuteQuery(); | |
var keyword = result.FirstOrDefault(); | |
if (keyword != null) | |
{ | |
return keyword; | |
} | |
keyword = termStore.KeywordsTermSet.CreateTerm(name, DefaultLanguage, Guid.NewGuid()); | |
ctx.Load(keyword); | |
ctx.ExecuteQuery(); | |
return keyword; | |
} | |
/// <summary> | |
/// Retrieve formatted Term string | |
/// </summary> | |
/// <param name="term"></param> | |
/// <returns></returns> | |
private static string GetTermString(Term term) | |
{ | |
return string.Format("-1;#{0}{1}{2}", term.Name, TaxonomyGuidLabelDelimiter,term.Id); | |
} | |
private static string GetTermsString(IEnumerable<Term> terms) | |
{ | |
var termsString = terms.Select(GetTermString).ToList(); | |
return string.Join(";#", termsString); | |
} | |
private const string TaxKeywordFieldName = "TaxKeyword"; | |
private const int DefaultLanguage = 1033; | |
private const string TaxonomyGuidLabelDelimiter = "|"; | |
} | |
} |
Key points:
- The operation of setting Enterprise Keywords field value consists of two steps, first one to resolve Keyword in Managed Metadata service application (MMS), this what EnsureKeyword method is intended for. After Keyword has been retrieved or created if it not existed, the value of Enterprise Keyword field is set (TaxonomyFieldValueCollection type).
Usage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using (var ctx = new ClientContext(webUri)) | |
{ | |
ctx.Credentials = CreateSPOCredentials(userName, password); | |
var list = ctx.Web.Lists.GetByTitle(listTitle); | |
var item = list.GetItemById(itemId); | |
KeywordsManager.SetTaxKeywordValue(item,new []{"2013","2010"}); | |
item.Update(); | |
ctx.ExecuteQuery(); | |
} | |
public static SharePointOnlineCredentials CreateSPOCredentials(string userName,string password) | |
{ | |
var securePassword = new SecureString(); | |
foreach (var ch in password) | |
{ | |
securePassword.AppendChar(ch); | |
} | |
return new SharePointOnlineCredentials(userName, securePassword); | |
} |
Good post! Thank you. Sometimes the SharePoint API is so awkward… Do you know how I can extract a text representation of the taxonomyfieldvaluecollection? I want to copy values from one item to another. To “re-assemble” the delimited values seems a bad approach.
Thanks! Regarding converting taxonomyfieldvaluecollection into text representation, indeed, it seems there is no straightforward way.. so, i’m also not aware any other ways except parsing taxonomyfieldvaluecollection object
Thanks for this! I find it is working well but sometimes I get the error “There is already a term with the same default label and parent term”. This seems to happen with terms that have already been added to the Keywords term store. However, a lot of the terms have already been added and the KeywordsManager works well.
Have you experienced this issue before? Any guidance on your findings?
Thanks!
Vadim, please disregard my last comment. I figured out most of the issues. Most have to do with content and the fact that terms are Case Sensitive. For example, the following line of code will not find a match if the case of the terms you wish to add and the case of the terms in the store are different (for example: Blogs and blogs):
var result = ctx.LoadQuery(keywords.Where(k => k.Name == name));
However, when the code tries to create the term (since a match was not found) it throws an exception about entering a duplicate term. So creating a term seems to be case insensitive.
To circumvent this I have to load all the keywords and then perform another LINQ query with string comparison that is OrdinalCaseIgnore. Not very efficient but I get errors when trying to specify the case comparison at LoadQuery time. (k => k.Name.Equals(name, StringComparison.OrdinalCaseIgnore)) throws an error.
Additionally, I found that you need to encode the ampersand, speechmarks, and trim any white space around the term you are trying to add. There is a NormalizeName method of the Term class that helps with this. However, it requires a round trip to the server for each term so I built a custom method.
Cheers! Thanks again for a helpful blog post!!
Scott, thank you for sharing all your great findings! Indeed, it seems to be a right approach, case sensitivity should be taken into account and regarding NormalizeName, totally agree..
Hi Scott, I’ve faced the same issue, can you please the code to Vijay9896@outlook.com
Great Post!