Click here to Skip to main content
Click here to Skip to main content

Eligible Contests

Add your own
alternative version

Tagged as

Stats

298.7K views
5.3K downloads
417 bookmarked
Posted 5 Jan 2017
Licenced

Wexflow: Open source workflow engine in C#

, 16 Nov 2018
  Poor Excellent
Add a reason or comment to your vote: x
A high performance, extensible, modular and cross-platform workflow engine.

Table of contents

  1. Features
  2. Examples
  3. Benefits
  4. How to install Wexflow?
  5. How to uninstall Wexflow?
  6. How to use Wexflow?
    1. General
    2. Wexflow Manager
    3. Wexflow Back Office
    4. Wexflow Android Manager
  7. Workflow samples
    1. Sequential workflows
    2. Execution graph
    3. Flowchart workflows
    4. Workflow events
  8. How to create a custom task?
  9. How to debug Wexflow?
  10. Libraries used by Wexflow
  11. History

Wexflow is a high performance, extensible, modular and cross-platform workflow engine. The goal of Wexflow is to automate recurring tasks without user intervention. With the help of Wexflow, building automation and workflow processes become easy. Wexflow also helps in making the long-running processes straightforward.

Wexflow aims to make automations, workflow processes, long-running processes and interactions between systems, applications and folks easy, straightforward and clean. The communication between systems or applications becomes easy through this powerful workflow engine.

Wexflow makes use of Quartz.NET open source job scheduling system that is used in large scale entreprise systems. Thus, Wexflow offers felixibility in planning workflow jobs such as cron workflows.

Furthermore, Wexflow makes use of LiteDB NoSQL Document Store database in its server and back office which enhance and improve the performance of this workflow engine.

Features

Wexflow provides the following features:

Wexflow comes with a back office too, so you can search and filter among all your workflows, have real-time statistics on your workflows, manage your workflows with ease, design your workflows with ease, and track your workflows with ease:

Just to give you an idea of what Wexflow does, this is a screenshot from the "Designer" page. Using the "Designer" page, we get a nice visual overview of the dependency graph of the workflow. Each node represents a task which has to be run:

  

Discover more about the features in details.

Examples

Here are some examples of using Wexflow:

  • Batch recording live video feeds.
  • Batch transcoding audio and video files.
  • Batch uploading videos and their metadata to YouTube SFTP dropbox.
  • Batch encrypting and decrypting large files.
  • Batch converting, resizing and cropping images.
  • Creating and sending reports and invoices by email.
  • Connecting systems and applications via watch folders.
  • Batch downloading files over FTP/FTPS/SFTP/HTTP/HTTPS/Torrent.
  • Batch uploading files over FTP/FTPS/SFTP.
  • Database administration and maintenance.
  • Synchronizing the content of local or remote directories.
  • Batch sending tweets.
  • etc.

Benefits

Here are the benefits of using Wexflow:

  • Gain time by automating repetitive tasks.
  • Save money by avoiding re-work and corrections.
  • Reduce human error.
  • Become more efficient and effective in completing your tasks.
  • Become more productive in what you do.
  • Become consistent in what you do.

How to install Wexflow?

Wexflow is easy to install and needs effortless configuration. It can be installed and configured in seconds.

This section shows how to install the .NET version of Wexflow on a Windows system, and how to install the .NET Core version of Wexflow on a Windows system or a Linux system.

Windows (.NET version)

Wexflow can be installed on Windows XP, Windows server 2003 or higher. Wexflow supports .NET Framework 4.0 or higher.

To install Wexflow, proceed as follows:

1. Install Microsoft .NET Framework 4.0 or higher.

2. Install Wexflow:

3. You can choose to create a desktop shortcut:

4. Click on install to perform the installation:

5. Finally, click on finish to finish the installation:

6.  Install Microsoft Sync Framework 2.1 Redistributables if you want to use Sync task:

     6.1 Synchronization-v2.1-x86-ENU.msi and ProviderServices-v2.1-x86-ENU.msi if you are using Wexflow x86.

     6.2 Synchronization-v2.1-x64-ENU.msi and ProviderServices-v2.1-x64-ENU.msi if you are using Wexflow x64.

The following menus are added in the start menu:

  • The "Manager" menu opens Wexflow Manager GUI. 
  • The "Back office" menu opens Wexflow's back office website. 
  • The "Documentation" menu opens the documentation folder of Wexflow. 
  • The "Configuration" menu opens the configuration folder of Wexflow.
  • The "Logs" menu opens the log file of the day.

After Wexflow is installed a Windows Service named Wexflow is installed and starts automatically. To start Wexflow Manager, this Windows Service must be running. However, If you want to stop it you can do it from Windows Services console:

.NET Core version

Windows

  1. Download and install .NET Core 2.1 runtime.
  2. Download and extract the .NET Core package somewhere on your system.
  3. Double-click on "install.bat" to install the configuration files of Wexflow.
  4. That's it. Double-click on "run.bat" to start Wexflow workflow server:

The port 8000 must be free. If you are already using the .NET version of Wexflow, you must stop Wexflow Windows service or change the port of the .NET Core version.

To change the port of the .NET Core version, edit the file Wexflow.Server\appsettings.json and change the value of the setting "WexflowServicePort". Then, change the port in the back office too by editing the configuration file Back-office\js\settings.js.

Finally, you can open the back office by opening the web page Back-office\index.html on a browser:

You can sign in with these credentials if you installed Wexflow for the first time:

Username: admin

Password: wexflow2018

You can then change the password from the back office.

Linux

  1. Download and install .NET Core 2.1 runtime.
  2. Download and extract the .NET Core package in /opt/.
  3. Open a terminal and type the following commands:
cd /opt/wexflow/Wexflow.Server
sudo dotnet Wexflow.Server.dll

That's it, Wexflow is installed and ready for work:

Finally, you can open the back office by opening the web page /opt/wexflow/Back-office/index.html  on a browser:

You can sign in with these credentials if you installed Wexflow for the first time:

Username: admin

Password: wexflow2018

You can then change the password from the back office.

If you want to use the image processing tasks, you must install these libraries:

sudo apt install libc6-dev libgdiplus

Android

After Wexflow server is installed, proceed as follows to install Wexflow Manager on an Android device:

1. Download wexflow.apk

2. Install wexflow.apk

3. Launch Wexflow application and open the application settings through the settings menu:

 

5. Click on "Wexflow Web Service URI":

6. Configure Wexflow Web Service Uri:

Ensure that Wexflow server is running and that the port 8000 is open in the firewall.

That's it, Wexflow application is ready for work. You can sign in with these credentials if you installed Wexflow for the first time:

Username: admin

Password: wexflow2018

You can change the password from the back office.

After you sign in you will get the list of workflows that you can manage easily from your android device:

How to uninstall Wexflow?

Windows

To uninstall Wexflow, simply click on "Uninstall" menu from "Windows Start menu > Wexflow".

Or go to "Configuration Panel > Add/remove programs" then select "Wexflow" and click on uninstall:

After Wexflow is uninstalled, the folders C:\Wexflow\ and C:\WexflowTesting\ are not deleted to prevent user defined workflows and testing scenarios from being deleted. However, If you do not need them you can delete them manually.

The log file C:\Program Files\Wexflow\Wexflow.log is also not deleted to keep track of the last operations done by Wexflow. However, If you do not need the logs you can delete the log files.

Linux

To uninstall Wexflow from a Linux system, proceed as follows:

sudo rm -rf /opt/wexflow

Android

To uninstall Wexflow from an Android device, simply open Settings>Applications>Wexflow then uninstall it.

How to use Wexflow?

General

After installing Wexflow, the folders C:\Wexflow\ and C:\WexflowTesting\ are created. The folder C:\Wexflow\ contains the following elements:

  • Wexflow.xml which is the main configuration file of Wexflow engine. Its path can be configured from C:\Program Files\Wexflow\Wexflow.Clients.WindowsService.exe.config
  • Workflows/ which contains the workflows in XML format.
  • Trash/ which is the trash folder.
  • Temp/ which is the temporary foler of Wexflow.
  • Database/ which contains the database of Wexflow workflow engine.
  • GlobalVariables.xml which contains the global variables for workflows.

The folder C:\WexflowTesting\ contains data of testing scenarios.

The logs are written in C:\Program Files\Wexflow\Wexflow.log. There is one log file per day. The old log files are saved in this format Wexflow.logyyyyMMdd.

In the .NET Core version:

  • Windows: The folders C:\Wexflow-dotnet-core\ and C:\WexflowTesting\ are created. The path of the main configuration file C:\Wexflow-dotnet-core\Wexflow.xml can be configured from Wexflow.Server\appsettings.json. The logs are written in Wexflow.Server\Wexflow.log.
  • Linux: The folders /opt/wexflow/Wexflow/ and /opt/wexflow/WexflowTesting/ are created. The path of the main configuration file /opt/wexflow/Wexflow/Wexflow.xml can be configured from /opt/wexflow/Wexflow.Server/appsettings.json. The logs are written in /opt/wexflow/Wexflow.Server/Wexflow.log.

Below the configuration file of a workflow:

<?xml version="1.0" encoding="utf-8" ?>
<!--
    This is the configuration file of a workflow. 
    A workflow is composed of:
      - An id which is an integer that must be unique.
      - A name which is a string that must be unique.
      - A description wich is a string.
      - A settings section which is composed of the following elements:
        - A launchType which is one of the following options:
          - startup: The workflow is launched when Wexflow Engine starts.
          - trigger: The workflow is launched manually from Wexflow Manager.
          - periodic: The workflow is launched periodically.
          - corn: The workflow is launched depending on a cron expression.
        - A period which is necessary for the periodic launchType. It is 
          a timeSpan in this format dd.hh:mm:ss. For example the period
          00.00:02:00 will launch the workflow every 2 minutes.
        - A cron expression which is necessary for the cron launchType.
          For example '0 0/2 * * * ?' will launch the workflow every 2 minutes.
          Read the following documentation for more details: https://github.com/aelassas/Wexflow/wiki/Cron-scheduling
        - The enabled option which allows to enable or disable a workflow.
          The possible values are true or false.
      - A Tasks section which contains the tasks that will be executed by
        the workflow one after the other.
        - A Task is composed of:
          - An id which is an integer that must be unique.
          - A name wich is one of the options described in the tasks documentation.
          - A description which is a string.
          - The enable option which allows to enable or disable a task. The possible 
            values are true or false.
          - A collection of settings.
        - An ExecutionGraph section which contains the execution graph of the workflow.
          This section is optional and described in the samples section.
-->
<Workflow xmlns="urn:wexflow-schema" id="$int" name="$string" description="$string">
  <Settings>
    <Setting name="launchType" value="startup|trigger|periodic" />
    <Setting name="period" value="dd.hh:mm:ss" />
    <Setting name="enabled" value="true|false" />
  </Settings>
  <Tasks>
    <Task id="$int" name="$string" description="$string" enabled="true|false">
      <Setting name="$string" value="$string" />
      <Setting name="$string" value="$string" />
      <!-- You can add as many settings as you want. -->
    </Task>
    <Task id="$int" name="$string" description="$string" enabled="true|false">
      <Setting name="$string" value="$string" />
      <Setting name="$string" value="$string" />
    </Task>
    <!-- You can add as many tasks as you want. -->
  </Tasks>
  <!-- This node is optional and described in the samples section. -->
  <ExecutionGraph />
</Workflow>

For cron workflows, read the following documentation for more details.

The name option of a Task must be one of the followings.

The execution graph is explained in the samples section.

To learn how to make your own workflows, you can check out the workflow samples availabe in C:\Wexflow\Workflows\ and read the tasks documentation.

If a new workflow is created in C:\Wexflow\Workflows\ or if an existing workflow is deleted or modified, you don't have to restart Wexflow Windows Service so that these modifications take effect. Wexflow engine will automatically detect the changes and reload, add or delete the workflow.

To disable a workflow, you can set the enabled settings option of the workflow to false. If you want to make a workflow disappears from the list of the workflows loaded by Wexflow engine, you can create a directory named disabled within C:\Wexflow\Workflows\ and move that workflow to that directory.

How tasks communicate between each other?

State is transferred between tasks through selectFiles and through selectEntities settings.

This works the following way:

  1. A task in a workflow does its job and produces files which it stores in a collection.
  2. Another task (must be in the same workflow) can afterwards reference those files with the selectFiles XML property, specifying the ID of the task that produced the required files. It then can use these files to do its own job.

More visually (from the examples):

<Workflow xmlns="urn:wexflow-schema" id="1" name="Workflow_Invoices" description="Workflow_Invoices">
    <Settings>
        <Setting name="launchType" value="trigger" />
        <Setting name="enabled" value="true" />
    </Settings>
    <Tasks>
        <Task id="1" name="FilesLoader" description="Loading invoices" enabled="true">
            <Setting name="folder" value="C:\WexflowTesting\Invoices\" />
        </Task>
        <!-- some more tasks here -->
        <Task id="6" name="FilesMover" description="Moving invoices" enabled="true">
            <Setting name="selectFiles" value="1" />
            <Setting name="destFolder" value="C:\WexflowTesting\Invoices_sent\" />
        </Task>
    </Tasks>
</Workflow>

selectEntities setting works the same way as selectFiles. The only difference is that selectEntities is designed to be used for tasks that manipulate custom objects from a database or from web services for example. To go further, read this documentation regarding entities.

Wexflow Manager

When you open Wexflow Manager for the first time, you will get this window:

Here are the credentials to sign in:

  • Username: admin
  • Password: wexflow2018

After you sign in, you will get this window:

You can then change the password from the back office.

With Wexflow Manager, you can manage your workflows with ease. Wexflow Manager is a simple application that allows the user to do the following things:

  • See all the workflows loaded by Wexflow Engine.
  • See the status of the selected workflow (running, suspended or disabled).
  • Start a workflow.
  • Stop a workflow.
  • Suspend a workflow.
  • Resume a workflow.
  • View the logs.
  • The "Back office" button opens Wexflow's back office website from which you can manage workflows, design workflows, track workfows and have real time statistics on workflows.
  • The "Refresh" button allows to reload the list of workflows.
  • The "Restart server" button allows to restart Wexflow Server.
  • The "Search" button allows to search for workflows.
  • Open the help page.
  • Check if a new version is available.

To see what's going on in Wexflow, open the log file C:\Program Files\Wexflow\Wexflow.log in a text editor like Notepad++. Notepad++ will update the log file as it fills up.

Wexflow Back Office

Wexflows's back office is a website that can be hosted on IIS, Apache, Nginx or any other web server. Wexflow's back office can also run locally.

Wexflow's back office gives real-time statistics on workflows. It will let you manage, design and track your workflows with ease. You can use the back office to access, configure, manage, administer, and develop your workflows with ease.

When you open Wexflow's back office for the first time, you will arrive on the login page:

Here are the credentials to sign in:

  • Username: admin
  • Password: wexflow2018

After you sign in, you can change the password from the "Users" page.

If a user forgot his password, he can click on "Forgot password?" link to reset his password:

When the user clicks on "Submit" button, an email is sent to him with a temporary password that he can change after he signs in.

To allow the back office sending emails, the SMPT configuration must be set in the configuration file: C:\Program Files\Wexflow\Wexflow.Clients.WindowsService.exe.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
  </configSections>
  <appSettings>
    <add key="WexflowSettingsFile" value="C:\Wexflow\Wexflow.xml"/>
    <add key="Smtp.Host" value="smtp.gmail.com"/>
    <add key="Smtp.Port" value="587"/>
    <add key="Smtp.EnableSsl" value="true"/>
    <add key="Smtp.User" value="user"/>
    <add key="Smtp.Password" value="password"/>
    <add key="Smtp.From" value="user"/>
  </appSettings>
  ...
 </configuration>

In the .NET Core version, the configuration file to edit in a Windows system is Wexflow.Server\appsettings.json:

{
  "WexflowSettingsFile": "C:\\Wexflow-dotnet-core\\Wexflow.xml",
  "WexflowServicePort":  8000, 
  "Smtp.Host": "smtp.gmail.com",
  "Smtp.Port": 587,
  "Smtp.EnableSsl": true,
  "Smtp.User": "user",
  "Smtp.Password": "password",
  "Smtp.From": "user",
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Verbose",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

In a Linux system, the configuration file to edit is /opt/wexflow/Wexflow.Server/appsettings.json:

{
  "WexflowSettingsFile": "/opt/wexflow/Wexflow/Wexflow.xml",
  "WexflowServicePort":  8000, 
  "Smtp.Host": "smtp.gmail.com",
  "Smtp.Port": 587,
  "Smtp.EnableSsl": true,
  "Smtp.User": "user",
  "Smtp.Password": "password",
  "Smtp.From": "user",
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Verbose",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

After you sign in, you will arrive on the dashboard page:

Wexflow gives you a beautiful dashboard to view real-time statistics on your workflows. Indeed, the "Dashboard" page gives you real-time statistics on workflows and will let you track your workflow server with ease and detail. From the dashboard, you can also filter the workflow entries by a keyword or by date. You can also order the workflow entries by date, by name, etc.

The "Manager" page will let you manage your workflows with ease. Indeed, from this page you can start a workflow, suspend a running workflow, resume a suspended workflow, stop a running workflow and search for workflows by a keyword:

The "Designer" page will let you design your workflows with ease. Indeed, from this page you can create a new workflow, edit an existing workflow or delete a workflow. This page will also let you visualise the dependency graph of a workflow. Using the "Designer" page, we get a nice visual overview of the dependency graph of the workflow. Each node represents a task which has to be run:

The "History" page will let you track all your workflows and everything that happens on the workflow server. Indeed, from this page you will have an overview of all the workflow instances executed on the workflow server. Furthermore, you can filter the entries by keywords or date. You can also order the entries by date, by name, etc.

The "Users" page allows to create new users, change passwords and user's informations, and delete users who have restricted access.

A user who has restricted rights has only access to the "Dashboard" page and the "History" page. When he signs in, he'll have the following view of the back office:

To install Wexflow's back office on a web server simply copy the content of the folder "C:\Program Files\Wexflow\Back office\" on the web server. Then configure the workflow server URI if you want to install the back office on another machine. Simply edit the file "js/settings.js":

Settings = {
    Uri: "http://localhost:8000/wexflow/"
};

Instead of localhost, put the IP or the DNS of the machine where workflow server is installed.

Wexflow Android Manager

Wexflow provides an Android application for managing workflows.

Wexflow Android Manager allows the user to do the following things:

  • See all the workflows loaded by Wexflow Engine.
  • See the status of the selected workflow (running, suspended or disabled).
  • Start a workflow.
  • Stop a workflow.
  • Suspend a workflow.
  • Resume a workflow.
  • The Refresh button allows to reload the list of workflows.

Workflow samples

Wexflow is designed in a way to make things easy and straightforward for beginners in the world of workflows and automation.

Workflows can be designed through Wexflow Web Designer or through XML editing by editing the configuration file of the workflow. However, it is highly recommended to understand Wexflow workflow syntax in order to become familiar with this workflow engine.

Each workflow in Wexflow has a configuration file located by default in the folder C:\Wexflow\Workflows. Each configuration file contains a set of settings and tasks to do depending on a specified schedule and a specified configuration.

In this section, few workflow samples will be presented in order to make the end user familiar with Wexflow workflows syntax.

Sequential workflows

A sequential workflow executes a set of tasks in order, one by one. Tasks are executed in a sequential manner until the last task finishes. The order of the execution of the tasks can be altered by modifying the execution graph of the workflow.

Workflow 1

This workflow uploads invoices to an SFTP server, then waits for 2 days and then notifies the customers.

<Workflow xmlns="urn:wexflow-schema" id="1" name="Workflow_Invoices" description="Workflow_Invoices">
    <Settings>
        <Setting name="launchType" value="trigger" />
        <Setting name="enabled" value="true" />
    </Settings>
    <Tasks>
        <Task id="1" name="FilesLoader" description="Loading invioces" enabled="true">
            <Setting name="folder" value="C:\WexflowTesting\Invoices\" />
        </Task>
        <Task id="2" name="Ftp" description="Uploading invoices" enabled="true">
            <Setting name="protocol" value="sftp" /> <!-- ftp|ftps|sftp -->
            <Setting name="command" value="upload" /> <!-- list|upload|download|delete -->
            <Setting name="server" value="127.0.1" />
            <Setting name="port" value="21" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
            <Setting name="path" value="/" />
            <Setting name="selectFiles" value="1" />
        </Task>
        <Task id="3" name="Wait" description="Waiting for 2 days" enabled="true">
            <Setting name="duration" value="2.00:00:00" />
        </Task>
        <Task id="4" name="FilesLoader" description="Loading emails" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\Emails\Invoices.xml" />
        </Task>
       <Task id="5" name="MailsSender" description="Notifying customers" enabled="true">
            <Setting name="selectFiles" value="4" />
            <Setting name="host" value="127.0.0.1" />
            <Setting name="port" value="587" />
            <Setting name="enableSsl" value="true" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
        </Task>
        <Task id="6" name="FilesMover" description="Moving invoices" enabled="true">
            <Setting name="selectFiles" value="1" />
            <Setting name="destFolder" value="C:\WexflowTesting\Invoices_sent\" />
        </Task>
    </Tasks>
</Workflow>

First of all, the FilesLoader task loads all the invoices located in the folder C:\WexflowTesting\Invoices\, then the Ftp task uploads them to the SFTP server, then the Wait task waits for 2 days, then the FilesLoader task loads the emails in XML format and then the MailsSender task sends the emails. Finally, the FilesMover task moves the invoices to the folder C:\WexflowTesting\Invoices_sent\.

Workflow 2

This workflow waits for files to arrive in C:\WexflowTesting\Watchfolder1\ and C:\WexflowTesting\Watchfolder2\ then uploads them to an FTP server then moves them to C:\WexflowTesting\Sent\ folder. This workflow starts every 2 minutes.

<Workflow xmlns="urn:wexflow-schema" id="2" name="Workflow_FilesSender" description="Workflow_FilesSender">
    <Settings>
        <Setting name="launchType" value="periodic" />
        <Setting name="period" value="00.00:02:00.00" />
        <Setting name="enabled" value="true" />
    </Settings>
    <Tasks>
        <Task id="1" name="FilesLoader" description="Loading files" enabled="true">
            <Setting name="folder" value="C:\WexflowTesting\Watchfolder1\" />
            <Setting name="folder" value="C:\WexflowTesting\Watchfolder2\" />
        </Task>
        <Task id="2" name="Ftp" description="Uploading files" enabled="true">
            <Setting name="protocol" value="ftp" /> <!-- ftp|ftps|sftp -->
            <Setting name="command" value="upload" /> <!-- list|upload|download|delete -->
            <Setting name="server" value="127.0.1" />
            <Setting name="port" value="21" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
            <Setting name="path" value="/" />
            <Setting name="selectFiles" value="1" />
        </Task>
        <Task id="3" name="FilesMover" description="Moving files to Sent folder" enabled="true">
            <Setting name="selectFiles" value="1" />
            <Setting name="destFolder" value="C:\WexflowTesting\Sent\" />
        </Task>
    </Tasks>
</Workflow>

First of all, the FilesLoader task loads all the files located in the folders C:\WexflowTesting\Watchfolder1\ and C:\WexflowTesting\Watchfolder2\ then the Ftp task loads the files and uploads them to the FTP server. Finally, the FilesMover task moves the files to the folder C:\WexflowTesting\Sent\.

Workflow 3

This workflow transcodes the WAV files located in C:\WexflowTesting\WAV\ to MP3 format through FFMPEG and moves the transcoded files to C:\WexflowTesting\MP3\.

<Workflow xmlns="urn:wexflow-schema" id="3" name="Workflow_ffmpeg" description="Workflow_ffmpeg">
    <Settings>
        <Setting name="launchType" value="trigger" />
        <Setting name="enabled" value="true" />
    </Settings>
    <Tasks>
        <Task id="1" name="FilesLoader" description="Loading WAV files" enabled="true">
            <Setting name="folder" value="C:\WexflowTesting\WAV\" />
        </Task>
        <Task id="2" name="ProcessLauncher" description="WAV to MP3" enabled="true">
            <Setting name="selectFiles" value="1" />
            <!-- You need to install FFMPEG -->
            <Setting name="processPath" value="C:\Program Files\ffmpeg\bin\ffmpeg.exe" />
            <!-- variables: {$filePath},{$fileName},{$fileNameWithoutExtension}-->
            <Setting name="processCmd" value="-i {$filePath} -codec:a libmp3lame -qscale:a 2 {$output:$fileNameWithoutExtension.mp3}" /> 
            <Setting name="hideGui" value="true" />
            <Setting name="generatesFiles" value="true" /> 
        </Task>
        <Task id="3" name="FilesMover" description="Moving MP3 files from temp folder" enabled="true">
            <Setting name="selectFiles" value="2" />
            <Setting name="destFolder" value="C:\WexflowTesting\MP3\" />
        </Task>
    </Tasks>
</Workflow>

First of all, the FilesLoader task loads all the files located in the folder C:\WexflowTesting\WAV\ then the ProcessLauncher task launches FFMPEG process on every file by specifying the right command in order to create the MP3 file. Finally, the FilesMover task moves the MP3 files to the folder C:\WexflowTesting\MP3\.

Workflow 4

This workflow waits for WAV files to arrive in C:\WexflowTesting\WAV\ then transcodes them to MP3 files through VLC then uploads the MP3 files to an FTP server then moves the WAV files to C:\WexflowTesting\WAV_processed\. This workflow starts every 2 minutes.

<Workflow xmlns="urn:wexflow-schema" id="4" name="Workflow_vlc" description="Workflow_vlc">
    <Settings>
        <Setting name="launchType" value="periodic" />
        <Setting name="period" value="00.00:02:00.00" />
        <Setting name="enabled" value="true" />
    </Settings>
    <Tasks>
        <Task id="1" name="FilesLoader" description="Loading WAV files" enabled="true">
            <Setting name="folder" value="C:\WexflowTesting\WAV\" />
        </Task>
        <Task id="2" name="ProcessLauncher" description="WAV to MP3" enabled="true">
            <Setting name="selectFiles" value="1" />
            <!-- You need to install VLC-->
            <Setting name="processPath" value="C:\Program Files\VideoLAN\VLC\vlc.exe" />
            <!-- variables: {$filePath},{$fileName},{$fileNameWithoutExtension}-->
            <Setting name="processCmd" value="-I dummy {$filePath} :sout=#transcode{acodec=mpga}:std{dst={$output:$fileNameWithoutExtension.mp3},access=file} vlc://quit" />
            <Setting name="hideGui" value="true" />
            <Setting name="generatesFiles" value="true" />
        </Task>
        <Task id="3" name="Ftp" description="Uploading MP3 files" enabled="true">
            <Setting name="protocol" value="ftp" />
            <Setting name="command" value="upload" />
            <Setting name="server" value="127.0.1" />
            <Setting name="port" value="21" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
            <Setting name="path" value="/" />
            <Setting name="selectFiles" value="2" />
        </Task>
        <Task id="4" name="FilesMover" description="Moving WAV files" enabled="true">
            <Setting name="selectFiles" value="1" />
            <Setting name="destFolder" value="C:\WexflowTesting\WAV_processed\" />
        </Task>
    </Tasks>
</Workflow>

First of all, the FilesLoader task loads all the files located in the folder C:\WexflowTesting\WAV\ then the ProcessLauncher task launches VLC process on every file by specifying the right command in order to create the MP3 file. Then, the Ftp task loads the MP3 files generated by the ProcessLauncher task and then uploads them to the FTP server. Finally, the FilesMover task moves the processed WAV files to the folder C:\WexflowTesting\WAV_processed\.

Workflow 5

This workflow downloads specific files from an FTP server. This workflow starts by listing all the files located at the root folder of the server, then the specific files that will be downloaded are tagged through an XSLT (LisFiles.xslt), then the files are downloaded by the Ftp task through todo="toDownload" and from="app4" tags, then the downloaded files are moved to the folder C:\WexflowTesting\Ftp_download\.

<Workflow xmlns="urn:wexflow-schema" id="5" name="Workflow_Ftp_download_tag" description="Workflow_Ftp_download_tag">
    <Settings>
        <Setting name="launchType" value="trigger" /> <!-- startup|trigger|periodic -->
        <Setting name="enabled" value="true" /> <!-- true|false -->
    </Settings>
    <Tasks>
        <Task id="1" name="Ftp" description="Listing files (FTP)" enabled="true">
            <Setting name="command" value="list" />
            <Setting name="protocol" value="ftp" /> <!-- ftp|ftps|sftp -->
            <Setting name="server" value="127.0.1" />
            <Setting name="port" value="21" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
            <Setting name="path" value="/" />
        </Task>
        <Task id="2" name="ListFiles" description="Listing files" enabled="true">
        </Task>
        <Task id="3" name="Xslt" description="Renaming and tagging files" enabled="true">
            <Setting name="selectFiles" value="2" />
            <Setting name="xsltPath" value="C:\Wexflow\Xslt\ListFiles.xslt" />
            <Setting name="version" value="2.0" /> <!-- 1.0|2.0 -->
            <Setting name="removeWexflowProcessingNodes" value="false" />
        </Task>
        <Task id="4" name="Ftp" description="Downloading files" enabled="true">
            <Setting name="command" value="download" />
            <Setting name="protocol" value="ftp" /> <!-- ftp|ftps|sftp -->
            <Setting name="server" value="127.0.1" />
            <Setting name="port" value="21" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
            <Setting name="path" value="/" />
            <Setting name="selectFiles" todo="toDownload" from="app4" />
        </Task>
        <Task id="5" name="FilesMover" description="Moving files to Ftp_download" enabled="true">
            <Setting name="selectFiles" value="4" />
            <Setting name="destFolder" value="C:\WexflowTesting\Ftp_download\" />
            <Setting name="overwrite" value="true" />
        </Task>
    </Tasks>
</Workflow>

Roughly speaking, the Ftp task loads the list of files located at the root folder of the FTP server in the running instance of the workflow, then the ListFiles task outputs and XML file that contains all the files loaded then the Xslt task takes as input this XML and generates an XML wich contains a system node called <WexflowProcessing> wich contains the list of files to be tagged and/or renamed.

To understand how tagging and renaming files work, refer to the documentation of the ListFiles and Xslt tasks.

Below is the XSLT ListFiles.xslt used for tagging files:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/">
    <root>
      <WexflowProcessing>
        <xsl:for-each select="//WexflowProcessing/Workflow/Files//File">
          <xsl:choose>
            <xsl:when test="@name = 'file1.txt'">
              <File taskId="{@taskId}" name="{@name}" renameTo="file1_renamed.txt" 

                    todo="toRename" 

                    from="app1" />
            </xsl:when>
            <xsl:when test="@name = 'file2.txt'">
              <File taskId="{@taskId}" name="{@name}" renameTo="file2_renamed.txt" 

                    todo="toSend" 

                    from="app2" />
            </xsl:when>
            <xsl:when test="@name = 'file3.txt'">
              <File taskId="{@taskId}" name="{@name}" renameTo="file3_renamed.txt" 

                    todo="toDownload" 

                    from="app3" />
            </xsl:when>
            <xsl:when test="@name = 'file4.txt'">
              <File taskId="{@taskId}" name="{@name}" renameTo="file4_renamed.txt"

                    todo="toDownload" 

                    from="app4" />
            </xsl:when>
          </xsl:choose>
        </xsl:for-each>
      </WexflowProcessing>
    </root>
  </xsl:template>
</xsl:stylesheet>

Execution graph

This workflow loads the file C:\WexflowTesting\file1.txt then uploads it to an FTP server then moves it to C:\WexflowTesting\Sent\ folder.

<Workflow xmlns="urn:wexflow-schema" id="6" name="Workflow_Ftp_upload" description="Workflow_Ftp_upload">
    <Settings>
        <Setting name="launchType" value="trigger" />
        <Setting name="enabled" value="true" />
    </Settings>
    <Tasks>
        <Task id="1" name="FilesLoader" description="Loading files" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\file1.txt" />
        </Task>
        <Task id="2" name="Ftp" description="Uploading files" enabled="true">
            <Setting name="protocol" value="ftp" />
            <Setting name="command" value="upload" />
            <Setting name="server" value="127.0.1" />
            <Setting name="port" value="21" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
            <Setting name="path" value="/" />
            <Setting name="selectFiles" value="1" />
        </Task>
        <Task id="3" name="FilesMover" description="Moving files to Sent folder" enabled="true">
            <Setting name="selectFiles" value="1" />
            <Setting name="destFolder" value="C:\WexflowTesting\Sent\" />
        </Task>
    </Tasks>
    <ExecutionGraph>
      <Task id="1"><Parent id="-1" /></Task>
      <Task id="2"><Parent id="1"  /></Task>
      <Task id="3"><Parent id="2"  /></Task>
    </ExecutionGraph>
</Workflow>

First of all, the FilesLoader task loads the file C:\WexflowTesting\file1.txt then the Ftp task loads that file and uploads it to the FTP server. Finally, the FilesMover task moves that file to the folder C:\WexflowTesting\Sent\.

By convention, the parent task id of first task to be executed must always be -1. The execution graph of this workflow will execute the tasks in the following order:

However, if the execution graph is modified as follows:

<ExecutionGraph>
  <Task id="1"><Parent id="-1" /></Task>
  <Task id="3"><Parent id="1"  /></Task>
  <Task id="2"><Parent id="3"  /></Task>
</ExecutionGraph>

The tasks will be executed as follows:

If the execution graph is modified as follows:

<ExecutionGraph>
  <Task id="3"><Parent id="-1" /></Task>
  <Task id="2"><Parent id="3"  /></Task>
  <Task id="1"><Parent id="2"  /></Task>
</ExecutionGraph>

The tasks will be executed as follows:

Two things are forbidden in the execution graph:

  1. Infinite loops.
  2. Parallel tasks.

Here is an example of an infinite loop:

<ExecutionGraph>
  <Task id="1"><Parent id="-1" /></Task>
  <Task id="2"><Parent id="1"  /></Task>
  <Task id="1"><Parent id="2"  /></Task>
</ExecutionGraph>

Here is an example of parallel tasks:

<ExecutionGraph>
  <Task id="1"><Parent id="-1" /></Task>
  <Task id="2"><Parent id="1"  /></Task>
  <Task id="3"><Parent id="1"  /></Task>
</ExecutionGraph>

Flowchart workflows

A flowchart workflow is a workflow that contains at least one flowchart node (If/While/Switch) in its execution graph. A flow chart node takes as input a flowchart task (A task that returns either true or false after performing its job) and a set of tasks to execute in order, one by one. The order of the execution of the tasks can be altered by modifying the execution graph of the flowchart node.

If

The following workflow is a flowchart workflow that is triggered by the file file.trigger. If the file file.trigger is found on the file system then this workflow will upload the file file1.txt to an FTP server then it will notify customers that the upload was successful. Otherwise, if the trigger file.trigger is not found on the file system then the workflow will notify customers that the upload failed.

<Workflow xmlns="urn:wexflow-schema" id="7" name="Workflow_If" description="Workflow_If">
    <Settings>
        <Setting name="launchType" value="trigger" />
        <Setting name="enabled" value="true" />
    </Settings>
    <Tasks>
        <Task id="1" name="FilesLoader" description="Loading files" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\file1.txt" />
        </Task>
        <Task id="2" name="Ftp" description="Uploading files" enabled="true">
            <Setting name="protocol" value="ftp" />
            <Setting name="command" value="upload" />
            <Setting name="server" value="127.0.1" />
            <Setting name="port" value="21" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
            <Setting name="path" value="/" />
            <Setting name="selectFiles" value="1" />
        </Task>
        <Task id="3" name="FilesLoader" description="Loading emails (OK)" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\Emails\Emails.xml" />
        </Task>
       <Task id="4" name="MailsSender" description="Notifying customers (OK)" enabled="true">
            <Setting name="selectFiles" value="3" />
            <Setting name="host" value="127.0.0.1" />
            <Setting name="port" value="587" />
            <Setting name="enableSsl" value="true" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
        </Task>
        <Task id="5" name="FilesLoader" description="Loading emails (KO)" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\Emails\Emails.xml" />
        </Task>
       <Task id="6" name="MailsSender" description="Notifying customers (KO)" enabled="true">
            <Setting name="selectFiles" value="5" />
            <Setting name="host" value="127.0.0.1" />
            <Setting name="port" value="587" />
            <Setting name="enableSsl" value="true" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
        </Task>
        <Task id="99" name="FileExists" description="Checking trigger file" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\file.trigger" />
        </Task>
    </Tasks>
    <ExecutionGraph>
      <If id="100" parent="-1" if="99">
         <Do>
            <Task id="1"><Parent id="-1" /></Task>
            <Task id="2"><Parent id="1"  /></Task>
            <Task id="3"><Parent id="2"  /></Task>
            <Task id="4"><Parent id="3"  /></Task>
         </Do>
         <Else>
            <Task id="5"><Parent id="-1" /></Task>
            <Task id="6"><Parent id="5"  /></Task>
         </Else>
      </If>
    </ExecutionGraph>
</Workflow>

By convention, the parent task id of the first task to execute in <Do> and <Else> nodes must always be -1.

You can add If flowchart nodes pretty much wherever you want in the execution graph. Also, you can add as mush as you want. You can also add them in the event nodes OnSuccess, OnWarning and OnError.

An If can be inside an If, a While and a Switch.

While

This workflow is triggered by the file file.trigger. While the file file.trigger exists, this workflow will upload the file file1.txt to an FTP server then it will notify customers then it will wait for 2 days then it will start again.

<Workflow xmlns="urn:wexflow-schema" id="8" name="Workflow_While" description="Workflow_While">
    <Settings>
        <Setting name="launchType" value="trigger" />
        <Setting name="enabled" value="true" />
    </Settings>
    <Tasks>
        <Task id="1" name="FilesLoader" description="Loading files" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\file1.txt" />
        </Task>
        <Task id="2" name="Ftp" description="Uploading files" enabled="true">
            <Setting name="protocol" value="ftp" />
            <Setting name="command" value="upload" />
            <Setting name="server" value="127.0.1" />
            <Setting name="port" value="21" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
            <Setting name="path" value="/" />
            <Setting name="selectFiles" value="1" />
        </Task>
        <Task id="3" name="FilesLoader" description="Loading emails" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\Emails\Emails.xml" />
        </Task>
       <Task id="4" name="MailsSender" description="Notifying customers" enabled="true">
            <Setting name="selectFiles" value="3" />
            <Setting name="host" value="127.0.0.1" />
            <Setting name="port" value="587" />
            <Setting name="enableSsl" value="true" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
        </Task>
        <Task id="5" name="Wait" description="Waiting for 2 days..." enabled="true">
            <Setting name="duration" value="02.00:00:00" />
        </Task>
        <Task id="99" name="FileExists" description="Checking trigger file" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\file.trigger" />
        </Task>
    </Tasks>
    <ExecutionGraph>
      <While id="100" parent="-1" while="99">
        <Task id="1"><Parent id="-1" /></Task>
        <Task id="2"><Parent id="1"  /></Task>
        <Task id="3"><Parent id="2"  /></Task>
        <Task id="4"><Parent id="3"  /></Task>
        <Task id="5"><Parent id="4"  /></Task>
      </While>
    </ExecutionGraph>
</Workflow>

By convention, the parent task id of the first task to be executed in the <While> node must always be -1.

You can add While flowchart nodes pretty much wherever you want in the execution graph. Also, you can add as mush as you want. You can also add them in the event nodes OnSuccess, OnWarning and OnError.

A While can be inside a While, an If and a Switch.

Switch

This workflow starts every 24 hours. On Monday, it uploads files to an FTP server and on Wednesday it notifies customers.

<Workflow xmlns="urn:wexflow-schema" id="43" name="Workflow_Switch" description="Workflow_Switch">
  <Settings>
    <Setting name="launchType" value="periodic" />
	<Setting name="period" value="1.00:00:00" />
    <Setting name="enabled" value="true" />
  </Settings>
  <Tasks>
    <Task id="1" name="Now" description="Getting current day" enabled="true">
      <Setting name="culture" value="en-US" />  
      <Setting name="format" value="dddd" />
    </Task>
	<Task id="2" name="FilesLoader" description="Loading files" enabled="true">
      <Setting name="file" value="C:\WexflowTesting\file1.txt" />
    </Task>
    <Task id="3" name="Ftp" description="Uploading files" enabled="true">
      <Setting name="protocol" value="ftp" />
      <Setting name="command" value="upload" />
      <Setting name="server" value="127.0.1" />
      <Setting name="port" value="21" />
      <Setting name="user" value="user" />
      <Setting name="password" value="password" />
      <Setting name="path" value="/" />
      <Setting name="selectFiles" value="1" />
    </Task>
   <Task id="4" name="FilesLoader" description="Loading emails" enabled="true">
      <Setting name="file" value="C:\WexflowTesting\Emails\Emails.xml" />
   </Task>
   <Task id="5" name="MailsSender" description="Notifying customers" enabled="true">
        <Setting name="selectFiles" value="3" />
        <Setting name="host" value="127.0.0.1" />
        <Setting name="port" value="587" />
        <Setting name="enableSsl" value="true" />
        <Setting name="user" value="user" />
        <Setting name="password" value="password" />
    </Task>
  </Tasks>
  <ExecutionGraph>
    <Switch id="100" parent="-1" switch="1">
      <Case value="Monday">
        <Task id="2"><Parent id="-1" /></Task>
		<Task id="3"><Parent id="2" /></Task>
      </Case>
      <Case value="Wednesday">
        <Task id="4"><Parent id="-1" /></Task>
        <Task id="5"><Parent id="4" /></Task>
      </Case>
      <Default />
    </Switch>
  </ExecutionGraph>
</Workflow>

By convention, the parent task id of the first task to be executed in the Case/Default nodes must always be -1.

You can add Switch flowchart nodes pretty much wherever you want in the execution graph. Also, you can add as mush as you want. You can also add them in the event nodes OnSuccess, OnWarning and OnError.

A Switch can be inside a While, an If and a Switch.

Workflow events

After a workflow finishes its job, its final result is either success, or warning or error. If its final result is success, the OnSuccess event is triggered. If its final result is warning, the OnWarning event is triggered. If its final result is error, the OnError event is triggered. An event contains a set of tasks and/or flowchart nodes to execute in order, one by one. The order of the execution of the tasks and/or flowchart nodes can be altered by modifying the execution graph of the event.

This workflow uploads the file1.txt to an FTP server then notifies customers in case of success.

<Workflow xmlns="urn:wexflow-schema" id="9" name="Workflow_Events" description="Workflow_Events">
    <Settings>
        <Setting name="launchType" value="trigger" />
        <Setting name="enabled" value="true" />
    </Settings>
    <Tasks>
        <Task id="1" name="FilesLoader" description="Loading files" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\file1.txt" />
        </Task>
        <Task id="2" name="Ftp" description="Uploading files" enabled="true">
            <Setting name="protocol" value="ftp" />
            <Setting name="command" value="upload" />
            <Setting name="server" value="127.0.1" />
            <Setting name="port" value="21" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
            <Setting name="path" value="/" />
            <Setting name="selectFiles" value="1" />
        </Task>
       <Task id="3" name="FilesLoader" description="Loading emails" enabled="true">
            <Setting name="file" value="C:\WexflowTesting\Emails\Emails.xml" />
        </Task>
       <Task id="4" name="MailsSender" description="Notifying customers" enabled="true">
            <Setting name="selectFiles" value="3" />
            <Setting name="host" value="127.0.0.1" />
            <Setting name="port" value="587" />
            <Setting name="enableSsl" value="true" />
            <Setting name="user" value="user" />
            <Setting name="password" value="password" />
        </Task>
    </Tasks>
    <ExecutionGraph>
      <Task id="1"><Parent id="-1" /></Task>
      <Task id="2"><Parent id="1"  /></Task>
      <OnSuccess>
        <Task id="3"><Parent id="-1" /></Task>
        <Task id="4"><Parent id="3"  /></Task>
      </OnSuccess>
    </ExecutionGraph>
</Workflow>

The flowchart event nodes <OnWarning> and <OnError> can be used in the same way. You can put If, While and Switch flowchart nodes in event nodes.

These are simple and basic workflows to give an idea on how to make your own workflows. However, if you have multiple systems, applications and automations involved in a workflow, the workflow could be very interesting.

How to create a custom task?

General

Custom tasks are a must in a workflow engine and allow systems and applications to interact.

To create a custom task MyTask for example you will need to proceed as follows:

  1. Create a class library project in Visual Studio and name it Wexflow.Tasks.MyTask.
  2. Reference Wexflow.Core.dll and its dependencies through nuget package manager:
PM> Install-Package Wexflow

     3. Create a public class MyTask that implements the abstract class Wexflow.Core.Task.

Wexflow.Tasks.MyTask code should look like as follows:

using System.Threading;
using System.Xml.Linq;
using Wexflow.Core;

namespace Wexflow.Tasks.MyTask
{
    public class MyTask : Task
    {
        public MyTask(XElement xe, Workflow wf) : base(xe, wf)
        {
            // Task settings goes here
        }

        public override TaskStatus Run()
        {
            try
            {
                // Task logic goes here

                return new TaskStatus(Status.Success);
            }
            catch (ThreadAbortException)
            {
                throw;
            }
        }
    }
}

Each task returns a TaskStatus object when it finishes performing its job. TaskStatus is composed of the following elements:

public Status Status { get; set; }
public bool Condition { get; set; }
public string SwitchValue { get; set; }

The Status can be one of the followings:

public enum Status
{
  Success,
  Warning,
  Error
}

For example, if a task performs an opetation on a collection of files and if this operation succeeds for all the files then its Status should be Success. Otherwise if this operation succeeds for some files and fails for others then its Status should be Warning. Otherwise if this operation fails for all the files then its Status should be Error.

The Condition property is designed for flowchart tasks. In addition to the Status of the task, a flowchart task returns either true or false after performing its operation.

The Condition property should always be set to false for sequential tasks.

The SwitchValue is designed to be used by Switch flowchart nodes. If you set a value in the SwitchValue property and use this task in a Switch flowchart node, the case corresponding to the value will be executed. Otherwise, if the Default case is set, it will be executed.

You can use the TaskStatus constructor that suits your needs.

To retrieve settings, you can use the following methods:

string settingValue = this.GetSetting("settingName");
string settingValue = this.GetSetting("settingName", defaultValue);
string[] settingValues = this.GetSettings("settingName");

To select the files loaded by the running instance of a workflow through the selectFiles settings option, you can do it as follows:

FileInf[] files = this.SelectFiles();

To select entities loaded by the running instance of a workflow through the selectEntities settings option, you can do it as follows:

Entity[] entities = this.SelectEntities();

The Entity class could be very useful when working with custom tasks that manipulate objects from a database or Web Services for example.

To load a file within a task, you can do it as follows:

this.Files.Add(new FileInf(path, this.Id));

To load an entity within a task, you can do it as follows:

this.Entities.Add(myEntity);

Finally if you finished coding your custom task, compile the class library project and copy the assembly Wexflow.Tasks.MyTask.dll in C:\Program Files\Wexflow\. Your custom task is then ready to be used as follows:

<Task id="$int" name="MyTask" description="My task description" enabled="true">
    <Setting name="settingName" value="settingValue" />
</Task>

That's it. That's all the things you need to know to start coding your own custom tasks.

To test the custom task, create a new workflow (new XML file) and put the configuration of the custom task in it as follows:

<Workflow xmlns="urn:wexflow-schema" id="99" name="Workflow_MyWorkflow" description="Workflow_MyWorkflow">
	<Settings>
		<Setting name="launchType" value="trigger" /> <!-- startup|trigger|periodic -->
		<Setting name="enabled" value="true" /> <!-- true|false -->
	</Settings>
	<Tasks>
        <Task id="1" name="MyTask" description="My task description" enabled="true">
            <Setting name="settingName" value="settingValue" />
        </Task>
	</Tasks>
</Workflow>

Then, place that XML file in C:\Wexflow\Workflows\.

The workflow will then appear in the list of workflows in Wexflow Manager or Wexflow Web Manager. You can then launch it from there.

Logging

The following methods are available from the Task class for logging:

public void Info(string msg);
public void InfoFormat(string msg, params object[] args);
public void Debug(string msg);
public void DebugFormat(string msg, params object[] args);
public void Error(string msg);
public void ErrorFormat(string msg, params object[] args);
public void Error(string msg, Exception e);
public void ErrorFormat(string msg, Exception e, params object[] args);

Files

Files can be loaded in a task by calling the methods Add or AddRange:

this.Files.Add(myFile);
this.Files.AddRange(myFiles);

Then the files loaded can be selected in other tasks by their task Id as follows:

<Setting name="selectFiles" value="$taskId" />

To select the files loaded by the running instance of a workflow through the selectFiles settings option, you can do it as follows:

FileInf[] files = this.SelectFiles();

Entities

Entity is an abstract class having the Id of the task as property:

namespace Wexflow.Core
{
    public abstract class Entity
    {
        public int TaskId { get; set; }
    }
}

The entity class is designed to be inherited by other classes such as objects retrieved from a database or a web service or an API or whatever. Then, these objects can be loaded in a task by calling the methods Add or AddRange:

this.Entities.Add(myEntity);
this.Entities.AddRange(myEntities);

Then, the entities loaded can be selected in other tasks by their task Id as follows:

<Setting name="selectEntities" value="$taskId" />

Entities are designed to be used in custom tasks.

To select entities loaded by the running instance of a workflow through the selectEntities settings option, you can do it as follows:

Entity[] entities = this.SelectEntities();

The Entity class could be very useful when working with custom tasks that manipulate objects from a database or Web Services for example.

Hashtable

Tasks contains a Hashtable that can be used as a shared memory between them.

To add an object to the Hashtable, simply proceed as follows:

this.Hashtable.Add("myKey", myObject);

To retrieve an object from the Hashtable, simply proceed as follows:

var myObject = this.Hashtable["myKey"];

To remove an object from the Hashtable, simply proceed as follows:

this.Hashtable.Remove("myKey");

Designer

To make your custom task MyTask appear in the dropdown of the designer, simply open the file C:\Wexflow\TasksNames.json and add "MyTask" in it as follows:

[
...
"MyTask"
]

You must also add the settings by opening the file C:\Wexflow\TasksSettings.json and adding your custom settings as follows:

{
...
"MyTask": ["settingName"]
}

That's it. MyTask will show up in the designer dropdows and when selected its settings will show up too.

Debugging

To debug custom tasks, you can use logging.

You can also clone this repository and open Wexflow.vs2017.sln or Wexflow.vs2010.sln in Visual Studio and follow these guidelines to debug Wexflow Windows Service. Then, you can create your custom task in the solution and debug it. Of course, to debug it you have to proceed as follows:

  1. Create your custom task.
  2. Reference your custom task in Wexflow.Clients.WindowsService.
  3. Create a workflow using your custom task (see the documentation).
  4. Place your workflow in C:\Wexflow\Workflows.
  5. Open Wexflow Manager or Wexflow Web Manager and trigger your workflow from there.

How to debug Wexflow?

To debug Wexflow, proceed as follows:

  • Install Microsoft .NET Framework 4.0 or higher.
  • Install Microsoft Sync Framework 2.1 SDK if you want to debug Sync task.
  • Install Visual Studio 2010 or higher.
  • Copy the folders "Wexflow" and "WexflowTesting" in C:\. You can download them from here.
  • Debug the project Wexflow.Clients.WindowsService to start Wexflow server in debug mode.
  • Debug the project Wexflow.Clients.Manager to start Wexflow Manager in debug mode.

That's it. I hope you enjoyed reading this article. If you have any thoughts to improve Wexflow or if you face any issues or if you want to contribute to this project please let me know in the comments or in GitHub.

Libraries used by Wexflow

Here is the list of the libraries used by Wexflow:

  • FluentFTP: An FTP client supporting FTP and FTPS(exmplicit/implicit) written in C# and under MIT license.
  • SSH.NET: An SSH library for .NET written in C# and under MIT license.
  • SharpZipLib: A Zip, GZip, Tar and BZip2 library written in C# and under MIT license.
  • Saxon-HE: An XSLT and XQuery Processor that provides implementations of XSLT (2.0), XQuery (1.0, 3.0, and 3.1), and XPath (2.0, 3.0, and 3.1) at the basic level of conformance defined by W3C. It's an open source library available under the Mozilla Public License version 1.0.
  • log4net: A port of the famous Apache log4j framework to the Microsoft .NET runtime. It's under the Apache license version 2.0.
  • TweetSharp: A fast and clean wrapper around the Twitter AP written in C#.
  • Microsoft Sync Framework 2.1: A data synchronization platform that allows to synchronize data across multiple data stores.
  • Json.NET: A high-performance JSON framework for .NET written in C# and under MIT license.
  • Hammock: an HTTP library that simplifies consuming and wrapping RESTful services.
  • Mono.Security: A library that provides the missing pieces to .NET security.
  • Oracle Data Access Components (ODAC): Oracle database client for .NET.
  • MySQL Connector/Net: A fully-managed ADO.NET driver for MySQL.
  • System.Data.SQLite: An ADO.NET provider for SQLite.
  • Npgsql: An open source ADO.NET Data Provider for PostgreSQL written in C# and under the PostgreSQL License, a liberal OSI-approved open source license.
  • .NET Data Provider for Teradata: An ADO.NET provider for Teradata.
  • Eto.Forms: A cross platform GUI framework for desktop and mobile applications in .NET.
  • highlight.js: Javascript syntax highlighter.
  • Cytoscape.js: Graph theory / network library for analysis and visualization.
  • OpenPop.NET: An open source implementation of a POP3 client and a robust MIME parser written in C#. It allows developers easy access to email on a POP3 server in a matter of minutes.
  • iTextSharp: .NET port of the iText library.
  • DiffPlex: A Netstandard 1.0 C# library to generate textual diffs.
  • MonoTorrent: A cross-platform library implementing the BitTorrent protocol. It is based on the Mono implementation of the .NET Framework.
  • Quartz.NET: A port of Java Quartz which is an enterprise class job sheduling system.
  • SharpCompress: A fully managed C# library to deal with many compression types and formats.
  • DiscUtils: Utility libraries to interact with discs, filesystem formats and more.
  • SevenZipSharp: Managed 7-zip library written in C# that provides data (self-)extraction and compression (all 7-zip formats are supported). It wraps 7z.dll or any compatible one and makes use of LZMA SDK.
  • 7-Zip: 7-Zip is a file archiver with a high compression ratio.
  • Selenium WebDriver: A set of different software tools each with a different approach to supporting browser automation.
  • ChromeDriver: An open source tool for automated testing of webapps across many browsers.
  • NUglify: A HTML, JavaScript and CSS minification Library for .NET.
  • SharpScss: A portable cross-platform pinvoke .NET wrapper around libsass to convert SCSS to CSS.
  • libsass: A C/C++ implementation of a Sass compiler.
  • YamlDotNet: A .NET library for YAML.
  • LiteDB: A .NET NoSQL Document Store in a single data file.
  • Nancy: Lightweight, low-ceremony, framework for building HTTP based services on .Net and Mono.
  • Kestrel: A cross platform web server for ASP.NET Core.

History

  • 05 Jan 2017:
  • 09 Jan 2017: 
    • Released version 1.0.1.
    • Created Wexflow Windows Service.
    • Created Tar, Tgz and Sql tasks.
    • Updated Wexflow Manager.
    • Fixed some bugs.
  • 16 Jan 2017:
  • 23 Jan 2017:
    • Released version 1.0.3.
    • Created HttpSyncFilesRenamerFilesExist and Wait tasks.
    • Created file tags functionality.
    • Added list, download and delete commands to Ftp task (FTP/FTPS(explicit/implicit)/SFTP).
    • Added retryCount and retryTimeout setting options to Ftp task.
    • Updated Wexflow manager.
    • Updated Wexflow engine.
    • Fixed some bugs.
    • Updated setup file.
    • Updated article content.
  • 26 Jan 2017:
    • Released version 1.0.4.
    • Created XSD validation of worklfow files before loading them.
    • Created tasks execution graph.
    • Created flowchart workflows (DoIf and DoWhile).
    • Created workflow events (OnSuccess, OnWarning and OnError).
    • Created FileExists flowchart task.
    • Updated setup.
    • Updated article content.
  • 30 Jan 2017:
    • Released version 1.0.5.
    • Created Wexflow Web Manager: a lightweight JavaScript API (~6Kb) for managing workflows.
    • Created Wexflow Manager GUI for Linux.
    • Updated Wexflow Manager for Windows.
    • Updated setup for Windows.
    • Created a setup for Linux.
  • 06 Feb 2017:
    • Released version 1.0.6.
    • Created Wexflow Android Manager: an Android application for managing workflows.
    • Updated Wexflow Web Manager.
    • Updated Wexflow Manager (Windows and Linux).
    • Updated Wexflow Engine.
  • 17 Feb 2017:
    • Released version 1.0.7.
    • Created Movedir task.
    • Updated Wexflow Web Manager.
    • Updated Wexflow Manager (Windows, Linux and Android).
    • Updated Wexflow Engine.
    • Fixed some bugs.
  • 06 Mar 2017:
    • Released version 1.0.8.
    • Created Swictch/Case flowchart node.
    • Created Now task.
    • Created Wexflow Manager for macOS.
    • Updated If and While flowchart nodes syntax.
    • Updated setup projects.
    • Now, an If flowchart node can be inside an If, a While and a Switch flowchart nodes.
    • Now, a While flowchart node can be inside an If, a While and a Switch flowchart nodes.
    • Now, a Switch flowchart node can be inside an If, a While and a Switch flowchart nodes.
    • Code refactoring.
    • Fixed some bugs.
  • 07 Apr 2017:
    • Released version 1.0.9.
    • Fixed Switch flowchart node children execution.
    • Fixed WCF service configuration issue.
  • 22 May 2017:
    • Released version 1.1.
    • Created Workflow task. This task allows to start, suspend, resume or stop a list of workflows.
    • Performance optimization.
    • Code refactoring.
  • 07 Oct 2017:
    • Released version 1.2.
    • Implemented mail attachments in MailsSender task.
    • Changed encoding to UTF-8 in logging and Wexflow Web Service client.
    • Hot reload of workflows:
      • If a workflow XML file is added to Workflows folder, It is automatically loaded without having to restart Wexflow Windows service.
      • If a workflow XML file is deleted from Workflows folder, It is automatically stopped and removed without having to restart Wexflow Windows service.
      • If a workflow XML file is changed. It is automatically reloaded without having to restart Wexflow Windows service.
    • Fixed empty Default node issue in Switch flowchart node.
    • Updated Windows, macOS and Linux manager apps.
  • 11 Oct 2017:
  • 13 Oct 2017:
    • Released version 1.4.
    • Implemented Wexflow Designer edit mode:
      • Now it is possible to edit the workflow configuration through Wexflow Designer.
      • Now it is possible to edit tasks configuration and settings through Wexflow Designer.
  • 15 Oct 2017:
    • Released version 1.5.
    • Added new features to Wexflow Designer:
      • Add a task setting.
      • Remove a task setting.
      • Add an attribute to selectFiles and selectAttachments task settings.
      • Remove an attribute from selectFiles and selectAttachments task settings.
      • Add a task to a linear workflow.
      • Remove a task from a linear workflow.
  • 16 Oct 2017:
  • 17 Oct 2017:
    • Released version 1.7.
    • Now we can delete workflows in Wexflow Designer.
    • Enhanced task settings edition.
    • Updated the documentation of MailsSender task.
    • Fixed some bugs in Wexflow Designer.
    • Updated Windows setup project.
  • 18 Oct 2017:
    • Released version 1.8.
    • Implemented the visualization of the execution graph of a linear workflow in Wexflow Designer.
    • Updated Windows setup project.
  • 19 Oct 2017:
    • Released version 1.9.
    • Added new features and fixed some issues in Wexflow Designer:
      • Implemented the visualization of the execution graph of a non lienar workflow.
      • Displayed the xml button after saving a new workflow.
      • Fixed an issue of editing the id of a new workflow after saving it.
      • Fixed an issue regarding the value of the workflow file path after saving a new workflow and editing its name.
      • Fixed an issue of changing the id of a selected workflow.
  • 20 Oct 2017:
    • Released version 2.0.
    • Added a new feature and fixed some issues in Wexflow Web Designer:
      • Changed the style of Wexflow Web Designer.
      • Fixed an issue when saving an existing workflow.
      • Fixed an issue when changing the id of an existing workflow multiple times.
    • Added new features to Wexflow Web Manager:
      • Changed the style of Wexflow Web Manager.
      • Added a confirm dialog when stopping a workflow.
    • Added regexPattern option in the task FilesLoader.
    • Added recursive load option in the task FilesLoader.
    • Updated the documentation of the task FilesLoader.
    • Updated Wexflow web service.
    • Updated the workflow Workflow_FilesLoader.
    • Updated the folder samples\WexflowTesing.
    • Updated Windows setup project.
    • Updated the documentation of the task FilesRenamer.
  • 27 Oct 2017:
    • Released version 2.1.
    • Added new tasks:
      • Sha1: This task generates SHA-1 hashes of a collection of files and writes the results in an XML file.
      • Sha256: This task generates SHA-256 hashes of a collection of files and writes the results in an XML file.
      • Sha512: This task generates SHA-512 hashes of a collection of files and writes the results in an XML file.
      • FilesConcat: This task concatenates a collection of files.
      • FilesSplitter: This task splits files into chunks.
      • FilesInfo: This task generates files information of a collection of files and writes the results in an XML file.
      • MediaInfo: This task generates the most relevant technical and tag data for video and audio files and outputs the results in an XML file.
      • MailsReceiver: This task fetches a collection of emails.
      • ProcessKiller: This task kills a process.
    • Implemented unit tests (vs2017).
    • Updated samples\WexflowTesting folder.
    • Updated samples\Workflows folder.
    • Updated the documenttaion of the tasks FilesExist and Xslt.
    • Updated Windows installer.
  • 05 Nov 2017:
    • Released version 2.2.
    • Added Designer and Logs buttons in Wexflow Manager.
    • Added Help and About menus in Wexflow Manager.
    • Added the task description in the execution graph in Wexflow Designer.
    • Fixed an issue in Wexflow Designer: We cannot save a workflow because of a typo in wexflow-designer.js
    • Created Wexflow.Core nuget package.
    • Added new tasks:
      • Unzip: This task extracts ZIP archives.
      • Untar: This task extracts TAR archives.
      • Untgz: This task extracts TAR.GZ archives.
      • ProcessInfo: This task shows information about a process.
      • TextToPdf: This task generates PDF files from TEXT files.
      • HtmlToPdf: This task generates PDF files from HTML files.
      • SqlToXml: This task executes a collection of SQL scripts (select queries) and outputs the results in XML files.
      • SqlToCsv: This task executes a collection of SQL scripts (select queries) and outputs the results in CSV files.
      • Guid: This task generates Guids and outputs the result in an XML file.
    • Added extension setting in the task Xslt.
    • Updated the task FilesCopier.
    • Fixed an issue in Task.GetSetting.
    • Added new unit tests.
    • Updated ImagesTransformer and ProcessKiller unit tests.
    • Updated samples\WexflowTesting folder.
    • Updated samples\Workflows folder.
    • Updated Windows installer.
    • Code refactoring.
  • 14 Nov 2017:
    • Released version 2.3.
    • Wexflow Designer:
      • Moved tasks names and tasks settings to external files (C:\Wexflow\TasksNames.json and C:\Wexflow\TasksSettings.json).
      • Added Xml and Documentation buttons for tasks.
    • Added Hashtable as shared memory for tasks.
    • Added deleteMessages setting to the task MailsReceiver.
    • Fixed some issues in Sql, SqlToCsv and SqlToXml tasks.
    • Documented Wexflow.Core.
    • Updated Wexflow.Core nuget package.
    • Updated the way we debug Wexflow Windows Service and Wexflow Manager.
    • Fixed the issue #36.
    • Fixed an issue in Wexflow engine.
  • 12 Oct 2018:
    • Released version 2.4.
    • Files renamed via FilesRenamer task were not accessed via successive tasks. Now, the files renamed via FilesRenamer can be accessed via successive tasks.
    • SqlToCsv task is now more configurable:
      • Added a setting to write the header or not, defaults to true.
      • Added setting for quote character such as " so columns can be quoted, eg "column1","column2", defaults to empty string.
      • Added the ability to have multiple result tables to be exported to a single file.
      • Refactored code to write each row to the output file because writing very large data sets to a single StringBuilder will use excessive memory and fail. Perhaps the best trade off is to use original StringBuilder approach and write every 50,000 or 100,000 records, but for code that functions for all use cases is preferred over code that does not.
    • Zip task: Now, if the zip file name is left blanked, the RenameToOrName of the first file in the collection will be taken as the zip file name. For example if the RenameToOrName of the first file is file1.txt, the zip file name will be file1.zip.
  • 18 Oct 2018:
    • Released version 2.5.
    • Created the new following tasks:
      • Torrent: this task downloads torrent files.
      • FilesDiff: this task calculates the diff of two files.
      • FilesEqual: this task allows to check whether two files are the same.
    • Added Refresh button and scrolling in Wexflow Manager.
    • Updated Android app:
      • Updated app style.
      • Added Refresh button.
      • Fixed an issue in the UI.
    • XmlToCsv task is now more configurable:
      • Added a setting to write the seperator character, defaults to ;
      • Added setting for quote character such as " so columns can be quoted, eg "column1","column2", defaults to empty string.
      • Refactored code to write each row to the output file because writing very large data sets to a single StringBuilder will use excessive memory and fail. Perhaps the best trade off is to use original StringBuilder approach and write every 50,000 or 100,000 records, but for code that functions for all use cases is preferred over code that does not.
    • MailsSender task is now more configurable:
      • Cc is now optional.
      • Added Bcc. It is also optional.
    • Fixed an issue of downloading files over HTTPS in Http task.
  • 20 Oct 2018:
    • Released version 2.6.
    • Created 64 bit version of Wexflow.
    • Added "Restart server" button in Wexflow Manager: This button allows to restart Wexflow Windows service from Wexflow Manager.
    • Created the new following tasks:
  • 23 Oct 2018:
    • Released version 2.7.
    • Added cron scheduling: Now a workflow can starts depending on a cron expression.
      • Updated Wexflow Engine.
      • Updated Wexflow Server.
      • Updated Wexflow Manager.
      • Updated Wexflow Web Manager.
      • Updated Wexflow Designer.
    • Added period field validation in Wexflow Designer for periodic workflows.
    • Added cron expressions validation in Wexflow Designer for cron workflows.
    • Upgraded SqlSqlToXml and SqlToCsv tasks to Oracle.ManagedDataAccess (#59).
    • Fixed a graphical issue in Wexflow Manager.
    • Fixed some issues in Wexflow Designer.
    • Fixed an issue when stopping Wexflow Server.
    • Fixed an issue when a periodic or a cron workflow is deleted or modified in Wexflow Engine.
    • Fixed System.Data.SQLite.dll issue (#61).
    • Updated nuget package to version 2.7.2.
  • 29 Oct 2018:
    • Released version 2.8.
    • Now periodic workflows are launched using Quartz.NET scheduler.
    • Now Wexflow Server restarts in an asynchronous way from Wexflow Manager.
    • Fixed a typo in Wexflow Manager.
    • Fixed an issue in MediaInfo task.
    • Fixed an issue about logging in Workflow task.
    • Fixed an issue in FilesMover unit test.
    • Updated the task FileExists.
    • Updated nuget package to version 2.8.1.
    • Created the new following tasks:
      • FilesEncryptor: This task encrypts files.
      • FilesDecryptor: This task decrypts the encrypted files by the task FilesEncryptor.
      • TextsEncryptor: This task encrypts text based files.
      • TextsDecryptor: This task decrypts the files crypted by the task TextsEncryptor.
      • Unrar: This task extracts RAR archives.
      • UnSevenZip: This task extracts 7Z archives.
      • ImagesConcat: This task concatenates images.
      • ImagesOverlay: This task overlays images.
      • CsvToSql: This task converts CSV files to SQL scripts.
      • DatabaseBackup: This task backups databases.
      • DatabaseRestore: This task restores databases.
      • IsoCreator: This task creates a .iso from a source folder.
      • IsoExtractor: This task extracts .iso files.
      • SevenZip: This task creates a .7z from a collection of files.
      • TextToSpeech: This task converts text files to speech files.
      • SpeechToText: This task converts audio files to text files.
      • FileMatch: This is a flowchart task that checks whether a file exists or not in a directory by using a regex pattern.
  • 04 Nov 2018:
    • Released version 2.9.
    • Added search function in the RESTful API.
    • Added search functionality in Wexflow Manager, Wexflow Web Manager and Wexflow Web Designer.
    • Fixed an issue about scrolling in Wexflow Manager.
    • Updated the style of Wexflow Web Designer.
    • Created the new following tasks:
      • WebToScreenshot: This task takes screenshots from urls.
      • WebToHtml: This task retrieves HTML sources from urls.
      • UglifyJs: This task uglifies JavaScript files.
      • UglifyCss: This task compresses and minifies CSS files.
      • UglifyHtml: This task compresses and minifies HTML files.
      • HtmlToText: This task extracts text from HTML files.
      • ScssToCss: This task converts SCSS files to CSS files.
      • YamlToJson: This task converts YAML files to JSON files.
      • JsonToYaml: This task converts JSON files to YAML files.
      • CsvToJson: This task converts CSV files to JSON files.
      • CsvToYaml: This task converts CSV files to YAML files.
      • HttpGet: This task executes GET requests.
      • HttpPost: This task executes POST requests.
      • HttpPut: This task executes PUT requests.
      • HttpPatch: This task executes PATCH requests.
      • HttpDelete: This task executes DELETE requests.
      • Ping: This is a flowchart task that checks whether a server responds to a ping request or not.
      • ExecCs: This task executes C# scripts.
      • ExecVb: This task executes Visual Basic scripts.
  • 07 Nov 2018:
  • 09 Nov 2018:
  • 12 Nov 2018:
    • Released version 3.2.
    • Added authentication to the Android application.
    • Added the page "forgot-password.html" in the back office.
    • Added "Forget password?" link in Wexflow Manager.
    • Fixed an issue in Wexflow Manager about authetication.
    • Fixed an issue in Wexflow Manager about security (Restrict Wexflow Manager to administrators only).
    • Fixed an issue in SpeechToText and TextToSpeech tasks.
    • Fixed an issue in DatabaseBackup and DatabaseRestore tasks.
  • 16 Nov 2018:
    • Released version 3.3.
    • Ported Wexflow workflow engine to .NET Core 2.1.
      • Here are the instructions to install the .NET Core version on Windows.
      • Here are the instructions to install the .NET Core version on Linux.
    • Fixed an issue regarding entries and history entries in the back office.
    • Fixed an issue in the tasks ImagesConcat and ImagesOverlay.
    • Fixed an issue in the tasks Sha256 and Sha512.
    • Fixed a validation issue in the designer.
    • Updated the Template task.
    • Updated Wexflow Web Manager.

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Akram El Assas
Engineer
Morocco Morocco
Akram El Assas graduated from the french engineering school ENSEIRB located in Bordeaux, a city in the south of France, and got his diploma in software engineering in 2010. He worked for about 3 years in France for Mediatvcom, a company specialized in audiovisual, digital television and new technologies. Mediatvcom offers services such as consulting, project management, audit and turnkey solutions adapted to the needs of customers. Akram worked mainly with Microsoft technologies such as C#, ASP.NET and SQL Server but also with JavaScript, jQuery, HTML5 and CSS3. Akram worked on different projects around digital medias such as Media Asset Management systems, Digital Asset Management systems and sometimes on HbbTV apps.

Akram went back to his native country Morocco in 2013. Since then, he started discovering Android and Java development but also continued working with Microsoft technologies.

Comments and Discussions

 
-- No messages could be retrieved (timeout) --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04-2016 | 2.8.181116.1 | Last Updated 16 Nov 2018
Article Copyright 2017 by Akram El Assas
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid