Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / productivity / SharePoint / SharePoint2013

Workflow (SharePoint 2013, 2010) custom action using Sandboxed Solution

5.00/5 (3 votes)
13 Sep 2016CPOL6 min read 34.2K   252  
How to create a Sandboxed Workflow Action for SharePoint On-Premise (2013 or 2010)

Introduction

In SharePoint workflow, there are many actions available which can be used from the SharePoint Designer UI. In some situations, these actions may not suit your business requirements. So what to do? You have to write a custom action for automating your business process. In this article, we will see how we can create our custom actions.

Approach of Building Custom Action

We can develop custom actions into ways such as Custom Declarative Action and Sandboxed Action. Let's discuss the difference between the two actions.

Custom Declarative Action: For creating a custom declarative action, you will find template in Visual Studio. You can use all existing actions from tool box in the left side. You cannot write any custom code here. If you need any custom code, then you have to go for the web service because you can call web service from custom activities.

Sandboxed Action: Custom Sandboxed Action lets you to write custom code. In this article, my aim is to show you how we can create Custom Sandboxed Action. I am not going to elaborate this section more because in the whole article, we will discuss about it.

Inside of the Custom Sandbox Action

Now define a business problem which will be solved by custom sandbox action. Let's say we have list called Project List. Project List has a column named Related Tender. The Related Tender column is choice type. We have another list called Tender List. Tender List has a column named Tender Number, and it is single line text type. The requirement is: whenever users will add a new item in Tender List, Tender Number will be added as new choice into Related Tender column.

Requirement

I think you can already guess what I am going to do here. I will create a custom action called Update Choice. Then, I will create a workflow for Tender List which will be triggered when a new item is added. Let's see more in action. I will try to develop the following action.

designer action

From Visual Studio, create a SharePoint Empty project and choose deploy as a sandboxed solution.

sandbox

deploy as

Now rename the Feature and open it by clicking double. Change scope Web to Site. As it's a sandboxed solution, we have to activate it in site and then it will be available to SharePoint Designer for using.

feature_rename

NB: Don't forget to change the Scope. If you forget to change it, then you will not find it in SharePoint Designer.

Basically Sandboxed Action has two major parts like Element XML and a code behind .cs class. At first, add the Element XML class.

Right click on your project, and add Empty Element from templates and also provide a meaningful name.

element_xml

Now open your Element XML file, and it should look like the following:

XML
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
</Elements>

Now, add WorkflowActions tag as inner tag of Elements tag. Then, add Action tags as inner tag of WorkflowActions.

XML
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <WorkflowActions>
    <Action Name="Update Any Choice Field"
            SandboxedFunction="true"
            Assembly="$SharePoint.Project.AssemblyFullName$"
            ClassName="UpdateChoice.UpdateChoice"
            FunctionName="UpdateChoiceValues"
            AppliesTo="all"
            Category="Ak Soultions">
    </Action>
  </WorkflowActions>
</Elements>

Let's discuss the attributes of Action tag.

  • Name: It's the name of your Action. You can choose any meaningful name.
  • SandboxedFunction: In our case, it is true as we are not going to do anything at the farm level. If you need anything in farm level, you can make it false.
  • Assembly: It is the assembly reference of this project. In our case, the value is $SharePoint.Project.AssemblyFullName$.
  • ClassName: This is the name of our code behind class. There is a specific format to provide its name. In the first part, provide your namespace name followed by dot (.), then our class name. Let's say your namespace is XXX and class is YYY. So the format becomes XXX.YYY.
  • FunctionName: The function which will be executed while executing the action. The signature of this function must Hashtable.
  • AppliesTo: We can provide its value as all so that this action becomes available to all sub-sites.
  • Category: You can categorize you custom actions by giving a name. If category name does not exist, then new category will be created. Otherwise, this action will be added into the existing category.

So we find that we need a code behind class with a function. Let's add a class in our project and also a function to our class which returns a Hashtable.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SharePoint;
using Microsoft.SharePoint.UserCode;
using Microsoft.SharePoint.Workflow;
using System.Collections;
using System.Collections.Specialized;
namespace UpdateChoice
{
    public class UpdateChoice
    {
        public Hashtable UpdateChoiceValues()
        {
            var hash = new Hashtable();
            return hash;
        }
    }
}

Now, open our Element XML file again and add RuleDesigner tag inside Action tag. Inside the RuleDesigner tags, we have to bind all our fields which we want to pass as parameters of our action.

C#
<RuleDesigner Sentence="Update Choice Field with the value: %1,
Field Name(internal name): %2, List Name: %3, Site Url: %4.">
       <FieldBind Field="Value" Text="Value"
       Id="1" DesignerType="TextBox" />
       <FieldBind Field="FieldName" Text="Field Name"
       Id="2" DesignerType="TextBox" />
       <FieldBind Field="ListName" Text="List Name"
       Id="3" DesignerType="TextBox" />
       <FieldBind Field="SiteUrl" Text="Site Url"
       Id="4" DesignerType="TextBox" />
</RuleDesigner>

The Sentence attribute of RuleDesigner tag will be shown with our custom action. To implement the above scenario, I have declared four fields.

  • Value: This field will be added to Related Tender column as Choice. According to the above scenario, it should be the value of Tender Number.
  • FieldName: Which column are we going to update. From the designer, we have to provide the internal name of that column. According to the above scenario, it should be Related_x0020_Tender.
  • ListName & SiteUrl: We need to pass the list name here because without this, we will not be able to update the Choice column. SiteUrl is also needed because list may belong in other context. We will see more in our code behind class.

For each field, we have to provide four attributes like Field, Id, Text and DesignerType.

  • Field: Name of the field. It should be unique.
  • Id: Each field must have a unique Id. We can link it in Sentence attribute by giving %[Id Number].
  • Text: This text will be shown in SharePoint Designer UI.
  • DesignerType: How do you want display this field.

Now we have to define our actual parameters. To do that, add Parameters tag inside Action tag.

XML
<Parameters>
        <Parameter Name="__Context" 
        Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, 
        Microsoft.SharePoint.WorkflowActions" 
        Direction="In" DesignerType="Hide" />
        <Parameter Name="Value" Type="System.String, 
        mscorlib" Direction="In" DesignerType="ParameterNames" />
        <Parameter Name="FieldName" Type="System.String, 
        mscorlib" Direction="In" DesignerType="ParameterNames" />
        <Parameter Name="ListName" Type="System.String, 
        mscorlib" Direction="In" DesignerType="ParameterNames" />
        <Parameter Name="SiteUrl" Type="System.String, 
        mscorlib" Direction="In" DesignerType="ParameterNames" />
</Parameters>

Actually in this, you have to specify the Type and Direction of our fields that we are going to pass in this action. Type can be String, Int, Guid and so on. Direction can have two values like In or Out. In means, you just need to pass this value to the action. You do not need to return any value from action. Out is used where you need to return some value from action. The first parameter named __Context is special type parameter. Always, we have to provide it. It will be the current context when this work flow will run. DesignerType will be Hide as we do not want to show this in action.

Now, update our UpdateChoiceValues function.

C#
public Hashtable UpdateChoiceValues(SPUserCodeWorkflowContext context,
string Value, string FieldName, string ListName, string SiteUrl)
       {
           var hash = new Hashtable();
           using (SPSite site = new SPSite(context.CurrentWebUrl))
           {
               using (SPWeb web = site.OpenWeb())
               {
                   try
                   {
                       hash["result"] = UpdateChoiceAction
                       (Value, FieldName, ListName, SiteUrl);
                       SPWorkflow.CreateHistoryEvent(web, context.WorkflowInstanceId, 0,
                       web.CurrentUser, TimeSpan.Zero, "Information", "done" + Value, String.Empty);
                   }
                   catch (Exception ex)
                   {
                       hash["result"] = ex.InnerException.Message;
                       SPWorkflow.CreateHistoryEvent(web, context.WorkflowInstanceId, 0,
                       web.CurrentUser, TimeSpan.Zero, "Error", ex.Message, String.Empty);
                   }
               }
           }
           return hash;
       }

Here, I have put all Parameter tags as the parameter of UpdateChoiceValues function so that we can write our business logic with these parameters. I have written another function named UpdateChoiceAction to implement the above scenario.

C#
private string UpdateChoiceAction(string value, string fieldName, string listName, string siteUrl)
        {
            try
            {
                using (SPSite site = new SPSite(siteUrl))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        web.AllowUnsafeUpdates = true;
                        SPList spList = web.Lists[listName];
                        SPFieldChoice spChoiceField = (SPFieldChoice)spList.Fields[fieldName];
                        spChoiceField.AddChoice(value);
                        spChoiceField.Update();
                        web.AllowUnsafeUpdates = false;
                        return "Field has been updated with new value: " + value;
                    }
                }
            }
            catch (Exception ex)
            {
                return "Error: " + ex.Message;
            }
        }

Now have a look at my whole code.

Element.xml

XML
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <WorkflowActions>
    <Action Name="Update Any Choice Field"
            SandboxedFunction="true"
            Assembly="$SharePoint.Project.AssemblyFullName$"
            ClassName="UpdateChoice.UpdateChoice"
            FunctionName="UpdateChoiceValues"
            AppliesTo="all"
            Category="Ak Soultions">
      <RuleDesigner Sentence="Update Choice Field with the value: %1, 
      Field Name(internal name): %2, List Name: %3, Site Url: %4.">
        <FieldBind Field="Value" Text="Value" 
        Id="1" DesignerType="TextBox" />
        <FieldBind Field="FieldName" Text="Field Name" 
        Id="2" DesignerType="TextBox" />
        <FieldBind Field="ListName" Text="List Name" 
        Id="3" DesignerType="TextBox" />
        <FieldBind Field="SiteUrl" Text="Site Url" 
        Id="4" DesignerType="TextBox" />
      </RuleDesigner>
      <Parameters>
        <Parameter Name="__Context" 
        Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, 
        Microsoft.SharePoint.WorkflowActions" Direction="In" 
        DesignerType="Hide" />
        <Parameter Name="Value" Type="System.String, 
        mscorlib" Direction="In" DesignerType="ParameterNames" />
        <Parameter Name="FieldName" Type="System.String, 
        mscorlib" Direction="In" DesignerType="ParameterNames" />
        <Parameter Name="ListName" Type="System.String, 
        mscorlib" Direction="In" DesignerType="ParameterNames" />
        <Parameter Name="SiteUrl" Type="System.String, 
        mscorlib" Direction="In" DesignerType="ParameterNames" />
      </Parameters>
    </Action>
  </WorkflowActions>
</Elements>

UpdateChoice.cs

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SharePoint;
using Microsoft.SharePoint.UserCode;
using Microsoft.SharePoint.Workflow;
using System.Collections;
using System.Collections.Specialized;
namespace UpdateChoice
{
    public class UpdateChoice
    {
        public Hashtable UpdateChoiceValues(SPUserCodeWorkflowContext context, 
        string Value, string FieldName, string ListName, string SiteUrl)
        {
            var hash = new Hashtable();
            using (SPSite site = new SPSite(context.CurrentWebUrl))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    try
                    {
                        hash["result"] = UpdateChoiceAction
                        (Value, FieldName, ListName, SiteUrl);
                        SPWorkflow.CreateHistoryEvent(web, context.WorkflowInstanceId, 0, 
                        web.CurrentUser, TimeSpan.Zero, "Information", "done" + Value, String.Empty);
                    }
                    catch (Exception ex)
                    {
                        hash["result"] = ex.InnerException.Message;
                        SPWorkflow.CreateHistoryEvent(web, context.WorkflowInstanceId, 0, 
                        web.CurrentUser, TimeSpan.Zero, "Error", ex.Message, String.Empty);
                    }
                }
            }
            return hash;
        }
        private string UpdateChoiceAction(string value, string fieldName, string listName, string siteUrl)
        {
            try
            {
                using (SPSite site = new SPSite(siteUrl))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        web.AllowUnsafeUpdates = true;
                        SPList spList = web.Lists[listName];
                        SPFieldChoice spChoiceField = (SPFieldChoice)spList.Fields[fieldName];
                        spChoiceField.AddChoice(value);
                        spChoiceField.Update();
                        web.AllowUnsafeUpdates = false;
                        return "Field has been updated with new value: " + value;
                    }
                }
            }
            catch (Exception ex)
            {
                return "Error: " + ex.Message;
            }
        }
    }
}

Now, deploy this solution and find this action under Ak Solutions category.

action

PS: In SharePoint 2013 workflow, you will not find this action. So create 2010 workflow for using these kinds of custom action. What if we need in 2013 workflow?? Then the solution is very simple! Create 2010 workflow and run it from 2013 workflow. I mean you have to create two workflows.

Conclusion

You can deploy this in SharePoint Online also as it's a Sandboxed solution. To do that, go to site settings of your top level site. Then click Solutions under Web Designer Galleries. Now upload your .wsp file and activate it. That's all about writing a custom action. You are always welcome to let me know your feedback.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)