Custom Workflow Activity for Getting Managed Metadata Field Values

In this post I'll show you how to build a custom workflow activity in Visual Studio that gets managed metadata field values from a SharePoint 2013 list or library. You can use the workflow activity in any SharePoint Designer list workflows, including on Office 365 sites - custom workflow activities in SharePoint 2013 are entirely declarative, so deploying to SharePoint Online is not a problem.

This is the second of a three-part series on working with managed metadata fields in workflows:
I'll assume you know the basics of how to build and deploy custom workflow activities - if you need a bit more detail on the deployment side of things, have a read through my first post on custom workflow activities. For now, let's just say I've created a new custom workflow activity named Get MMS Field Value in Visual Studio.

Arguments and Variables

I'll start with a quick run through of the arguments and variables I've used in the activity. First the arguments:

The activity will get the value of a managed metadata field from the current list item, so we only need the caller to provide one argument value - the name of the field (mmsFieldName). We want to return two values to the caller: the term GUID (termGuidOut) and the term label integer (labelOut).

Now the variables:

We'll use listItemFields to store a JSON representation of the list item. The DynamicValue type is perfect for storing and manipulating JSON data. We'll use fieldPathTermGuid and fieldPathLabel to build the XPath expressions we need in order to isolate and extract the term GUID and the term label from the list item JSON.

Activity Design

The workflow activity consists of seven child activities:

  1. Get the current list item.
  2. Build an XPath expression to find the TermGuid property of the specified managed metadata field value.
  3. Build an XPath expression to find the Label property of the specified managed metadata field value.
  4. Add a try-catch block so we can catch any errors when we parse the list item.
  5. Within the try block, use the XPath expression we created earlier to get the TermGuid property from the managed metadata field value.
  6. Within the try block, use the XPath expression we created earlier to get the Label property from the managed metadata field value.
  7. Within the catch block, catch invalid operation exceptions and log the details to the workflow history list. (Invalid operation exceptions occur if the specified field does not exist or is not a managed metadata field.)

Let's walk through each of these in turn.

Step 1 - Get the current list item

The first task is to retrieve the list item from which you want to extract the managed metadata field:

I've used a LookupSPListItem activity to do this. The activity returns the JSON representation of the list item as a DynamicValue instance, which I've assigned to the listItemFields variable.

Step 2 - Build an XPath expression to find the TermGuid property

Now we've got the JSON representation of the list item, we need to figure out how to extract the properties we need. The only effective way to do this is to use the Visual Studio debugger (or a web debugger such as Fiddler) to take a look at the raw JSON data. In my development environment, it looks something like this:

{"d":{"results":[{"__metadata":{"id":"Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)","uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)","etag":"\"59\"","type":"SP.Data.CustomersListItem"},"FirstUniqueAncestorSecurableObject":{"__deferred":{"uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FirstUniqueAncestorSecurableObject"}},"RoleAssignments":{"__deferred":{"uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/RoleAssignments"}},"AttachmentFiles":{"__deferred":{"uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/AttachmentFiles"}},"ContentType":{"__deferred":{"uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/ContentType"}},"FieldValuesAsHtml":{"__deferred":{"uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FieldValuesAsHtml"}},"FieldValuesAsText":{"__deferred":{"uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FieldValuesAsText"}},"FieldValuesForEdit":{"__deferred":{"uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FieldValuesForEdit"}},"File":{"__deferred":{"uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/File"}},"Folder":{"__deferred":{"uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/Folder"}},"ParentList":{"__deferred":{"uri":"http:\/\/\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/ParentList"}},"FileSystemObjectType":0,"Id":5,"ContentTypeId":"0x0100B48DCF9FDA5C9B4BACDCC7DF84D111480007A91266528ED647BA59883DA1C209A8","Title":"Test5","Customer":{"__metadata":{"type":"SP.Taxonomy.TaxonomyFieldValue"},"Label":"1","TermGuid":"47081a2a-c78d-495c-bea9-e1ba8522e881","WssId":1},"TempCol":null,"MMS_x0020_Test_x0020_2":null,"Test_x0020_Create_x0020_List_x00":{"__metadata":{"type":"SP.FieldUrlValue"},"Description":"Stage 1","Url":"http:\/\/\/_layouts\/15\/wrkstat.aspx?List=77190d4b-c6a4-4e15-ac04-d3124492ca88&WorkflowInstanceName=96478011-ec99-469a-a209-c8b1170d0dc1"},"ID":5,"Modified":"2014-10-15T16:49:42Z","Created":"2014-10-08T13:55:57Z","AuthorId":1,"EditorId":1,"OData__UIVersionString":"1.0","Attachments":false,"GUID":"1e222378-6ebc-421f-b87f-f5374d9b8566"}]}}

I've highlighted the bits we're interested in. In this case, we can figure out that the XPath expression to get to the TermGuid property is as follows:


Note that the results property actually contains an array of one result, so we use results(0) to get the first object in the array.

We can use an Assign activity to create our XPath expression and assign it to the fieldPathTermGuid variable. If we replace Customer (the MMS field name in this example) with a placeholder for our mmsFieldName variable, the activity looks like this:

Remember that we're not using the XPath expression at this stage - we're just building an XPath expression from the workflow variables to use in a later task.

Step 3 - Build an XPath expression to find the Label property

We can use the same approach to build an XPath expression that retrieves the Label property - only the last node of the XPath expression is different. In this case we assign the XPath expression to our fieldPathLabel variable: 

Step 4 - Add a try-catch block

When we come to actually parse the list item, there's quite a lot that could go wrong. If the list item doesn't contain the specified field name, or the specified field is not a managed metadata field, the XPath expressions will fail and the workflow will throw an InvalidOperationException. As such, we want to build the parsing logic within a TryCatch activity:

Here you can see that we attempt to get the salient managed metadata field properties within a Try block, and we look for an InvalidOperationException in the Catch block. We'll look more closely at the activities within the Try and Catch blocks in the next step. For now, notice that the activities within the Try and Catch blocks are wrapped in Sequence activities. In a production scenario you will probably want to implement a more comprehensive error handling strategy, but for this proof-of-concept scenario I'm mainly interested in catching the common invalid operation exceptions.

Steps 5 and 6 - Get the TermGuid and Label property values

Within our Try block, we can use generic GetDynamicValueProperty<T> activities to get the TermGuid and Label property values:

In each case:

  • The Source property is the JSON representation of our list item.
  • The PropertyName property is the XPath expression that finds our TermGuid or Label property.
  • The Result property is the output argument that exposes each property to SharePoint Designer workflows.

Step 7 - Catch invalid operation exceptions

Within our InvalidOperationException catch block, all I'm doing at this stage is writing a (hopefully) helpful message to the workflow history list:

The Actions File

The next stage is to build the actions (.actions4) file for the workflow activity. The actions file defines the sentence that appears in SharePoint Designer when you add the custom activity to a workflow, together with the arguments (inputs and outputs) for the custom activity. I'll assume you know the basics of actions files - if not, check out my first post on custom workflow activities. In my case, the actions file looks like this:

As you can see from the Sentence attribute in the RuleDesigner element, we require the workflow designer to specify the name of the managed metadata field, and we return the term GUID and the term label integer value. Note that in the parameter definition for the mmsFieldName argument, we specify a DesignerType of FieldNames. This enables the workflow designer to select the name of the managed metadata field from a list of all the fields in the current list item.

In the SharePoint Designer workflow designer, the custom activity looks like this:

In this case, I've created a really simple SharePoint Designer workflow to test my custom activity. I use the custom activity to get the term GUID and term label integer properties from a managed metadata field named Customer, and I write the values to the workflow history list.

In the next post, I'll look at the other half of the problem - using these managed metadata field properties to update a managed metadata field in another list.


  1. I'm running into an issue with this.

    I'm running this on a SharePoint online site. I publish the solution and get the wsp and upload it to the features and all that jazz. But in the actual workflow my arguments are acting funny.

    I'm unable to click on the field to activate the field dropdown, and the output variables don't show up and all I get is the name rather than Variable: termGuid. When I go into properties for that activity i'm then able to get the field drop down. But the termGuid comes back with 'no builder specified' and LabelInt acts like a default input field.

    I've looked over the actions file a lot and unable to find and error. I'm relativity new to custom workflows, so any help would be amazing.

    1. This comment is right on. I did the same and found the same. Other parts of this blog seem solid but there is a bug in here. Wish he would have taken the time to reply :(

    2. Sorry I missed this first time around. I'm aware of the issue you're referring to, and as far as I can tell it's a bug/limitation in the SharePoint Designer UI. As Amon points out, opening up the activity properties renders a dropdown list that works as expected - that's the workaround I use, it's a couple of extra clicks, but it's functional and reliable. I don't believe there's anything you can change in the actions file that will alter this behaviour.

    3. This comment has been removed by the author.

    4. You need to add the UsesCurrentItem="true" attribute to the Action element to make the FieldNames designer work correctly.

      My complete Action element is below. I'm not sure how to workaround Blogger removing the angle brackets, so pretend the parens are angles:

      (Action Name="Get MMS Field Value" ClassName="ManagedMetadataActivities.GetManagedMetadataFieldValue"
      Category="Managed Metadata Actions" AppliesTo="all" UsesCurrentItem="true")


Post a Comment

Popular posts from this blog

The target principal name is incorrect. Cannot generate SSPI context.

Server-side activities have been updated

Versioning SharePoint Framework Packages