Skip to main content

Introducing Formengine - The New Formbuilder, try for FREE formengine.io.

Creating a workflow schema using code

info

This functionality is available in Workflow Engine 13.3.0 and later.

Overview

Usually, process schemas are created using Designer. However, it can also be useful to use the Builder steps feature to change the workflow before launching.

But sometimes, this is not sufficient. For example, you may want to convert a process schema from one format to the Workflow Engine format. In such cases, a mechanism for creating process schemas using code (programmatically) can come to your rescue. The process schema, in terms of Workflow Engine code, is called ProcessDefinition. The main class used to build a schema using code is called ProcessDefinitionBuilder. The ProcessDefinitionBuilder class implements a common pattern known as the Builder pattern, which is familiar to many developers. This pattern allows you to easily create a schema using a fluent style, making it more convenient.

Building a schema using ProcessDefinitionBuilder

The creation of the schema is done in several steps:

  1. Creation of an instance of the process builder, IProcessDefinitionBuilder.
  2. Creation of activities using CreateActivity method.
  3. Creation of transitions using CreateTransition method.
  4. Creation of other workflow elements (see CreateCommand, CreateTimer, CreateActor, etc.).
  5. Creation of XML with a schema.

Creating an extremely simple scheme

Start by creating a builder:

IProcessDefinitionBuilder builder = ProcessDefinitionBuilder.Create("SimpleSchema");

Then, create two activities:

builder.CreateActivity("StartActivity")
.Initial()
.State("StartState")
.EnableSetState()
.SetX(300)
.SetY(100)
.Ref(out ActivityDefinition startActivity);

builder.CreateActivity("FinalActivity")
.Final()
.State("FinalState")
.EnableSetState()
.SetX(600)
.SetY(100)
.Ref(out ActivityDefinition finalActivity);

Next, create a command:

builder.CreateCommand("go")
.Ref(out CommandDefinition goCommand);

Finally, create a transition between the activities:

builder.CreateTransition("InitialTransition", startActivity, finalActivity)
.Direct()
.TriggeredByCommand(goCommand)
.Ref(out TransitionDefinition initialTransition);

Good, the schema is ready. You can now get the XML representation using ProcessDefinition:

string xml = builder.ProcessDefinition.Serialize();

Here is the content of the resulting XML:

SimpleSchema.xml
<Process Name="SimpleSchema" CanBeInlined="false" Tags="" LogEnabled="false">
<Designer/>
<Commands>
<Command Name="go"/>
</Commands>
<Activities>
<Activity Name="StartActivity" State="StartState" IsInitial="true" IsFinal="false" IsForSetState="true" IsAutoSchemeUpdate="false">
<Designer X="300" Y="100" Hidden="false"/>
</Activity>
<Activity Name="FinalActivity" State="FinalState" IsInitial="false" IsFinal="true" IsForSetState="true" IsAutoSchemeUpdate="false">
<Designer X="600" Y="100" Hidden="false"/>
</Activity>
</Activities>
<Transitions>
<Transition Name="InitialTransition" To="FinalActivity" From="StartActivity" Classifier="Direct" AllowConcatenationType="And"
RestrictConcatenationType="And" ConditionsConcatenationType="And" DisableParentStateControl="false">
<Triggers>
<Trigger Type="Command" NameRef="go"/>
</Triggers>
<Conditions>
<Condition Type="Always"/>
</Conditions>
<Designer Hidden="false"/>
</Transition>
</Transitions>
</Process>

A simple process schema generated programmatically

In fact, this is a simple and understandable scheme with two activities and a transition between them. The names of the methods speak for themselves, and you can easily understand what each method does.

For a complete list of methods, refer to the API Reference.

Creating a vacation request process schema

Below is an example of building a schema from a demo using the Builder. Despite the fact that this code is quite long, it is well commented and easy to understand.

IProcessDefinitionBuilder processDefinitionBuilder = ProcessDefinitionBuilder.Create("VacationRequest");

processDefinitionBuilder
// create a parameter "Comment"
.CreateParameter("Comment", typeof(string), ParameterPurpose.Persistence)
.InitialValue("")
.Ref(out ParameterDefinition commentParameter)
// create a command "StartSigning"
.CreateCommand("StartSigning").CreateCommandParameter("Comment", commentParameter).NotRequired()
.Ref(out CommandDefinition startSigningCommand)
// create a command "Approve"
.CreateCommand("Approve").CreateCommandParameter("Comment", commentParameter).NotRequired()
.Ref(out CommandDefinition approveCommand)
// create a command "Reject"
.CreateCommand("Reject").CreateCommandParameter("Comment", commentParameter).NotRequired()
.Ref(out CommandDefinition rejectCommand)
// create a command "Paid"
.CreateCommand("Paid").CreateCommandParameter("Comment", commentParameter).NotRequired().Ref(out CommandDefinition paidCommand)
// create a timer "SendToBigBoss" with an interval of 10 minutes
.CreateTimer("SendToBigBoss").Interval(TimeSpan.FromMinutes(10)).Overridable().Ref(out TimerDefinition timer);

// create actors
processDefinitionBuilder
.CreateActor("BigBoss", "CheckRole")
.Value("Big Boss")
.Ref(out ActorDefinition bigBossActor)
.CreateActor("Accountant", "CheckRole")
.Value("Accountant")
.Ref(out ActorDefinition accountantActor)
.CreateActor("Manager", "Manager")
.Value("Manager")
.Ref(out ActorDefinition managerActor)
.CreateActor("Author", "Author")
.Value("Author")
.Ref(out ActorDefinition authorActor);

// create activities
processDefinitionBuilder
// create activity "VacationRequestCreated"
.CreateActivity("VacationRequestCreated")
.State("VacationRequestCreated")
.Initial()
.EnableSetState()
.EnableAutoSchemeUpdate()
.SetX(320).SetY(220)
.Ref(out ActivityDefinition vacationRequestCreatedActivity)
// create activity "ManagerSigning"
.CreateActivity("ManagerSigning")
.State("ManagerSigning")
.EnableSetState()
.EnableAutoSchemeUpdate()
.SetX(660).SetY(220)
.Ref(out ActivityDefinition managerSigningActivity)
// create activity "BigBossSigning"
.CreateActivity("BigBossSigning")
.State("BigBossSigning")
.EnableSetState()
.EnableAutoSchemeUpdate()
.SetX(1000).SetY(220)
.Ref(out ActivityDefinition bigBossSigningActivity)
// create activity "AccountingReview"
.CreateActivity("AccountingReview")
.State("AccountingReview")
.EnableSetState()
.EnableAutoSchemeUpdate()
.SetX(1000).SetY(380)
.Ref(out ActivityDefinition accountingReviewActivity)
// create activity "RequestApproved"
.CreateActivity("RequestApproved")
.State("RequestApproved")
.Final()
.EnableSetState()
.EnableAutoSchemeUpdate()
.SetX(1280).SetY(380)
.Ref(out ActivityDefinition requestApprovedActivity);

// create transitions
processDefinitionBuilder
// create transition from activity "ManagerSigning" to activity "VacationRequestCreated"
.CreateTransition("ManagerSigning_Draft_1", managerSigningActivity, vacationRequestCreatedActivity)
.Reverse()
.TriggeredByCommand(rejectCommand)
.CreateRestriction(managerActor, RestrictionType.Allow)
.ConcatAllowByAnd()
.ConcatRestrictByAnd()
.Conditional()
.ConcatConditionsByAnd()
.Always()
.SetX(575).SetY(236)
// create transition from activity "BigBossSigning" to activity "AccountingReview"
.CreateTransition("BigBossSigning_Activity_1_1", bigBossSigningActivity, accountingReviewActivity)
.Direct()
.TriggeredByCommand(approveCommand)
.CreateRestriction(bigBossActor, RestrictionType.Allow)
.ConcatAllowByAnd()
.ConcatRestrictByAnd()
.Conditional()
.ConcatConditionsByAnd()
.Always()
// create transition from activity "ManagerSigning" to activity "AccountingReview"
.CreateTransition("ManagerSigning_Approved_1", managerSigningActivity, accountingReviewActivity)
.Direct()
.TriggeredByCommand(approveCommand)
.CreateRestriction(managerActor, RestrictionType.Allow)
.ConcatAllowByAnd()
.ConcatRestrictByAnd()
.Conditional()
.ConcatConditionsByAnd()
.Always()
.SetX(761).SetY(334)
// create transition from activity "ManagerSigning" to activity "BigBossSigning"
.CreateTransition("ManagerSigning_BigBossSigning_1", managerSigningActivity, bigBossSigningActivity)
.Direct()
.TriggeredByCommand(approveCommand)
.CreateRestriction(managerActor, RestrictionType.Allow)
.ConcatAllowByAnd()
.ConcatRestrictByAnd()
.Conditional()
.CreateExpression("@sum > 100")
.ConcatConditionsByAnd()
.SetX(905).SetY(238)
// create transition from activity "VacationRequestCreated" to activity "ManagerSigning"
.CreateTransition("Draft_ManagerSigning_1", vacationRequestCreatedActivity, managerSigningActivity)
.Direct()
.TriggeredByCommand(startSigningCommand)
.CreateRestriction(authorActor, RestrictionType.Allow)
.ConcatAllowByAnd()
.ConcatRestrictByAnd()
.Conditional()
.ConcatConditionsByAnd()
.Always()
.SetX(572).SetY(268)
// create transition from activity "BigBossSigning" to activity "ManagerSigning"
.CreateTransition("BigBossSigning_ManagerSigning_1", bigBossSigningActivity, managerSigningActivity)
.Reverse()
.TriggeredByCommand(rejectCommand)
.CreateRestriction(bigBossActor, RestrictionType.Allow)
.ConcatAllowByAnd()
.ConcatRestrictByAnd()
.Conditional()
.ConcatConditionsByAnd()
.Always()
.SetX(902).SetY(268)
// create transition from activity "ManagerSigning" to activity "BigBossSigning"
.CreateTransition("ManagerSigning_BigBossSigning_2", managerSigningActivity, bigBossSigningActivity)
.DirectionNotSpecified()
.TriggeredByTimer(timer)
.Conditional()
.ConcatConditionsByAnd()
.Always()
.SetX(903).SetY(122)
// create transition from activity "AccountingReview" to activity "RequestApproved"
.CreateTransition("Accountant_Activity_1_1", accountingReviewActivity, requestApprovedActivity)
.Direct()
.TriggeredByCommand(paidCommand)
.CreateRestriction(accountantActor, RestrictionType.Allow)
.ConcatAllowByAnd()
.ConcatRestrictByAnd()
.Conditional()
.ConcatConditionsByAnd()
.Always()
// create transition from activity "AccountingReview" to activity "ManagerSigning"
.CreateTransition("Accountant_ManagerSigning_1", accountingReviewActivity, managerSigningActivity)
.Reverse()
.TriggeredByCommand(rejectCommand)
.CreateRestriction(accountantActor, RestrictionType.Allow)
.ConcatAllowByAnd()
.ConcatRestrictByAnd()
.Conditional()
.ConcatConditionsByAnd()
.Always()
.SetX(702).SetY(420);

string xml = processDefinitionBuilder.ProcessDefinition.Serialize();

And here is the resulting XML:

VacationRequest.xml
<Process Name="VacationRequest" CanBeInlined="false" Tags="" LogEnabled="false">
<Designer/>
<Actors>
<Actor Name="BigBoss" Rule="CheckRole" Value="Big Boss"/>
<Actor Name="Accountant" Rule="CheckRole" Value="Accountant"/>
<Actor Name="Manager" Rule="Manager" Value="Manager"/>
<Actor Name="Author" Rule="Author" Value="Author"/>
</Actors>
<Parameters>
<Parameter Name="Comment" Type="String" Purpose="Persistence"/>
</Parameters>
<Commands>
<Command Name="StartSigning">
<InputParameters>
<ParameterRef Name="Comment" IsRequired="false" NameRef="Comment"/>
</InputParameters>
</Command>
<Command Name="Approve">
<InputParameters>
<ParameterRef Name="Comment" IsRequired="false" NameRef="Comment"/>
</InputParameters>
</Command>
<Command Name="Reject">
<InputParameters>
<ParameterRef Name="Comment" IsRequired="false" NameRef="Comment"/>
</InputParameters>
</Command>
<Command Name="Paid">
<InputParameters>
<ParameterRef Name="Comment" IsRequired="false" NameRef="Comment"/>
</InputParameters>
</Command>
</Commands>
<Timers>
<Timer Name="SendToBigBoss" Type="Interval" Value="10m" NotOverrideIfExists="false"/>
</Timers>
<Activities>
<Activity Name="VacationRequestCreated" State="VacationRequestCreated" IsInitial="true" IsFinal="false" IsForSetState="true"
IsAutoSchemeUpdate="true">
<Designer X="320" Y="220" Hidden="false"/>
</Activity>
<Activity Name="ManagerSigning" State="ManagerSigning" IsInitial="false" IsFinal="false" IsForSetState="true"
IsAutoSchemeUpdate="true">
<Designer X="660" Y="220" Hidden="false"/>
</Activity>
<Activity Name="BigBossSigning" State="BigBossSigning" IsInitial="false" IsFinal="false" IsForSetState="true"
IsAutoSchemeUpdate="true">
<Designer X="1000" Y="220" Hidden="false"/>
</Activity>
<Activity Name="AccountingReview" State="AccountingReview" IsInitial="false" IsFinal="false" IsForSetState="true"
IsAutoSchemeUpdate="true">
<Designer X="1000" Y="380" Hidden="false"/>
</Activity>
<Activity Name="RequestApproved" State="RequestApproved" IsInitial="false" IsFinal="true" IsForSetState="true"
IsAutoSchemeUpdate="true">
<Designer X="1280" Y="380" Hidden="false"/>
</Activity>
</Activities>
<Transitions>
<Transition Name="ManagerSigning_Draft_1" To="VacationRequestCreated" From="ManagerSigning" Classifier="Reverse"
AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And"
DisableParentStateControl="false">
<Restrictions>
<Restriction Type="Allow" NameRef="Manager"/>
</Restrictions>
<Triggers>
<Trigger Type="Command" NameRef="Reject"/>
</Triggers>
<Conditions>
<Condition Type="Always"/>
</Conditions>
<Designer X="575" Y="236" Hidden="false"/>
</Transition>
<Transition Name="BigBossSigning_Activity_1_1" To="AccountingReview" From="BigBossSigning" Classifier="Direct"
AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And"
DisableParentStateControl="false">
<Restrictions>
<Restriction Type="Allow" NameRef="BigBoss"/>
</Restrictions>
<Triggers>
<Trigger Type="Command" NameRef="Approve"/>
</Triggers>
<Conditions>
<Condition Type="Always"/>
</Conditions>
<Designer Hidden="false"/>
</Transition>
<Transition Name="ManagerSigning_Approved_1" To="AccountingReview" From="ManagerSigning" Classifier="Direct"
AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And"
DisableParentStateControl="false">
<Restrictions>
<Restriction Type="Allow" NameRef="Manager"/>
</Restrictions>
<Triggers>
<Trigger Type="Command" NameRef="Approve"/>
</Triggers>
<Conditions>
<Condition Type="Always"/>
</Conditions>
<Designer X="761" Y="334" Hidden="false"/>
</Transition>
<Transition Name="ManagerSigning_BigBossSigning_1" To="BigBossSigning" From="ManagerSigning" Classifier="Direct"
AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And"
DisableParentStateControl="false">
<Restrictions>
<Restriction Type="Allow" NameRef="Manager"/>
</Restrictions>
<Triggers>
<Trigger Type="Command" NameRef="Approve"/>
</Triggers>
<Conditions>
<Condition Type="Expression" ConditionInversion="false">
<Expression><![CDATA[@sum > 100]]></Expression>
</Condition>
</Conditions>
<Designer X="905" Y="238" Hidden="false"/>
</Transition>
<Transition Name="Draft_ManagerSigning_1" To="ManagerSigning" From="VacationRequestCreated" Classifier="Direct"
AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And"
DisableParentStateControl="false">
<Restrictions>
<Restriction Type="Allow" NameRef="Author"/>
</Restrictions>
<Triggers>
<Trigger Type="Command" NameRef="StartSigning"/>
</Triggers>
<Conditions>
<Condition Type="Always"/>
</Conditions>
<Designer X="572" Y="268" Hidden="false"/>
</Transition>
<Transition Name="BigBossSigning_ManagerSigning_1" To="ManagerSigning" From="BigBossSigning" Classifier="Reverse"
AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And"
DisableParentStateControl="false">
<Restrictions>
<Restriction Type="Allow" NameRef="BigBoss"/>
</Restrictions>
<Triggers>
<Trigger Type="Command" NameRef="Reject"/>
</Triggers>
<Conditions>
<Condition Type="Always"/>
</Conditions>
<Designer X="902" Y="268" Hidden="false"/>
</Transition>
<Transition Name="ManagerSigning_BigBossSigning_2" To="BigBossSigning" From="ManagerSigning" Classifier="NotSpecified"
AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And"
DisableParentStateControl="false">
<Triggers>
<Trigger Type="Timer" NameRef="SendToBigBoss"/>
</Triggers>
<Conditions>
<Condition Type="Always"/>
</Conditions>
<Designer X="903" Y="122" Hidden="false"/>
</Transition>
<Transition Name="Accountant_Activity_1_1" To="RequestApproved" From="AccountingReview" Classifier="Direct"
AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And"
DisableParentStateControl="false">
<Restrictions>
<Restriction Type="Allow" NameRef="Accountant"/>
</Restrictions>
<Triggers>
<Trigger Type="Command" NameRef="Paid"/>
</Triggers>
<Conditions>
<Condition Type="Always"/>
</Conditions>
<Designer Hidden="false"/>
</Transition>
<Transition Name="Accountant_ManagerSigning_1" To="ManagerSigning" From="AccountingReview" Classifier="Reverse"
AllowConcatenationType="And" RestrictConcatenationType="And" ConditionsConcatenationType="And"
DisableParentStateControl="false">
<Restrictions>
<Restriction Type="Allow" NameRef="Accountant"/>
</Restrictions>
<Triggers>
<Trigger Type="Command" NameRef="Reject"/>
</Triggers>
<Conditions>
<Condition Type="Always"/>
</Conditions>
<Designer X="702" Y="420" Hidden="false"/>
</Transition>
</Transitions>
</Process>

Well, the picture is for clarity:

The schema of the vacation request process, generated programmatically

Conclusion

In this guide, we have looked at how to create a workflow schema using code (programmatically). The API allows you to make any process schemas, and is in no way inferior to the capabilities of a visual Designer. If necessary, find the right method to create a schema element - use the documentation from the API Reference.

You can find more information in this article.