Workflow activity: Set Managed Metadata column

Sometimes you wonder why certain things are not possible or why Microsoft did not included that in their shipping of the product. On the other hand, it makes sure we also have a job to do 😉
One of those things is the fact that the out-of-the-box version of SharePoint 2010 seems to miss certain workflow actions that seem logical. This blog post covers one of those, the in-ability to set a Managed Metadata column.

In my current project, we encountered this behaviour when trying to create workflows that modify mms columns. Out of the box, the only way available is to use either the Update List Item or Set Field actions.
In both cases however, you need to provide the exact string for the MMS value, in the form of <id>;<value>, i.e. 34;My Value. Not ideal as this could be different here and there and could change in the future. More importantly though, it does not work in all cases. It seems to work fine when no value was set for the column yet, but as soon as it is, you are not able to modify it from the workflow.

So I decided to develop a small custom action to overcome this problem and enable us to update MMS columns, whatever their current value is. In this post, I will not go over the entire setup of developing a custom action. Please review my other post on email activities for more info on that. Instead, I will only cover the code needed specifically for this action.

Actions file

     <Action Name=Set Managed Metadata Column
             ClassName=$SharePoint.Project.FileNameWithoutExtension$.SetMMSColumn

Assembly=$SharePoint.Project.AssemblyFullName$

AppliesTo=all

Category=List Actions>
      <RuleDesigner Sentence=Update %1 with %2 from %3 and %4>
        <FieldBind Field=ColumnName Text=this column DesignerType=TextArea Id=1/>
        <FieldBind Field=TermName Text=this term DesignerType=TextArea Id=2/>
        <FieldBind Field=TermSetName Text=this termset DesignerType=TextArea Id=3/>
        <FieldBind Field=GroupName Text=this group DesignerType=TextArea Id=4/>
      </RuleDesigner>
      <Parameters>
        <Parameter Name=__Context Type=Microsoft.SharePoint.WorkflowActions.WorkflowContext Direction=In />
        <Parameter Name=__ListId Type=System.String, mscorlib Direction=In />
        <Parameter Name=__ListItem Type=System.Int32, mscorlib Direction=In />
        <Parameter Name=__ActivationProperties Type=Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties, Microsoft.SharePoint Direction=Out />
        <Paramater Name=ColumnName Type=System.String, mscorlib Direction=In/>
        <Paramater Name=TermName Type=System.String, mscorlib Direction=In/>
        <Paramater Name=TermSetName Type=System.String, mscorlib Direction=In/>
        <Paramater Name=GroupName Type=System.String, mscorlib Direction=In/>
      </Parameters>
    </Action>

As you can see above, the action defines an action with 4 parameters, the name of the MMS column, the name of the term, the name of the termset and finally the name of the group. The other 4 parameters are default parameters that allow you to get the context of the workflow.

Now let us move on to the code of the action.

       #region [ Custom Workflow Properties ]

        public static DependencyProperty ColumnNameProperty = DependencyProperty.Register(“ColumnName”, typeof(string), typeof(SetMMSColumn));
        [ValidationOption(ValidationOption.Required)]
        public string ColumnName
        {
            get
            {
                return ((string)(base.GetValue(ColumnNameProperty)));
            }
            set
            {
                base.SetValue(ColumnNameProperty, value);
            }
        }

public static DependencyProperty TermNameProperty = DependencyProperty.Register(“TermName”, typeof(string), typeof(SetMMSColumn));
       [ValidationOption(ValidationOption.Required)]
       public string TermName
        {
           get
            {
                return ((string)(base.GetValue(TermNameProperty)));
            }
            set
            {
                base.SetValue(TermNameProperty, value);
            }
        }

public static DependencyProperty TermSetNameProperty = DependencyProperty.Register(“TermSetName”, typeof(string), typeof(SetMMSColumn));
       [ValidationOption(ValidationOption.Required)]
       public string TermSetName
        {
            get
            {
                return ((string)(base.GetValue(TermSetNameProperty)));
            }
            set
            {
                base.SetValue(TermSetNameProperty, value);
            }
        }

        public static DependencyProperty GroupNameProperty = DependencyProperty.Register(“GroupName”, typeof(string), typeof(SetMMSColumn));
        [ValidationOption(ValidationOption.Required)]
        public string GroupName
        {
            get
            {
                return ((string)(base.GetValue(GroupNameProperty)));
            }
            set
            {
                base.SetValue(GroupNameProperty, value);
            }
        }

#endregion
[ Custom Workflow Properties ]

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            using (var web = __Context.Web)
            {
                   try
                   {
                    Common.AddCommentWorkflowHistory(“Start trying to set MMS column”, executionContext, WorkflowInstanceId);
                    // get column to update
                    SPList list = web.Lists.GetList(new Guid(__ListId), true);
                   SPListItem item = list.GetItemById(__ListItem);
                   Common.AddCommentWorkflowHistory(“Obtained list and item references”, executionContext, WorkflowInstanceId);
                   // get MMS column
                   TaxonomyField field = (TaxonomyField)list.Fields[ColumnName];
                   Common.AddCommentWorkflowHistory(“Obtained list, item and column references”, executionContext, WorkflowInstanceId);
                   //Get the Guid for the Term Store
                   Guid termStoreID = field.SspId;
                   TaxonomySession session = new TaxonomySession(web.Site);
                   Common.AddCommentWorkflowHistory(“Taxonomy session created”, executionContext, WorkflowInstanceId);
                   // Get group, termset and term
                   TermStore store = session.TermStores[termStoreID];
                   Common.AddCommentWorkflowHistory(“TermStore reference created”, executionContext, WorkflowInstanceId);
                   // Get Group, Set abd Term
                   Group group = store.Groups[GroupName];
                   Common.AddCommentWorkflowHistory(“Found group”, executionContext, WorkflowInstanceId);
                   TermSet set = group.TermSets[TermSetName];
                   Common.AddCommentWorkflowHistory(“Found TermSet”, executionContext, WorkflowInstanceId);
                   Term term = set.Terms[TermName];
                   Common.AddCommentWorkflowHistory(“Found Term”, executionContext, WorkflowInstanceId);
                   // Set value
                   field.SetFieldValue(item, term);
                   Common.AddCommentWorkflowHistory(“Updated column”, executionContext, WorkflowInstanceId);
                   item.Update();
                }
               catch (Exception ex)
                {
                   // Log entry to Workflow History Log
                   Common.WriteFailToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, “Failed to set MMS column. Error message: {0}”, ex.Message));
                   throw;
               }
               finally
               {
                   // Cleanup – Dispose of these private properties before exiting
                  if (__ActivationProperties != null)
                  {
                       __ActivationProperties.Dispose();
                  }
                  if (__Context != null)
                  {
                      __Context.Dispose();
                  }
                }
            }
           return ActivityExecutionStatus.Closed;
       }

Now the real meat to the bone is in the ActivityExecutionStatus method that will be called by our workflow on start. We will first obtain references to the list and current item before proceeding. Once done, we get the MMS column from the item and cast it to a TaxonomyField. We can then obtain the configured TermStore for that column through the field reference.

We then setup a TaxonomySession and obtain a reference to the TermStore. From that point on, easy to get the Group, TermSet and Term required for the update. We finally update the item using the SetFieldValue method from the TaxonomyField.

This simple action can be extended by allowing parameters from the start of the action to allow the end-user to enter the appropriate term, set and group on start of the workflow. That is however beyond the scope of this blog.

Couple of things you should be aware of.

  1. When no TermStores are returned by the session object, please ensure that your MMS service proxy is added to the default proxy group using the following PowerShell command:

    Set-SPMetadataServiceApplicationProxy -Identity “<your MMS name>” –DefaultProxyGroup

  2. If your activity does not show up in SharePoint designer, please check if you created the appropriate language folder in your project for the ACTIONS file. Misplacing this file will cause the activity not to show up.
  3. The session object is not very consistent. I found it more reliable to use the term store ID from the taxonomy field to request the TermStore from the session object.
  4. When deployed, run the workflow a couple of times. It takes a while for the connections to be setup and running the workflow directly after deployment might cause an exception in finding the TermStore.
  5. I suspect the reason why we cannot update a MMS column from a workflow using the OOB actions is because the link to the TermStore fails in doing the update. I have not reveived the OOB code using Reflector or something, but it could explain why it is not posisble.


Hope this fills a gap in your requirements!

Update: I have updated the code of the email activity to include this activity too. Download the sample solution here!

6 thoughts on “Workflow activity: Set Managed Metadata column”

  1. Patrick,

    Do you have a clean WSP for this solution? I downloaded your RAR file and found that there was several WSP files in the package. It made is very challenging to find the WSP that actually has the Managed Metadata activity in it. I found WSP at the following location had the Managed Metadata Update in the actions file.: Boom.WorkflowActivities\Boom.WorkflowActivities\bin\Debug but the one in the Boom.WorkflowActivities\bin\Debug directory did not.

    I deploy this solution to my server. reset IIS. When I go to Designer, it shows up, I set the required parameters. When I Publish the workflow, I get an validation error. Listed below.

    (0, 0) Activity ‘ID8’ validation failed: Path resolved to Referenced activity Property ‘ReturnValue’ whose PropertyType ‘System.Object’ does not match with the Target Type ‘System.String’.)

    Any help would be great!

Leave a reply to Aaron Cancel reply