Skip to main content

WorkflowRuntime Initialization

The second thing you should do, when integrating Workflow Engine into your project, is to initialize the WorkflowRuntime object. Generally, only one WorkflowRuntime object is required so that you can write the thread-safe code for lazy initialization. Here is an example:

using System;
using System.Xml.Linq;
using OptimaJet.Workflow.Core.Builder;
using OptimaJet.Workflow.Core.Bus;
using OptimaJet.Workflow.Core.Runtime;
using OptimaJet.Workflow.DbPersistence;

namespace Business
{
public static class WorkflowInit
{
private static readonly Lazy<WorkflowRuntime> LazyRuntime =
new Lazy<WorkflowRuntime>(InitWorkflowRuntime);

public static WorkflowRuntime Runtime
{
get { return LazyRuntime.Value; }
}

private static WorkflowRuntime InitWorkflowRuntime()
{
// TODO If you have a license key, you have to register it here
//WorkflowRuntime.RegisterLicense("your license key text");

// TODO If you are using database different from SQL Server
// you have to use different persistence provider here.
var connectionString = System.Configuration.ConfigurationManager
.ConnectionStrings["ConnectionString"].ConnectionString;
var dbProvider = new MSSQLProvider(connectionString);

var builder = new WorkflowBuilder<XElement>(
dbProvider,
new OptimaJet.Workflow.Core.Parser.XmlWorkflowParser(),
dbProvider
).WithDefaultCache();

// plugin setup
var plugin = new OptimaJet.Workflow.Core.Plugins.BasicPlugin();

var runtime = new WorkflowRuntime()
.WithBuilder(builder)
.WithPersistenceProvider(dbProvider)
.EnableCodeActions()
.SwitchAutoUpdateSchemeBeforeGetAvailableCommandsOn()
.WithPlugin(plugin) // plugin connect
.AsSingleServer(); // .AsMultiServer();

// events subscription
runtime.ProcessActivityChanged += (sender, args) => { };
runtime.ProcessStatusChanged += (sender, args) => { };
// TODO If you have planned to use Code Actions functionality that required references
// to external assemblies you have to register them here
//runtime.RegisterAssemblyForCodeActions(Assembly.GetAssembly(typeof(SomeTypeFromMyAssembly)));

// starts the WorkflowRuntime
// TODO If you have planned use Timers the best way to start WorkflowRuntime is somewhere outside
// of this function in Global.asax for example
runtime.Start();

return runtime;
}
}
}

Now you call WorkflowRuntime object methods in the following way:

var commands = WorkflowInit.Runtime.GetAvailableCommands(processId, currentUserId);

Here you can stop and simply copy-paste this code into your application, replacing the persistence provider with the relevant one for your database, and it will work.

But if you want to understand the way objects are initialized, keep reading. We will probably inspire you towards non-standard efficient solutions.

Runtime initialization is described in the InitWorkflowRuntime function, let's analyze it line by line.

// TODO If you have a license key, you have to register it here
//WorkflowRuntime.RegisterLicense("your license key text");

Insert your license key into the RegisterLicense method call to remove the restrictions of a Personal license.

var connectionString = System.Configuration.ConfigurationManager
.ConnectionStrings["ConnectionString"].ConnectionString;
var dbProvider = new MSSQLProvider(connectionString);

We receive the database connection string from a configuration file and create a dbProvider object — a mediator between the database and WorkflowRuntime. In this case, it's MS SQL server. Each of persistence provider's classes implements three interfaces, that's why dbProvider is further mentioned three times. Here are the interfaces implemented:

  • IPersistenceProviderWorkflowRuntime uploads and downloads a persisted state from the database through this interface;
  • ISchemePersistenceProvider<XElement>WorkflowRuntime uses it for uploading and downloading process schemes. Specified by XElement type, it indicates the scheme's XML format;
  • IWorkflowGenerator<XElement> — allows for new scheme generation. In case of using a basic persistence provider, the schemes are downloaded from the WorkflowScheme table.
var builder = new WorkflowBuilder<XElement>(
dbProvider,
new OptimaJet.Workflow.Core.Parser.XmlWorkflowParser(),
dbProvider
).WithDefaultCache();

In this part, we create a WorkflowBuilder<XElement> — a critical component that enables scheme upload management, generation, update and parsing as well as process initialization. The object incrementally aggregates the following components:

  • IWorkflowGenerator<XElement> for scheme generation;
  • XmlWorkflowParser for transforming XML schemes into ProcessDefinition objects;
  • IWorkflowGenerator<XElement> (XElement tag indicates XML format).

Then you call the .WithDefaultCache() method that builds cache for parsed process schemes.

Though it seems complicated, in practice you hardly have to change this code. All these interfaces and classes are points of expansion of Workflow Engine's functionality. For example, you may want to store your schemes in another format or to use a powerful mechanism of scheme generation. For example, you can add an activity or a transition to existing schemes. We will describe all these capabilities in the next sections.

We create the WorkflowRuntime object and start configuring it with the following line:

var runtime = new WorkflowRuntime()

Let's assign the WorkflowBuilder object:

.WithBuilder(builder)

... and a persistence provider:

.WithPersistenceProvider(dbProvider)

If you plan to use Action Provider or Rule Provider, uncomment the following code. You can read more about ActionProvider and RuleProvider in respective sections of documentation.

// TODO If you are using Action Provider uncomment following line of code
//.WithActionProvider(new ActionProvider())
// TODO If you are using Rule Provider uncomment following line of code
//.WithRuleProvider(new RuleProvider())

Let's enable the Code Actions functionality — writing code for managing Actions inside the scheme:

.EnableCodeActions()

Thereafter, we turn on automated updates of schemes marked with IsObsolete. The engine applies a lazy update method before it gets the list of available commands:

.SwitchAutoUpdateSchemeBeforeGetAvailableCommandsOn();

Finally, setup you runtime for the single-server or multi-server environment:

  • Use the following call for the single-server environment:

    runtime.AsSingleServer();
  • Use the following call for the multi-server environment:

    runtime.AsMultiServer();

You may need to subscribe for WorkflowRuntime events to implement your business logic; it is better to do it here:

// events subscription
runtime.ProcessActivityChanged += (sender, args) => { };
runtime.ProcessStatusChanged += (sender, args) => { };

If you are planning to use the Code Actions functionality that requires references to external assemblies, you have to register the assemblies in the following way (we'll provide more details on that further on):

// TODO If you have planned to use Code Actions functionality that required references 
// to external assemblies you have to register them here
//runtime.RegisterAssemblyForCodeActions(Assembly.GetAssembly(typeof(SomeTypeFromMyAssembly)));

Launch the runtime:

// starts the WorkflowRuntime
// TODO If you have planned use Timers the best way to start WorkflowRuntime is somewhere
// outside of this function in Global.asax for example
runtime.Start();

The runtime starts and is ready to work, but there is a trick with Timers here. WorkflowRuntime will initialize and start when called for the first time. For example, when it gets a list of commands for a process. If your server has been inactive for a while, the system might have unexecuted and expired Timers. When you call runtime.Start(), the Timers will be executed. To speed up this process, delete runtime.Start() from the WorkflowRuntime InitWorkflowRuntime() function and call it in your app's initialization code.

The next section will cover the process of integrating scheme designer into your application.