Sunday, 4 January 2015

Custom Workflow Activity for Setting Managed Metadata Field Values

In this post I'll show you how to build a custom workflow activity in Visual Studio that can update managed metadata field values in a SharePoint 2013 list or library. This is the final part of a three-part series on working with managed metadata fields in workflows:
  • Getting and Setting Managed Metadata Fields in SharePoint 2013 Workflows. In this post, I introduce the scenario, explain why you can't use built-in list actions to work with managed metadata fields, and provide a conceptual overview of the custom workflow activities approach.
  • Custom Workflow Activity for Getting Managed Metadata Field Values. In this post, I'll walk you through how to build a custom workflow activity in Visual Studio that gets managed metadata field values from a list item.
  • Custom Workflow Activity for Setting Managed Metadata Field Values (this post). In this post, I'll walk you through how to build a workflow activity that sets managed metadata field values on a list item.
I've said it before, but it's worth repeating - you can use these custom workflow activities 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 series of posts tackles the scenario where you want to extract a managed metadata field value from an item in one list (previous post), and then apply that value to a managed metadata field in another list (this post). The main constraint is that the managed metadata fields in the source list and the destination list must both use the same term set. 

Arguments and Variables

If you've been following the series of posts you'll be familiar with the scenario and the concepts, so let's assume we've created a brand new custom workflow activity named Set MMS Field Value in Visual Studio and jump straight into defining arguments and variables. First the arguments:














I want to be able to use this activity update a managed metadata field on any SharePoint list or library, so the first piece of information we need is an identifier for the target list or library (selectedList). Next, we need to know which list item to update. List items are commonly identified using either a GUID identifier (listItemGuid) or an integer identifier (listItemIdIn) - I've defined arguments for both so the workflow designer can use either approach to identify the target list item. Next, we need to know the name of the managed metadata field in the target list item (mmsFieldName). Finally, we need the two property values that uniquely identify our managed metadata term (termGuid and termLabelInteger).

Now the variables:













We'll use listItemId to store the integer identifier for the target list item. The emptyGuid variable is just an empty GUID that we'll use for comparison purposes, and the remaining variables (metadataDV, propertiesDV and fieldValueDV) are DynamicValue properties that we'll use to progressively build the correct JSON structure to update a managed metadata field.

Activity Design

The workflow activity consists of seven child activities that correspond to three main tasks:







































  1. Get the integer identifier of the target list item. (If the workflow designer has provided an integer identifier, use it directly. Alternatively, if the workflow designer has provided a GUID identifier, use the GUID to look up the integer identifier.)
  2. Build up the JSON payload we must provide in order to update the specified managed metadata field.
  3. Update the specified managed metadata field on the specified list item.
Let's take a closer look at these three high-level tasks.

Task 1 - Get an integer identifier for the target list item

Our first task is to get an integer identifier for the target list item. Remember that we're giving the workflow designer two options: he or she can provide either a GUID identifier or an integer identifier to specify the target list item. To cater for both scenarios, we use an If activity. If the list item GUID is equal to an empty GUID, we can assume the workflow designer has used an integer identifier to specify the target list item. In this case, we use an Assign activity to set the the listItemId variable to the value of the listItemIdIn argument. If not, we use a LookupSPListItemId activity to look up the integer identifier using the specified GUID and then set the listItemId variable accordingly.

























Task 2 - Build a JSON payload for the target managed metadata field

Our next task is to build a JSON payload for the target managed metadata field. The payload must take the following format, where <Field name> is the name of the target managed metadata field, <Term label integer> is the term label integer of the managed metadata term, and <Term GUID> is the term GUID of the managed metadata term:


{"<Field name>":{
   "__metadata":{"type":"SP.Taxonomy.TaxonomyFieldValue"},
   "Label":"<Term label integer>",
   "TermGuid":"<Term GUID>",
   "WssId":-1 }}


To take advantage of the built-in child activities in Visual Studio, we need to create our JSON payload using DynamicValue structures. Because of the nested nature of this payload, we need to build the structure progressively from the inside out. First we use a BuildDynamicValue activity to build the contents of the innermost braces (the value of the __metadata property):





















Next, we use a BuildDynamicValue activity to build the value of the middle set of braces (the value of the <Field name> property):





















Notice how we set the __metadata key to the metadataDV value we created in the previous step, thereby creating a nested DynamicValue instance.

Finally, we use a CreateDynamicValue activity to build the value of the outer set of braces:














Note: We use a CreateDynamicValue activity rather than a BuildDynamicValue activity in this task because it allows us to set the dictionary key (PropertyName) to a variable value (mmsFieldName in this case). The BuildDynamicValue activity only allows you to type static string text for the dictionary key (Path). That wouldn't work in this scenario as we don't know the name of the target managed metadata field at compile time.

Task 3 - Update the target list item

Now that we've identified our target list item and build our JSON payload, all that remains is to perform the update operation on the list item. We can use the built-in UpdateListItem activity to do this:















The Actions File

The next stage is to build the actions (.actions4) file for the workflow activity, to specify how our activity should behave when we add it in SharePoint Designer. My actions file looks like this:


















I won't go into more detail on the structure of the actions file right now, as there's nothing out of the ordinary in it and I don't want to get too repetitive. When you deploy the activity and use it in SharePoint Designer, it looks like this:















In this case, I'm using my Get MMS Field Value activity to get the term GUID and the term label integer from a managed metadata field named Customer in the current list item. I'm then using the Set MMS Field Value activity to set the value of the Client field on a list item in the Clients list to the same term. Because the source Customer field and the destination Client field both use the same term set, the workflow is able to copy the value across as desired.

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:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)","etag":"\"59\"","type":"SP.Data.CustomersListItem"},"FirstUniqueAncestorSecurableObject":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FirstUniqueAncestorSecurableObject"}},"RoleAssignments":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/RoleAssignments"}},"AttachmentFiles":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/AttachmentFiles"}},"ContentType":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/ContentType"}},"FieldValuesAsHtml":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FieldValuesAsHtml"}},"FieldValuesAsText":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FieldValuesAsText"}},"FieldValuesForEdit":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FieldValuesForEdit"}},"File":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/File"}},"Folder":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/Folder"}},"ParentList":{"__deferred":{"uri":"http:\/\/team.jason.net\/_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:\/\/team.jason.net\/_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:

d/results(0)/Customer/TermGuid

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.

Getting and Setting Managed Metadata Fields in SharePoint 2013 Workflows

In many workflow scenarios, you'll want to get a field value from a list item in one list and apply that value to a list item on another list. With most field types, you can do this easily using workflow variables and built-in list actions in SharePoint Designer 2013. However, it's widely acknowledged that working with managed metadata fields in SharePoint workflows is a bit of a nightmare. To get around the problem, I built some custom workflow activities to get and set managed metadata fields in SharePoint Designer workflows.

This is the first of a three-part series on working with managed metadata fields in workflows:


Problem overview


Managed metadata fields are similar in structure to lookup fields, with complex values of type TaxonomyFieldValue. If you use the REST API to get or set the value of a managed metadata field named Customer, the JSON payload looks something like this:

{"Customer":{
   "__metadata":{"type":"SP.Taxonomy.TaxonomyFieldValue"},
   "Label":"n",
   "TermGuid":"nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn",
   "WssId":n }}

You can see that the managed metadata field consists of three property values, where TermGuid uniquely identifies the term, Label represents the display name for the term, and WssId is the ID of the list item that stores the term locally in the TaxonomyHiddenList list.
Unfortunately, the built-in list actions in SharePoint Designer 2013 (Create List Item, Update List Item, Set Field in Current Item) will only allow you to set a managed metadata field to a string value. When you try to update a managed metadata field value, SharePoint is expecting a complex type in the format you can see above. However, any value you supply through a built-in list action in SharePoint Designer gets wrapped in quote marks and is treated as a string literal. Behind the scenes, SharePoint throws an InvalidClientQuery exception when it receives the request from your workflow, with the following message:

An unexpected 'PrimitiveValue' node was found when reading from the JSON reader. A 'StartObject' node was expected.


Distractions


You may have read that you can specify string values for managed metadata fields using the format <WssId>;#<Label>|<TermGuid>. That may have worked in SharePoint 2010 workflows, but it doesn't work in SharePoint Designer 2013 where workflow activities are built on the client web services. The error is clear - SharePoint is expecting a complex object, and it complains when the workflow manager sends it a string. It's also worth noting that the built-in list actions in SharePoint Designer don't expose hidden taxonomy fields.

Solution overview


To update the value of a managed metadata field, your workflow needs to send a request that looks something like this:

POST http://team.jason.net/_api/web/lists/getbyid(guid'fe31bb2a-8737-4085-be2e-70368115c688')/Items(29) HTTP/1.1
If-Match: *
X-HTTP-Method: MERGE
Accept: application/json; odata=verbose
Content-Type: application/json; odata=verbose
...

{"Customer":{"__metadata":{"type":"SP.Taxonomy.TaxonomyFieldValue"},"Label":"1","TermGuid":"47081a2a-c78d-495c-bea9-e1ba8522e881","WssId":"-1"},"__metadata":{"type":"SP.Data.ClientsListItem"}}

You need to plug four values into the JSON payload:
  • The name of the managed metadata field (Customer in this example). 
  • The Label value. This can either be the string-based term label or an integer-based lookup value. If you used the REST API to get the term details from an existing list item, you'll have an integer value.
  • The TermGuid value. 
  • The WssId value. You can safely set this value to -1. SharePoint will resolve the term correctly from the Label and TermGuid values.
You could do this with a Call HTTP Web Service activity in SharePoint Designer, but structuring the data would be ugly and laborious. A more elegant solution is to create a couple of custom activities in Visual Studio that you can reuse in multiple SharePoint Designer workflows. In part 2 I'll show you how to create a custom activity to get managed metadata field values, and in part 3 I'll show you how to create a custom activity to set managed metadata field values.

Monday, 17 November 2014

Site Management Workflow Activities - Sample Solution Now Available

Earlier this year, I published a series of blog posts on custom workflow activities for SharePoint 2013 and Office 365. Among other things, I covered how to build workflow activities to:

  • Create sites.
  • Set site permissions and list permissions.
  • Create groups and set group owners.
  • Break permissions inheritance.
  • Set the associated owners, members and visitors groups on a site.
Since then, several people have asked me to make my source solution available - so here it is:


Disclaimers:
  • It's proof-of-concept code, not production-ready code. 
  • For use as a learning aid in a test environment only.
  • Please read the corresponding blog posts before you play around with the custom activities - they'll make much more sense once you've read the explanations.
To build and test the solution, you'll need a test environment with a local SharePoint 2013 installation, SharePoint Designer 2013, and Visual Studio 2012 with Office Developer Tools or Visual Studio 2013.

When you build the solution, Visual Studio creates a .wsp package. You can add this to the solutions gallery on any SharePoint site (including Office 365) - workflow activities in SharePoint 2013 are entirely declarative, so you don't have to worry about resource points and other restrictions. The solution deploys a site-scoped feature named Site Management Workflow Activities. Once you've activated this feature, the custom activities will be available when you open the site in SharePoint Designer.

Final note: To run these activities, your workflow service needs to be running with app permissions and it needs full control rights over the entire site collection (as you'd expect - you're asking it to create sites, create groups, set permissions, and so on). For guidance, see my first post in the series and the MSDN article Create a workflow with elevated permissions by using the SharePoint 2013 Workflow platform.

Friday, 17 October 2014

Can't find Business Data Web Parts? Check your permissions.

Just a quick note on an issue we encountered today. Users with the Contribute permission level can, as you'd expect, edit wiki pages in SharePoint 2013. As part of the editing process, users can add various web parts to the wiki page. Today we were temporarily stumped when a user with Contribute permissions wanted to add a Visio Web Access web part to a wiki page. It turns out that this web part, along with most business data web parts, is only available when the user has the Add and Customize Pages permission (found in the Design permission level).

With the Contribute permission level, the insert web part options looked like this:



















Whereas with the Design permission level, the insert web part options looked like this:
















Conclusion - if your users are struggling to add business data web parts to wiki pages, it might be a permissions issue.

Friday, 3 October 2014

Server-side activities have been updated

If you use SharePoint Designer 2013 to build workflows, there's a fair chance you'll have come across the following error message:
Server-side activities have been updated. You need to restart SharePoint Designer to use the updated version of activities.
 Needless to say, restarting SharePoint Designer rarely makes the error go away. The usual advice is:

Approach 1: Clear the cache folders
See for example How to Clear Your SharePoint Designer 2010/2013 Cache. If you've just deployed some custom workflow activities to your site, this will probably solve your problem (and you should clear the cache folders every time you deploy custom activities). If the error occurs spontaneously, this approach often won't help.

Approach 2: Reinstall SharePoint Designer
This might work if you've got a preview version of SharePoint Designer installed. If not, it's unlikely to help. It didn't work for me, and it didn't work for countless others on the forums.

Approach 3: Install SharePoint Designer on another machine
This one kind of annoys me... it usually works, but it's hardly a practical solution to the problem.

In my case, having tried approach 1 and 2 and having established that the problem was client-specific (a colleague was able to connect to the same sites and create workflows just fine), I did some digging around to see what else could be causing the problem.

Short Answer


(If you're working on a Windows client rather than a SharePoint server)

Check your Programs list for Workflow Manager Client 1.0. If it's there, uninstall it. The Workflow Manager Client needs to run on every SharePoint server in a farm when you pair the farm with a Workflow Manager deployment. In my case, I'm using SharePoint Designer on my laptop to build workflows on an Office 365 site, so there was no reason for the Workflow Manager Client to be there. (I'm not even sure how it got there - maybe bundled with Visual Studio.)

In my case, once I'd uninstalled Workflow Manager Client, I was able to create and edit workflows in SharePoint Designer without errors.

Long Answer ("Show your work")


I started by getting a colleague, John Devaney, to run SharePoint Designer and connect to the site. Once we'd established he was able to create and edit workflows, we compared the contents of our cache folders. The contents of the WebsiteCache folder (%USERPROFILE%\AppData\Local\Microsoft\WebsiteCache) showed some key differences. When you drill down into the version folder for a particular site, mine looked like this:


























Whereas John's looked like this:




















We'd both started with empty cache folders. However, John's SharePoint Designer instance downloaded, generated, or copied in a handful of assemblies and culture definitions (the NLP files), whereas mine failed to do so. 

I experimented with copying these files across to my own cache folder. The key file turned out to be the Microsoft.SharePoint.WorkflowServices.Activities.Proxy.dll assembly. If I copied this assembly across to my cache folder, I was able to create and edit workflows. Without it, I got the Server-side activities have been updated error.

Next, I had a closer look at how SharePoint Designer goes about getting these assemblies. I used Fiddler to look at the web traffic that SharePoint Designer generates when I try to create a new workflow. It calls various client-side object model (CSOM) methods, including two methods in the WorkflowDeploymentService class:
  • GetDesignerActions. According to MSDN, this method "returns a list of valid Workflow Manager Client 1.0 actions for the specified server". As you'd expect, the response body contains a bunch of SharePoint Designer actions in .actions4 (XML) format.
  • GetActivitySignatures. This method takes a single DateTime argument named lastChanged, which suggests a connection to SharePoint Designer caching. Again, as you'd expect, the response body contains a set of activity definitions in XAML format.
The fact that these methods interact with Workflow Manager Client 1.0 on the SharePoint server got me thinking, so I checked the Programs list on my laptop. The working theory is that information on the local instance of Workflow Manager Client could conflict with the information returned by the server, and somehow prevent SharePoint Designer from generating the proxy assemblies. Regardless, uninstalling the local Workflow Manager Client instance solved the problem for me.

Seeing as it's not an easy issue to replicate, I'd be interested to hear whether this fix works for other people.

Monday, 27 January 2014

Custom Workflow Activity for Granting Permissions on a SharePoint List

In this post I'll show you how to build a custom workflow activity in Visual Studio 2012 that grants permissions on a SharePoint 2013 list or library to a user or group.

Note: this is part of a series of posts on building workflow activities to manage sites, groups, users and permissions. For a complete list of posts, together with a more detailed walkthrough of how to build a custom workflow activity and make it available in SharePoint Designer, start at the beginning.


Fundamentals

You can use the REST API to set permissions on any securable object in SharePoint 2013. However, the object on which you're trying to set permissions must not inherit permissions from a parent object. In the case of lists and libraries, this typically means that you must break permission inheritance before you attempt to set permissions. I described how to create a workflow activity that breaks permission inheritance in a previous post.

Once you've broken any permission inheritance, you grant permissions by adding a new role assignment to the securable object. You can use the REST API to do this. In the case of a list or library, you need to send a web request that resembles the following:

Endpoint:
{site collection URL}/_api/web/lists/getByTitle('{List title}')/roleassignments/addroleassignment(principalid={principal ID}, roledefid={role definition ID})

HTTP method:
POST

Headers:
Not required

Body:
Empty

Note: I described how to retrieve the integer role definition ID for a given role definition name (i.e. permission level) in a previous post, when I looked at granting permissions on SharePoint sites. In this case, I'm going to use the same Get Role Definition ID helper activity that I described in the previous post.

Build the XAML File

This is the latest in a long series of posts on creating workflow activities, so I'll keep this concise - please refer back to earlier posts if you want a bit more detail on the mechanics of building custom workflow activities for SharePoint 2013. As usual, I'll start by defining the arguments for my Grant List Permissions activity:













In this case, I need the consumer of the activity to provide:
  • The URL of the web that contains the list or library.
  • The title of the list or library.
  • The name of the permission level they want to assign.
  • The login name (title or email will also work) of the principal to whom they want to grant permissions.
The workflow activity will return the response status code returned by the REST API call.

Next, we define our activity variables:














Here we've got a variable to store our REST endpoint, variables to store the integer IDs for the principal and the role definition, and variables to store responses from the REST API call.

The complete activity looks like this:







































Let's take a brief look at each of these child activities.

Get Principal ID

Our first task is to get the integer ID of the principal to whom we want to grant permissions, using the login name provided by the activity consumer. I've used a LookupSPPrincipalId activity to do this:

















Get Role Definition ID

Next, we need to get the integer ID of the role definition we want to assign to the principal, using the permission level name provided by the activity consumer. Here, I've used a custom helper activity named GetRoleDefinitionId:

















Essentially, this helper activity uses a REST query to get the integer role definition ID for a specified role definition name. I described how to create this helper activity in a previous post, Custom Workflow Activity for Granting Permissions on a SharePoint Site.


Assign (build restUrl)

We've now got all the information we need to build our REST URL. I've used an Assign activity to do this:












The full value for the restUrl variable is as follows:
String.Format("{0}/_api/web/lists/getByTitle('{1}')/roleassignments/addroleassignment(principalid={2}, roledefid={3})", webUrl, listTitle, principalId, roleDefinitionId)


HttpSend

The next task is to send our REST request to the server, using an HttpSend activity:























Here, we're sending an HTTP POST request with an empty body to the REST URL we defined in the previous step. We're also assigning the headers, content and status code returned by the service to local variables. As I've mentioned in previous posts, I tend to assign responses to local variables through force of habit - you don't need to create and assign these variables unless you're planning to extract information from the responses.

At this stage you'll also need to configure your request headers by clicking the ellipsis button in the RequestHeaders row:


















Assign responseStatusCodeOut

In this final activity, we convert the response status code returned by the REST service into a SharePoint Designer-friendly string format. Returning a response status code is optional - it won't affect the functionality of your activity, but it could be useful if the consumer of your activity wants to perform additional actions, such as sending an email, in the event that something goes wrong.













Build the Actions File

To finalize the Grant List Permissions activity and configure it for use in SharePoint Designer, we need to edit the .actions4 file. In this case, the file should resemble the following:

<Action Name="Grant List Permissions" ClassName="SiteManagementActivities.GrantListPermissions" Category="Site Management" AppliesTo="all">
  <RuleDesigner Sentence="Grant the permission level %1 to user or group %2 on the list named %3 on web %4 (Output: %5)">
    <FieldBind  Field="permissionLevel" Text="Permission Level Name" Id="1" />
    <FieldBind  Field="principalLoginName" Text="Principal Login Name" Id="2" />
    <FieldBind  Field="listTitle" Text="List Title" Id="3" />
    <FieldBind  Field="webUrl" Text="SPWeb URL" Id="4" />
    <FieldBind  Field="responseStatusCodeOut" Text="Response Status Code" Id="5" />
  </RuleDesigner>
  <Parameters>
    <Parameter Type="System.String, mscorlib" Direction="In" Name="permissionLevel" />
    <Parameter Type="System.String, mscorlib" Direction="In" Name="principalLoginName" />
    <Parameter Type="System.String, mscorlib" Direction="In" Name="listTitle" />
    <Parameter Type="System.String, mscorlib" Direction="In" Name="webUrl" />
    <Parameter Type="System.String, mscorlib" Direction="Out" Name="responseStatusCodeOut" />
  </Parameters>
</Action>

Once you've deployed the solution, activated the feature and cleared the SharePoint Designer cache, you should be able to use the Grant List Permissions activity in SharePoint Designer workflows.

Update 17/11/14: a sample Visual Studio solution containing these workflow activities is now available for download.