How to Create Custom Workflow Action

   —   
In my previous blog post about the new Advanced Workflow module, I gave you a quick overview on how to register a custom workflow action. Today, as promised, I will guide you step by step through the complete process of implementing and registering the custom action. As you will see, it's pretty simple and straightforward.

Intro

The workflow actions are available only for the Advanced Workflow module. They are represented by action steps which are special type of workflow steps.

wf_action.png

When the workflow process moves a document to one of these action steps, an extra code is executed based on type of the action.

Kentico EMS brings several predefined action steps out of the box, e.g. Move document, Copy document, Synchronize document, etc., to integrate the workflow even with other modules. In addition to the wide list of actions, you can easily implement your custom action to integrate your custom code into the workflow process. Let's take a look how...

1. Define the goal

First of all, you need to define the goal you want to achieve. What do you want done when the process reaches your action step?

My goal is to implement support for Parallel approval. This means that the document needs to be approved by multiple managers before it is moved to the next step of the approval process, but I don't care about the order of approval. This behavior isn't supported by the Advanced Workflow module out of the box. Currently, you would need to define one step for each approval and you would lose the independence of approval order.

The first thing is to decide whether the action needs any parameter defined by the user. For my Parallel approval action, I want the user to be able to define the list of managers who need to approve the document before it's approved. This means that I will need one action parameter - the list of managers.

Let's register the action in the system...

2. Register the action

Now if you already know what you want to implement and which parameters you need for your action, you can register the action in the user interface. The list of predefined actions can be found in Site Manager > Development > Workflows section on an Actions tab.

wf_action_list.png

Here you can create the new action by clicking the New action button.

Definition

To define the action you need to provide several properties. The most important properties are the Assembly name and Class name. By specifying the values, you define the assembly and class which implements the action logic. You can also place the code of your custom action in the App_Code folder. In this case, you need to provide the 'App_Code' value in the Assembly name property.

For my action I will use these settings:

wf_action_edit.png

As you can see, the class with code of my action is named ParallelApprovalAction and is placed in the App_Code folder.

Parameters

The next step is to define the action parameters which can be managed on the Parameters tab. Defining the parameters here is similar to defining those of, for example, a web part. You need to provide at least the name, data type, caption and type of control which will be used to edit the parameter value.

I need only one parameter for the action to define a list of managers (users). For this reason I will create a Managers parameter of Text type with a size of 200 characters. I will use Uni selector control as an editing control with a predefined object type that has the value 'cms.user'. As a return column name I will use the UserName column. Also the selection mode will be Multiple since I want to allow selecting multiple managers. 

wf_action_param.png

Using these settings the control will store the selected users in the format: "<username1>;<username2>" (list of user names separated by semicolon).

3. Implement the magic

Now as the action is registered, I need to implement the logic of the action. Here are the main three pillars of the logic:
  • The action will store the list of managers who already approved the document into the document custom data field.
  • Until the list of specified managers (action parameter) is not same as the list of managers who already approved, the action returns the document to the previous step.
  • When the lists match, the action moves the document to the next step.

First, I need to inherit the class from the document action base class:
/// <summary> /// Parallel approval action class /// </summary> public class ParallelApprovalAction : DocumentWorkflowAction {


To get the value of a parameter, you can use this code snippet:
private List<string> mManagers = null; /// <summary> /// Managers who can approve /// </summary> public List<string> Managers { get { if (mManagers == null) { mManagers = GetResolvedParameter<string>("Managers", "").Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList(); } return mManagers; } }


Using this approach, I'll get the list of managers defined through the parameter.

To execute the custom logic of the action, the only thing you need to do is to override the Execute() method of the base class with your code.

The implementation of parallel approval logic looks like this:
public override void Execute() { if (Managers.Count > 0) { // Get list List<string> approved = ValidationHelper.GetString(Node.DocumentCustomData["Managers"], "").Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList(); // Add current manager string userName = User.UserName; if (!approved.Contains(userName)) { approved.Add(userName); } // Store manager Node.DocumentCustomData["Managers"] = approved.Join(";"); using (CMSActionContext ctx = new CMSActionContext()) { ctx.DisableAll(); Node.Update(); } // There are still managers to approve var missingManagers = Managers.Except(approved); if (missingManagers.Count() != 0) { Arguments.StopProcessing = true; using (WorkflowActionContext ctx = new WorkflowActionContext() { CheckStepPermissions = false }) { Arguments.Comment += string.Format(" | Document needs approval also from these manager(s): {0}", missingManagers.Join(", ")); WorkflowManager.MoveToSpecificStep(Node, Arguments.OriginalStep, Arguments.Comment, WorkflowTransitionTypeEnum.Automatic); } } } }


First of all, I get the list of already approved managers. Then I add the current user (who is the current manager) to the list, and store it in the document's data. Finally, I compare the list of defined managers with the list of managers stored in the document. If there is still a set of users who have yet to approve the document, the processing of the workflow is stopped, custom comment is stored, and the document is moved back to the previous step which is the one before the action step.

Please note: One extra step needs to be taken when using the class placed in the App_Code folder - you need to register your custom class. The import package, which can be downloaded at the end of this blog post, already contains the additional code snippet to register the class.

Action in "action"

To better understand the action logic, let's take a look how the action is used in the workflow process. In the figure below, you can see the sample workflow process with one custom approval step and the implemented action step.

wf_dia.png

For this example let's have two managers (users): manager1 and manager2.

Here's the settings of the Managers approval step:

step_properties.png

As you can see, there are additional values in settings to preserve better UI integrity. The defined macro condition disables the approving when the current user is a manager who has already approved the document. The tooltip just provides additional information to the user.

I now need to set up the security settings to allow only these managers to approve the document to the next step:

step_security.png


The detail of the action step with the selected managers can be seen in the screenshot below:

action_properties.png


Finally here's the document UI after the document has been approved by one of the managers:



The Approve button is disabled, providing an explanatory tooltip. There is also the custom comment in the workflow history with additional information about managers who have yet to approve the document. In our example, approval from manager2 would result in the document being published.

What's missing?

Let me warn you that there is room to improve the code in the action logic. To keep the provided code as simple as possible, I haven't covered all special cases.

Also there is still one missing piece in the puzzle. If a manager rejects the document, the list of managers who already approve it is still stored in the document's data and is not cleared. This means our parallel approval logic wouldn't work for the next approval.

This is the focus of my next blog post, in which I would like to show you how to easily customize the WorkflowManager (the manager of all workflow logic) to execute your custom code.

Give it a try!

The import package with the Parallel approval action can be downloaded here. Please feel free to use it as a base for your own implementation.
 
That's all for today. Pretty easy, huh? So why don't you give it a go; create your own workflow action and add it to the market place? ;)
Share this article on   LinkedIn

Jaroslav Kordula

Jaroslav joined Kentico in 2006. He is a Technical Leader in a development team whose main focus is content management. This includes Documents, Custom tables, Media libraries and other related functionality.

Comments

rhutnyk commented on

Jaroslav, my suggestion is based on the issue that there were 'testuser' and 'testuser1' and after 'testuser1' approved the document, macro condition for the workflow step did not allow 'testuser' to approve that document. But we could get the same issue with IDs, when there are 10 and 101. So it doesn't make any difference. Sorry for confusion.

Jaroslav Kordula commented on

Thanks for the suggestion. The main idea why to use user names istead of IDs was to support import/export and staging process. If you use the IDs and synchronize the workflow process definition to different environment, the IDs of users can differ and the IDs are not translated to the corresponding ones. But you can use GUIDs instead of IDs which should work as well.

rhutnyk commented on

Thanks for the great article with detailed explanation.

The only think I'd recommend to change here is to save managers' id instead of managers' username in document custom data field.

Jaroslav Kordula commented on

Hi,

there are several custom workflow actions out of the box for basic scenarios. Unfortunately we cannot cover all possible scenarios, that's why you have an easy way how you can create your own custom action. If you want to create a custom action to create a new document in specified location, this should cover just a few lines of code.

Also what bug exactly do you mean?

Thank you.

Manoj commented on

Hi,

I will make a post on this shortly, what I require is possible using a custom action step in the workflow but feel it should be a standard feature. In basic terms the ability to add say a child document to the current document based on branch logic is what I am after.

The thing that made this slightly difficult is that we had not used applied hot fix 7.05 where there was a bug fix.

Regards
Manoj

Jaroslav Kordula commented on

Hi,

I'm not sure, what exactly are you referenig to. Can you give me more detailed example? What functionality do you miss?

Thank you.

Manoj commented on

Hi,

This is really an informative example and the use of parameters to get details certainly helps us understand how the workflow works.

What I dont understand is why is there no "Add" document functionality on the workflow?

Regards
Manoj