Sunday, June 30, 2013

SharePoint 2013 Code Tips – Setting a Managed Metadata Field with the Client Object Model(CSOM and JSOM)

Technorati Tags: ,,,

This is my first blog post on SharePoint 2013 code tips. I am going to start this series by tackling a problem most SharePoint developers have run across when using the client object model with SharePoint 2010. The problem is updating managed metadata fields. Fortunately, this has become easier in SP2013 with the new Microsoft.SharePoint.Taxonomy.Client and the SP.Taxonomy.js file in JSOM. I wrote a blog post about updating managed metadata fields in SP2010 and you can read it here SP2010 Managed Metadata . I explained how you had to update two fields when updating managed metadata. This was confusing and error prone. Now you no longer need to update the additional field. In this post I will show you how to do this using both the managed CSOM and JSOM in the app model. I will cover updating single and multi valued managed metadata fields and a few things about accessing lists outside of the application host in Office 365. The code for this article can be found here Managed Metadata Code Samples

Updating Managed Metadata using CSOM

In order to update a managed metadata field in SP2010 you had to update two fields. One was the managed metadata field itself and the other was it’s corresponding backing text field. This was very cumbersome. First you had to use the managed metadata’s SchemaXml property to parse out the ID of its backing field and then use this ID to set the backing field. Fortunately, the new Microsoft.SharePoint.Client.Taxonomy namespace in SP2013 has made this much easier. No longer do you need to update two fields. It is also much easier to lookup the GUID for a term. The GUID is needed to update managed metadata fields. In SP2010 you needed to call the Taxonomy web service to get these and parsing the returned xml was a nightmare. The following code uses the managed SharePoint 2013 client object model to either update a single managed metadata value field or a multi-valued metadata field. The method takes a site URL, list name, item ID, the name of the managed metadata field and the label of the term. The caller of the method does not have to know anything about the term or field since the code will do all the work to get the appropriate information.

public static void SetManagedMetaDataField(string siteUrl,
string listName,
string itemID,
string fieldName,
string term)
{

ClientContext clientContext = new ClientContext(siteUrl);
List list = clientContext.Web.Lists.GetByTitle(listName);
FieldCollection fields = list.Fields;
Field field = fields.GetByInternalNameOrTitle(fieldName);

CamlQuery camlQueryForItem = new CamlQuery();
string queryXml = @"




!@itemid



";

camlQueryForItem.ViewXml = queryXml.Replace("!@itemid", itemID);

ListItemCollection listItems = list.GetItems(camlQueryForItem);


clientContext.Load(listItems, items =>; items.Include(i =>; i[fieldName]));
clientContext.Load(fields);
clientContext.Load(field);
clientContext.ExecuteQuery();

TaxonomyField txField = clientContext.CastTo(field);
string termId = GetTermIdForTerm(term, txField.TermSetId, clientContext);

ListItem item = listItems[0];

TaxonomyFieldValueCollection termValues = null;
TaxonomyFieldValue termValue = null;

string termValueString = string.Empty;

if (txField.AllowMultipleValues)
{

termValues = item[fieldName] as TaxonomyFieldValueCollection;
foreach (TaxonomyFieldValue tv in termValues)
{
termValueString += tv.WssId + ";#" + tv.Label + "|" + tv.TermGuid + ";#";
}

termValueString += "-1;#" + term + "|" + termId;
termValues = new TaxonomyFieldValueCollection(clientContext, termValueString, txField);
txField.SetFieldValueByValueCollection(item,termValues);

}
else
{
termValue = new TaxonomyFieldValue();
termValue.Label = term;
termValue.TermGuid = termId;
termValue.WssId = -1;
txField.SetFieldValueByValue(item, termValue);
}

item.Update();
clientContext.Load(item);
clientContext.ExecuteQuery();
}



The code uses the CSOM taxonomy classes TaxonomyFieldValueCollection and TaxonomyFieldValue. These make it easier for setting the different properties that are required for managed metadata values. Also the new TaxonomyField class enables the code to check whether the field accepts multiple values by using the AllowMultipleValues property. Also, these new classes expose methods for updating the field. You no longer set the field value directly on the item. The example above shows how you can append a new term to an existing group of terms. Finally, below is the code for the GetTermIdForTerm method which uses the new CSOM capabilities to search for a term’s unique ID based on a given term label using the new LabelMatchInformation class.



 public static string GetTermIdForTerm(string term,
Guid termSetId, ClientContext clientContext)
{
string termId = string.Empty;

TaxonomySession tSession = TaxonomySession.GetTaxonomySession(clientContext);
TermStore ts = tSession.GetDefaultSiteCollectionTermStore();
TermSet tset = ts.GetTermSet(termSetId);

LabelMatchInformation lmi = new LabelMatchInformation(clientContext);

lmi.Lcid = 1033;
lmi.TrimUnavailable = true;
lmi.TermLabel = term;

TermCollection termMatches = tset.GetTerms(lmi);
clientContext.Load(tSession);
clientContext.Load(ts);
clientContext.Load(tset);
clientContext.Load(termMatches);

clientContext.ExecuteQuery();

if (termMatches != null && termMatches.Count() > 0)
termId = termMatches.First().Id.ToString();

return termId;

}



What about Office 365 and JSOM




Implementing the same CSOM code in JSOM to run in Office 365 proved to be much more complex. For example, the code needs to lookup the term’s GUID before you can update the field.The CSOM is easier since it is synchronous and the calling thread waits until the lookup is completed. However when using JSOM you must make all your calls asynchronously. So in the code below subsequent calls must be made in the success call back functions. This of course makes the code hard to follow. The asynchronous requirement of JSOM also forced me to make the GetTermIdForTerm method asynchronous. In Office 365  to access lists residing in the hosting web you must set up a new SP.AppContextSite object in order to get the host web and list. All the same classes that are available in CSOM are also available in JSOM.  The Taxonomy classes are contained in the SP.Taxonomy.js file and is not loaded by default. So you must make sure this is loaded.



function SetManagedMetaDataField(listName,
itemID,
fieldName,
term) {

appweburl = decodeURIComponent(getQueryStringParameter('SPAppWebUrl'));
hostweburl = decodeURIComponent(getQueryStringParameter('SPHostUrl'));
context = new SP.ClientContext(appweburl);
factory = new SP.ProxyWebRequestExecutorFactory(appweburl);
context.set_webRequestExecutorFactory(factory);
appContextSite = new SP.AppContextSite(context, hostweburl);

var list = appContextSite.get_web().get_lists().getByTitle(listName);
var item = list.getItemById(itemID);
var field = list.get_fields().getByInternalNameOrTitle(fieldName);
var txField = context.castTo(field, SP.Taxonomy.TaxonomyField);


context.load(field);
context.load(txField);
context.load(item);


context.executeQueryAsync(
function () {

var termSetId = txField.get_termSetId().toString();
var termId;

getTermIdForTerm(function success(id) {
termId = id;
var value = item.get_item(fieldName);
var terms = new Array();

if (txField.get_allowMultipleValues()) {

var enumerator = value.getEnumerator();
while (enumerator.moveNext()) {
var tv = enumerator.get_current();
terms.push(tv.get_wssId() + ";#" + tv.get_label() + "|" + tv.get_termGuid());
}

terms.push("-1;#" + term + "|" + termId);
termValueString = terms.join(";#");
termValues = new SP.Taxonomy.TaxonomyFieldValueCollection(context, termValueString, txField);
txField.setFieldValueByValueCollection(item, termValues);
}
else {
var termValue = new SP.Taxonomy.TaxonomyFieldValue();
termValue.set_label(term);
termValue.set_termGuid(termId);
termValue.set_wssId(-1);
txField.setFieldValueByValue(item, termValue);
}

item.update();
context.executeQueryAsync(
function () {
alert('field updated');
}, function (sender, args) {
alert(args.get_message() + '\n' + args.get_stackTrace());
});
}, function (sender, args) {
alert(args.get_message() + '\n' + args.get_stackTrace());
},context, term, termSetId);

}, function error(err) {
alert(err.get_message());
});

}

function getTermIdForTerm(success, error, clientContext, term, termSetId)
{
var termId = "";

var tSession = SP.Taxonomy.TaxonomySession.getTaxonomySession(clientContext);
var ts = tSession.getDefaultSiteCollectionTermStore();
var tset = ts.getTermSet(termSetId);
var lmi = new SP.Taxonomy.LabelMatchInformation(clientContext);

lmi.set_lcid(1033);
lmi.set_trimUnavailable(true);
lmi.set_termLabel(term);

var termMatches = tset.getTerms(lmi);

clientContext.load(tSession);
clientContext.load(ts);
clientContext.load(tset);
clientContext.load(termMatches);

context.executeQueryAsync(
function () {

if (termMatches && termMatches.get_count() > 0)
termId = termMatches.get_item(0).get_id().toString();
success(termId);

}, function (sender, args) {
error(args);
});


}

function getQueryStringParameter(p) {
var params =
document.URL.split("?")[1].split("&");
var strParams = "";
for (var i = 0; i < params.length; i = i + 1) {
var singleParam = params[i].split("=");
if (singleParam[0] == p)
return singleParam[1];
}

}



Managed Metadata and SharePoint 2013




Managed metadata remote API has become a full citizen in SharePoint 2013. The taxonomy web service has been deprecated and replaced with a CSOM and JSOM client API allowing you to standardize on one remote api. Unfortunately, there is no support for manage metadata manipulation in the SharePoint 2013 REST api. So you will be forced to mix in JSOM into your REST applications for managed metadata.