Code actions in The Workflow Designer

Introduction
In version 1.4.3, for integration into the project runtime environment and custom actions execution, the following providers used: IWorkflowRuleProvider and IWorkflowActionProvider. This caused some inconvenience and limited the use of the Designer.
In version 1.4.4, we added the possibility to define actions in The Workflow Designer using C#.
In this article, I will explain:
- How to create user's actions in the Designer
- How to execute user's actions
- How to transmit parameters between Actions
We will base ourselves on the example from the article Alternative to Windows Workflow Foundation.
Code actions
It's new functionality. You can read description here.
Prepare a solution
We need to change the base example from the previous article. Add a document type request to the CreateInstance method of the WorkflowConsole project:
private static void CreateInstance() { processId = Guid.NewGuid(); Console.Write("Please, enter type of document:"); processType = Console.ReadLine(); try { WorkflowApp.WorkflowInit.Runtime.CreateInstance(schemeCode, processId.Value); Console.WriteLine("CreateInstance - OK.", processId); } catch (Exception ex) { Console.WriteLine("CreateInstance - Exception: {0}", ex.Message); processId = null; processType = null; } }
The document type will be stored in the processType variable. The variable is public, so we can get to it from WFE. We add the document type to Console:
//... public static string processType = null; static void Main(string[] args) { //... do { if (processId.HasValue) { Console.WriteLine("ProcessId = '{0}' (Type:{1}). CurrentState: {2}, CurrentActivity: {3}", processId, processType, WorkflowInit.Runtime.GetCurrentStateName(processId.Value), WorkflowInit.Runtime.GetCurrentActivityName(processId.Value)); } Console.Write("Enter code of operation:"); char operation = Console.ReadLine().FirstOrDefault(); //... } }
Create a scheme
Activities:
- draft - starting state
- switchtype - pending state
- state_contract - a state for "Contract" type
- state_invoce - a state for "Invoice" type
- state_unknown - a state for unknown document type
- final - final state
In switchtype state, the text "Hello World!" will be entered to Console.
Next is automatic transfer to one of three states:
- If the processType is equal to "Contract" the state will be "state_contract"
- If "Invoice" then "state-invoice"
- Otherwise, "state_unknown"
In state_contract, state-invoice, and state_unknown states, call a method of e-mail sending.
Let's click the "Code Actions" icon in the toolbar and add Code actions:
- HelloWorld
- CheckType
- SendMail
For "HelloWorld" item click the "Edit code" button; in the window we insert:
Console.WriteLine("Hello world!");
For "CheckType" item click the "Edit code" button; in the window we insert:
var a = WorkflowAppConsole.Program.processType ?? string.Empty; var b = parameter ?? string.Empty; return a.ToLower() == b.ToLower();
For "SendMail" item click the "Edit code" button; in the window we insert:
try { SmtpClient Smtp = new SmtpClient("smtp.mail.com", 25); Smtp.Credentials = new NetworkCredential("login", "pass"); MailMessage Message = new MailMessage(); Message.From = new MailAddress("from@mail.com"); Message.To.Add(new MailAddress("to@mail.com")); Message.Subject = "WorkflowEngine.NET"; Message.Body = "Parameter:" + parameter ?? string.Empty; Smtp.Send(Message); Console.WriteLine("SendMail - OK. Body:" + Message.Body); } catch(Exception ex) { Console.WriteLine("SendMail error:" + ex.Message); }
Ok, will add call the Code actions in activities and transitions.
In "switchtype" activity add "HelloWorld" action in Implemetation block:
For each out-Transition from switchtype activity set Condition block (Type="Action", Action="CheckType") and set Action parameter Contract or Invoice:
In "state_contract", "state_invoice", "state_unknown" activities add "SendMail" action in Implemetation block:
Click on the "Save" button for save the schema to DB.
In the result, you should receive the following scheme: scheme.xml.
Test
Execute the WorkflowAppConsole.
Operation: 0 - CreateInstance 1 - GetAvailableCommands 2 - ExecuteCommand 3 - GetAvailableState 4 - SetState 5 - DeleteProcess 9 - Exit The process isn`t created. Please, enter type of document:Contract CreateInstance - OK. ProcessId = '79067ecd-01e0-4bf1-9b96-6d6a369ea7bc' (Type:Contract). CurrentState: , CurrentActivity: draft Enter code of operation:2 Available commands: - agree (LocalizedName:agree, Classifier:Direct) Enter command:agree Hello world! ExecuteCommand - OK. ProcessId = '79067ecd-01e0-4bf1-9b96-6d6a369ea7bc' (Type:Contract). CurrentState: , CurrentActivity: state_contract Enter code of operation:2 Available commands: - agree (LocalizedName:agree, Classifier:Direct) Enter command:agree SendMail - OK. Body:Parameter:Contract ExecuteCommand - OK. ProcessId = '79067ecd-01e0-4bf1-9b96-6d6a369ea7bc' (Type:Contract). CurrentState: final, CurrentActivity: final Enter code of operation:0 Please, enter type of document:Invoice CreateInstance - OK. ProcessId = 'ce6f472d-5d62-4a32-afde-6bb5c5d3fdae' (Type:Invoice). CurrentState: , CurrentActivity: draft Enter code of operation:2 Available commands: - agree (LocalizedName:agree, Classifier:Direct) Enter command:agree Hello world! ExecuteCommand - OK. ProcessId = 'ce6f472d-5d62-4a32-afde-6bb5c5d3fdae' (Type:Invoice). CurrentState: , CurrentActivity: state_invoce Enter code of operation:2 Available commands: - agree (LocalizedName:agree, Classifier:Direct) Enter command:agree SendMail - OK. Body:Parameter:Invoice ExecuteCommand - OK. ProcessId = 'ce6f472d-5d62-4a32-afde-6bb5c5d3fdae' (Type:Invoice). CurrentState: final, CurrentActivity: final Enter code of operation:0 Please, enter type of document:Other CreateInstance - OK. ProcessId = 'c228863c-1035-4784-8d6b-edd321845a57' (Type:Other). CurrentState: , CurrentActivity: draft Enter code of operation:2 Available commands: - agree (LocalizedName:agree, Classifier:Direct) Enter command:agree Hello world! SendMail - OK. Body:Parameter: ExecuteCommand - OK. ProcessId = 'c228863c-1035-4784-8d6b-edd321845a57' (Type:Other). CurrentState: , CurrentActivity: state_unknown Enter code of operation:2 Available commands: - agree (LocalizedName:agree, Classifier:Direct) Enter command:agree ExecuteCommand - OK. ProcessId = 'c228863c-1035-4784-8d6b-edd321845a57' (Type:Other). CurrentState: final, CurrentActivity: final
Parameters transition between Actions
For each ProcessInstance, the engine stores a set of variables. You can use the Parameters block for parameter transition between CodeActions.
The following parameter types are supported:
- Temporary - the parameter retains its value during the execution of command and is not stored in the database (persistence store)
- Persistence - the parameter always retains its value and is stored in the database (persistence store)
- System - the parameter is part of the workflow engine
If the parameter is used within one Activity, it is necessary to use Temporary type, if the parameter will be used for different activities, Persistence type.
Add persistence parameter "TestParam"
Let's try to use this function. For setting parameters, use the SetParameter function, and to get parameters, GetParameter.
Add the Code action "SetParameterValue" which will record the text value in the "TestParam" parameter.
if (!processInstance.ProcessParameters.Any(c => c.Name == "TestParam")) { Console.WriteLine("TestParam isn`t found!"); return; } processInstance.SetParameter<string>("TestParam", parameter); Console.WriteLine("TestParam set value '{0}'", parameter);
Add the Code action "ShowParameterValue", which will output the parameter value to the "TestParam" console.
if (processInstance.ProcessParameters.Any(c => c.Name == "TestParam")) { var val = processInstance.GetParameter<string>("TestParam"); Console.WriteLine("TestParam: '{0}'", val); } else{ Console.WriteLine("TestParam isn`t found!"); }
Let's create a 4-stage scheme. On the first stage, we will request a parameter, on the second, output it.
In Activity_1 set SetParameter in Implementation block:
In Activity_2 set ShowParameter in Implementation block:
Click on the "Save" button for save the schema to DB.
In the result, you should receive the following scheme: scheme2.xml.
Test
Operation: 0 - CreateInstance 1 - GetAvailableCommands 2 - ExecuteCommand 3 - GetAvailableState 4 - SetState 5 - DeleteProcess 9 - Exit The process isn`t created. Please, enter type of document:Invoice CreateInstance - OK. ProcessId = 'a3473491-37b4-41b9-ba13-517848f6e101' (Type:Invoice). CurrentState: , CurrentActivity: draft Enter code of operation:2 Available commands: - agree (LocalizedName:agree, Classifier:Direct) Enter command:agree TestParam set value 'Hello World' ExecuteCommand - OK. ProcessId = 'a3473491-37b4-41b9-ba13-517848f6e101' (Type:Invoice). CurrentState: , CurrentActivity: Activity_1 Enter code of operation:2 Available commands: - agree (LocalizedName:agree, Classifier:Direct) Enter command:agree TestParam: 'Hello World' ExecuteCommand - OK. ProcessId = 'a3473491-37b4-41b9-ba13-517848f6e101' (Type:Invoice). CurrentState: , CurrentActivity: Activity_2
Security
The use of Code actions can be unsafe. For critically important applications, we recommend using the following providers: IWorkflowActionProvider and IWorkflowRuleProvider.
To switch Code actions off, use the DisableCodeActions method in WorkflowRuntime.
Total
The new functionality broadens WFE use, so now you can create various actions in the designer without recompilation of the application project. This will accelerate design and adjustment.By integrating WFE into your project, you will bring your application level closer to a BPM solutions level.
In version 1.4.4 we implemented only C#. If you need other language support, please write to us.
- By Dmitry Melnikov
- 2/16/2015
- code actions, user action, workflow designer