Alternative to Windows Workflow Foundation

Alternative to Windows Workflow Foundation

Alternative to Windows Workflow Foundation

Introduction

The .NET Framework includes workflow management technology - Windows Workflow Foundation (WWF). WWF is not the only development; there are others too.

This article is about the Workflow Engine.NET.

Reading this article, you'll learn how to:

  • add a workflow to your project;
  • create a workflow scheme using the designer;
  • manage workflow processes.

The Workflow Engine .NET can be used for free if you observe the restrictions on the number of circuit elements (Activity: 15; Transition: 25; Command: 5).

Connecting a designer to the project

In Visual Studio, create an ASP.NET MVC project

Сreate an ASP.NET Web Application

MVC project

Using NuGet add packages:

Open NuGet Package Manager Console

NuGet Package Manager Console

To install WorkflowEngine.NET Designer for ASP.NET MVC, run the following command in the Package Manager Console:

PM> Install-Package WorkflowEngine.NET-Designer

To install WorkflowEngine.NET Provider for MSSQL, run the following command in the Package Manager Console:

PM> Install-Package WorkflowEngine.NET-ProviderForMSSQL

Now you need to prepare the database

In our example, we use MS SQL. Providers for RavenDB and MongoDB are also available.

Create a new database or use the existing one. The package WorkflowEngine.NET-ProviderForMSSQL creates an SQL folder in the project. From this folder, you must run a CreatePersistenceObjects.sql script in the target database. This script is created by a set of tables.

Tables

Now set up the designer

The package WorkflowEngine.NET-Designer has added Controllers/DesignerController.cs and Views\Designer\Index.cshtml to the project.

Add initialization WorkflowRuntime to DesignerController.cs.

Replace

private WorkflowRuntime getRuntime
{
    get
    {
        //INIT YOUR RUNTIME HERE
        return null;
    }
}

with

private static volatile WorkflowRuntime _runtime;
private static readonly object _sync = new object();

private WorkflowRuntime getRuntime
{
    get
    {
        if (_runtime == null)
        {
            lock (_sync)
            {
                if (_runtime == null)
                {
                    var connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
                    var builder = new WorkflowBuilder<XElement>(
                        new OptimaJet.Workflow.DbPersistence.DbXmlWorkflowGenerator(connectionString),
                        new OptimaJet.Workflow.Core.Parser.XmlWorkflowParser(),
                        new OptimaJet.Workflow.DbPersistence.DbSchemePersistenceProvider(connectionString)
                        ).WithDefaultCache();

                    _runtime = new WorkflowRuntime(new Guid("{8D38DB8F-F3D5-4F26-A989-4FDD40F32D9D}"))
                        .WithBuilder(builder)
                        .WithPersistenceProvider(new OptimaJet.Workflow.DbPersistence.DbPersistenceProvider(connectionString))
                        .WithTimerManager(new TimerManager())
                        .WithBus(new NullBus())
                        .SwitchAutoUpdateSchemeBeforeGetAvailableCommandsOn()
                        .Start();
                }
            }
        }

        return _runtime;
    }
}

In the variable connectionString, the connection string to the database, which we created earlier.
Connection String Format:

Data Source=(local);Initial Catalog=WorkflowApp;Integrated Security=False;User ID=sa;Password=1

In the Views\Designer\Index.cshtml file, check the access path to the javascript files:

  • Kinetic
  • Jquery
  • JqueryUI

File names in the project may be different from those set from NuGet (!). It is also necessary to check the sequence of initialization of Jquery and JqueryUI. In the _Layout.cshtml file, perform re-initialization of the library.

If you have done everything correctly, you can run the project and open the URL with the Workflow Designer page: http://localhost:2163/Designer/Index (the port may be different).

WorkflowEngine.NET Designer Empty

Create a scheme of workflow

A scheme of workflow consists of a set of statuses (Activities) and relationships between them (Transitions).

Let's create a simple scheme of:

  • 4 stages: Draft, State1, State2, Final
  • 3 command: StartToRoute, Agree, Reject

So, let's begin.

  1. To create a command in the toolbar, click on the Commands icon. In the window that opens, add the commands: StartToRoute, Approve, Reject.

    Form Command

  2. Let's create the initial status. To do this, click on "Create activity" in the toolbar. A rectangle appears in the designer workspace; this is an Activity. When you double-click on the rectangle, the Activity properties editing form will appear. In the Name field, specify the values of Draft, State (Draft), Initial (check).

    Activity rect

    1. Name of current activity
    2. State of current activity
    3. Deleting this activity
    4. Create new Activity and Transition (From: current activity; To: new activity)
    5. Create new Transition (From: current activity)

    Form Activity

  3. Add the next Activity. To do this, click on "Create activity and transition" in the Activity. In a new Activity, indicate the field values: Name (State1), State = State1. To edit Transition, double-click on the "AA" circle. In the window that opens, specify Classifier (Direct), Trigger Type (Command), Trigger Command (Start).

    Transition item

    1. Touch points. Pulling these elements can be associated with other selected Activity.
    2. Active point. Indicates the type of transition. When you double-client opens a form of editing.
    3. Deleting the transition

    Form Transition

  4. Populate the following Activities and Transitions in the same way. For negative commands in the Classifier property, specify Reverse.
  5. In the final Activity, set a Final flag.
  6. Click on the "Save" button for save the schema to DB.

Scheme of workflow

You can download a ready-made XML file with the scheme: scheme.txt.

Simple operation of workflow

The basic operations include:

  • Create the process instance
  • Getting the list of available commands
  • Execution of the command
  • Getting the list of available states to set
  • Set State
  • Delete the process instance

Let's create a console project WorkflowAppConsole. We need it to demonstrate basic operations.

Add the following packages to the project:

To work with the engine we need to initialize WorkflowRuntime.
Add the WorkflowInit.cs:

using OptimaJet.Workflow.Core.Builder;
using OptimaJet.Workflow.Core.Bus;
using OptimaJet.Workflow.Core.Model;
using OptimaJet.Workflow.Core.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace WorkflowApp
{
    public class WorkflowInit
    {
        private static volatile WorkflowRuntime _runtime;
        private static readonly object _sync = new object();

        public static WorkflowRuntime Runtime
        {
            get
            {
                if (_runtime == null)
                {
                    lock (_sync)
                    {
                        if (_runtime == null)
                        {
                            //TODO CONNECTION STRING TO DATABASE
                            var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
                            var builder = new WorkflowBuilder<XElement>(
new OptimaJet.Workflow.DbPersistence.DbXmlWorkflowGenerator(connectionString),
                                new OptimaJet.Workflow.Core.Parser.XmlWorkflowParser(),
                                new OptimaJet.Workflow.DbPersistence.DbSchemePersistenceProvider(connectionString)
                                ).WithDefaultCache();

                            _runtime = new WorkflowRuntime(new Guid("{8D38DB8F-F3D5-4F26-A989-4FDD40F32D9D}"))
                                .WithBuilder(builder)                               
                                .WithPersistenceProvider(new OptimaJet.Workflow.DbPersistence.DbPersistenceProvider(connectionString))
                                .WithTimerManager(new TimerManager())
                                .WithBus(new NullBus())
                                .SwitchAutoUpdateSchemeBeforeGetAvailableCommandsOn()
                                .Start();
                        }
                    }
                }

                return _runtime;
            }
        }
    }
}

The console application denatures the work with the basic operations and displays the status of the process onto the console.
The variable processId stores the ID of the current process. The variable schemeCode stores the scheme code.
In the Main function, we suggest the user introduce the type of operation to be performed, then the appropriate handlers to be called.

static string schemeCode = "SimpleWF";
static Guid? processId = null;
static void Main(string[] args)
{
    Console.WriteLine("Operation:");
    Console.WriteLine("0 - CreateInstance");
    Console.WriteLine("1 - GetAvailableCommands");
    Console.WriteLine("2 - ExecuteCommand");
    Console.WriteLine("3 - GetAvailableState");
    Console.WriteLine("4 - SetState");
    Console.WriteLine("5 - DeleteProcess");
    Console.WriteLine("9 - Exit");

    Console.WriteLine("The process isn't created.");
    CreateInstance();
            
    do
    {
        if (processId.HasValue)
        {
            Console.WriteLine("ProcessId = '{0}'. CurrentState: {1}, CurrentActivity: {2}",
                processId,
                WorkflowInit.Runtime.GetCurrentStateName(processId.Value),
                WorkflowInit.Runtime.GetCurrentActivityName(processId.Value));
        }
                
        Console.Write("Enter code of operation:");
        char operation = Console.ReadLine().FirstOrDefault();
                
        switch(operation)
        {
            case '0':
                CreateInstance();
                break;
            case '1':
                GetAvailableCommands();
                break;
            case '2':
                ExecuteCommand();
                break;
            case '3':
                GetAvailableState();
                break;
            case '4':
                SetState();
                break;
            case '5':
                DeleteProcess();
                break;
            case '9':
                return;
            default:
                Console.WriteLine("Unknown code. Please, repeat.");
                break;
        }

        Console.WriteLine();
    } while (true);
}

Create the process instance:

private static void CreateInstance()
{
    processId = Guid.NewGuid();
    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;
    }
}

Getting the list of available commands:

private static void GetAvailableCommands()
{
    if(processId == null)
    {
        Console.WriteLine("The process isn't created. Please, create process instance.");
        return;
    }

    var commands = WorkflowInit.Runtime.GetAvailableCommands(processId.Value, string.Empty);
            
    Console.WriteLine("Available commands:");
    if (commands.Count() == 0)
    {
        Console.WriteLine("Not found!");
    }
    else
    {
        foreach (var command in commands)
        {
            Console.WriteLine("- {0} (LocalizedName:{1}, Classifier:{2})", command.CommandName, command.LocalizedName, command.Classifier);
        }
    }
}

Execution of the command:

private static void ExecuteCommand()
{
    if (processId == null)
    {
        Console.WriteLine("The process isn't created. Please, create process instance.");
        return;
    }

    WorkflowCommand command = null;

    do
    {
        GetAvailableCommands();
        Console.Write("Enter command:");
        var commandName = Console.ReadLine().ToLower().Trim();
        if (commandName == string.Empty)
            return;

        command = WorkflowInit.Runtime.GetAvailableCommands(processId.Value, string.Empty)
            .Where(c => c.CommandName.Trim().ToLower() == commandName).FirstOrDefault();
        if (command == null)
            Console.WriteLine("The command isn't found.");
    } while (command == null);

    WorkflowInit.Runtime.ExecuteCommand(processId.Value, string.Empty, string.Empty, command);
    Console.WriteLine("ExecuteCommand - OK.", processId);
}

Getting the list of available states to set:

private static void GetAvailableState()
{
    if (processId == null)
    {
        Console.WriteLine("The process isn't created. Please, create process instance.");
        return;
    }

    var states = WorkflowInit.Runtime.GetAvailableStateToSet(processId.Value, Thread.CurrentThread.CurrentCulture);
    Console.WriteLine("Available state to set:");

    if (states.Count() == 0)
    {
        Console.WriteLine("Not found!");
    }
    else
    {
        foreach (var state in states)
        {
            Console.WriteLine("- {0}", state.Name);
        }
    }
}

Set State:

private static void SetState()
{
    if (processId == null)
    {
        Console.WriteLine("The process isn't created. Please, create process instance.");
        return;
    }

    string stateName = string.Empty;
    WorkflowState state; 
    do
    {
        GetAvailableState();
        Console.Write("Enter state:");
        stateName = Console.ReadLine().ToLower().Trim();
        if (stateName == string.Empty)
            return;

        state = WorkflowInit.Runtime.GetAvailableStateToSet(processId.Value, Thread.CurrentThread.CurrentCulture)
            .Where(c => c.Name.Trim().ToLower() == stateName).FirstOrDefault();
        if (state == null)
            Console.WriteLine("The state isn't found.");
        else
            break;
    } while (true);

    if (state != null)
    {
        WorkflowInit.Runtime.SetState(processId.Value, string.Empty, string.Empty, state.Name, new Dictionary<string, object>());
        Console.WriteLine("SetState - OK.", processId);
    }
}

Delete the process instance:

private static void DeleteProcess()
{
    if (processId == null)
    {
        Console.WriteLine("The process isn't created. Please, create process instance.");
        return;
    }

    WorkflowInit.Runtime.PersistenceProvider.DeleteProcess(processId.Value);
    Console.WriteLine("DeleteProcess - OK.", processId);
    processId = null;
}

  • By Dmitry Melnikov
  • 7/31/2015
  • howtoconnect, wfe, wf, alternative wf