Conditional Transitions. Conditions and Expressions
General information
Workflow processes often use conditional transitions; in this section, we discuss Conditional Transitions in the Workflow Engine. This guide is also 100% suitable for Workflow Server.
In the process scheme, the transitions are specified with at least one condition per each transition, which determines whether this transition can be executed.
A condition can have one of the following types:
- Always - unconditional transition, to be always executed.
- Action - an Action of the Condition type (hereinafter, Action-Condition); it is a function that returns
true
orfalse
, called when the Workflow Engine determines whether to make a transition or not. In other words, this is the 'if' expression. - Expression - a simple expression that returns
true
orfalse
. Using special syntax, you can directly apply the values of the process parameters in it. In other words, this is the 'if' expression. - Otherwise - executed only if other transitions cannot be completed, that is, if Always or their conditions have returned
false
. In other words, this is the 'else' expression.
A transition can have exactly one condition of the Always type or the Otherwise type; and, any number of conditions of the Action -Condition type or the Expression type.
- A transition can be specified by any number of Action-Condition or Expression conditions.
- The result returned by Action-Condition or Expression can be inverted.
- PreExec. Result is the value used in the PreExecution mode only.
- By default, all conditions are combined by the AND operator; but, you can change it to OR.
During the runtime, when selecting transitions towards the next state, the Workflow Engine uses the following priorities.
- If Always exists, it is executed in any case.
- Each of Action-Condition and Expression are executed in turn. If they return
true
, the transition is performed. - Otherwise is executed only if no transitions have been performed at the previous two stages.
Now, we consider all the ways to create an Action-Condition or an Expression.
Expressions
An expression returns true
or false
. Its main advantage is that you can use the process parameters in it. Any
reference to a process parameter begins with the '@' character. An expression to substitute a parameter can be as follows:
@ParameterName
- the easiest option, the value of the parameter named 'parameterName' is substituted from the process. The type is the same as the type the parameter value.@ObjectParameter.ObjectProperty.StringProperty
- partial parameters can be used in the expression. That is, if you have a complex, multi-level object stored in the parameter, you can substitute a part of this object.@ParameterName:formatString
- the expression with a format string. A string formatted according to theformatString
template is substituted using the format syntax for the .NET platform. Please, for more details see here.@(ParameterName:formatString)
- if different special characters and delimiters are used in the format string, you must additionally enclose the expression in round brackets.@(ParameterName).Contains("Substring")
- by enclosing the expression in round brackets, you can use the methods of the parameter object. In this example, theContains
method of the string is used.
In addition to substitutions, you can use the following signs in expressions:
- operators
and,AND,And,&,&&
- AND condition. - operators
or,OR,Or,|,||
- OR condition. - operators
=,==
- equality. - operators
<>,!=
- inequality. - operators
not,NOT,Not,!
- negation. - operators
>,<,>=,<=
- comparison. - round brackets.
- operators of addition, subtraction, multiplication, division, etc.
- any other elements of lambda expressions in C#, see here for more details.
Examples of expressions
@Amount > 100
- the process parameter named 'Amount' is used in the expression.@Document.Amount > 100 AND @Document.CreationDate <= DateTime.Now.AddDays(-1)
- you see that some C# types and functions can be used. The process parameter called 'Document', containing an object with the 'Amount' and 'CreationDate' properties, is used in the expression.NOT @Document.IsSigned
- The process parameter called 'Document', containing an object with the boolean property 'IsSigned', is used in the expression.@(Document.CreationDate:yyyy) = "2020"
- an example with a format string; a format string turns the parameter into a string, so you need to compare it with a string too.
Let us consider how Workflow Engine interprets expressions to clearly understand the subject:
The following expression:
@Document.Amount > 100 AND @Document.CreationDate <= DateTime.Now.AddDays(-1)
It turns into this asynchronous lambda:
async (processInstance) => (await processInstance.GetParameterAsync<dynamic> ("Document.Amount")) > 100 && (await processInstance.GetParameterAsync<dynamic> ("Document.CreationDate")) <= DateTime.Now.AddDays(-1);
The resulting asynchronous lambda is compiled once, the first time you access the process scheme. Then, the already cached compiled function is used, therefore, expressions do not affect the performance.
You can use the designer to test if your expression can be compiled.
After clicking on 'Validate', the compilation result window will appear.
How to create Action-Conditions
Option One, Using IWorkflowActionProvider
How to work with Workflow Action Provider, please, read in detail in this guide. First of all, we should create a class,
implementing IWorkflowActionProvider
.
public interface IWorkflowActionProvider
{
void ExecuteAction(string name, ProcessInstance processInstance, WorkflowRuntime runtime,
string actionParameter);
Task ExecuteActionAsync(string name, ProcessInstance processInstance, WorkflowRuntime runtime,
string actionParameter, CancellationToken token);
bool ExecuteCondition(string name, ProcessInstance processInstance, WorkflowRuntime runtime,
string actionParameter);
Task<bool> ExecuteConditionAsync(string name, ProcessInstance processInstance, WorkflowRuntime runtime,
string actionParameter, CancellationToken token);
bool IsActionAsync(string name, string schemeCode);
bool IsConditionAsync(string name, string schemeCode);
List<string> GetActions(string schemeCode);
List<string> GetConditions(string schemeCode);
}
The ExecuteAction
, ExecuteActionAsync
, IsActionAsync
and GetActions
methods are used for Actions; they are not
of interest now. Let us consider the rest of the methods.
List<string> GetConditions(string schemeCode)
- must return the list of Action-Condition names, used in this implementation ofIWorkflowActionProvider
. This is the list shown in the Designer. Please, note thatschemeCode
is passed to these methods, thus, the set of Action-Conditions can differ for each of the schemes.bool IsConditionAsync(string name, string schemeCode)
- if the method returnstrue
, the Action-Condition is executed using the asynchronous methodExecuteConditionAsync
, otherwise, the synchronous methodExecuteCondition
is used.Task<bool> ExecuteConditionAsync(string name, ProcessInstance processInstance, WorkflowRuntime runtime, string actionParameter, CancellationToken token)
- an asynchronous Action-Condition.bool ExecuteCondition(string name, ProcessInstance processInstance, WorkflowRuntime runtime, string actionParameter)
- a synchronous * Action*-Condition.
Both of the Action-Condition methods, synchronous and asynchronous, have the following common parameters:
name
- the name of Action-Condition, specified and used in the process scheme.processInstance
- contains the whole information about the process, including the process parameters.runtime
- the instance of Workflow Runtime executing the given process.actionParameter
- the additional string parameter, set in the scheme designer; can also contain JSON; the setting form of this parameter can be customized.
After creating one or more classes with our Action-Conditions, we should connect them to Workflow Runtime on its initialization.
var runtime = new WorkflowRuntime()
...
runtime.WithActionProvider(new YourConditionProvider())
.AsSingleServer(); //.AsMultiServer();
You can connect any number of Action Providers to Workflow Runtime; moreover, in the WithActionProvider
method, you can specify the list
of the schemes codes to limit your provider with these schemes.
workflowRuntime.WithActionProvider(new YourConditionProvider(), new List<string>() {"SchemeCode1", "SchemeCode2"});
Option Two, Using Code Actions
Code Actions are created in the process scheme, namely, you write the code directly in the Designer. This approach is described in
detail here. All you need to do is select the type equal to Condition, check the Async checkbox if your *
Action*-Condition is asynchronous, and edit its code. To Action-Conditions created in the scheme, the same parameters are passed as to
those created in the provider. That is, processInstance
, runtime
and actionParameter
are available to you.