-
-
Notifications
You must be signed in to change notification settings - Fork 178
Custom Tasks
- Introduction
- General
- Installing Your Custom Task in Wexflow
- Suspend/Resume
- Logging
- Files
- Entities
- Shared Memory
- Designer Integration
- How to Debug a Custom Task?
Custom tasks are essential in any workflow engine, and Wexflow is no exception.
They allow you to extend the capabilities of Wexflow by integrating your own logic, operations, or external system interactions directly into workflows. Whether you're calling an API, processing files, querying a database, sending notifications, or triggering other services, custom tasks give you the flexibility to automate and orchestrate exactly what your application needs.
In Wexflow, tasks are modular building blocks. While Wexflow includes many built-in tasks, custom tasks empower you to build domain-specific solutions tailored to your environment or business logic.
By creating your own task class that inherits from Wexflow.Core.Task
, you can plug into Wexflow's engine and leverage features such as:
- Configuration through XML settings
- File and entity sharing across tasks
- Cross-task communication via shared memory
- Logging and debugging integration
- Suspend/resume/stop support
- Flow control using conditions and switch values
This guide walks you through everything you need to know to build, register, and run your own custom tasks in Wexflow.
To create a custom task—for example, MyTask
—follow these steps:
-
Create a Class Library Project
-
For the legacy version (Wexflow on .NET Framework 4.8):
Create a new Class Library (.NET Framework) project in Visual Studio and name itWexflow.Tasks.MyTask
.
Make sure the target framework is set to .NET Framework 4.8. -
For the stable version (Wexflow on .NET 8.0+):
Use the .NET CLI to create the project:dotnet new classlib -n Wexflow.Tasks.MyTask
Then edit the
.csproj
file to target .NET 8.0 or later:<TargetFramework>net8.0</TargetFramework>
-
-
Add the Wexflow NuGet Package
You can add the Wexflow package using the NuGet Package Manager in Visual Studio:Install-Package Wexflow
Or via the CLI:
dotnet add package Wexflow
Important: The project name must start with Wexflow.Tasks.
and the output DLL file must also begin with Wexflow.Tasks.
for Wexflow to recognize and load it.
To define your own task, inherit from the Task
class and override either RunAsync
(asynchronous) orRun
(synchronous).
If you want to use async/await
functionality, override RunAsync
instead of Run
. Here's a simple example of a custom task:
using System;
using System.Xml.Linq;
using Wexflow.Core;
using Task = Wexflow.Core.Task;
using TaskStatus = Wexflow.Core.TaskStatus;
namespace Wexflow.Tasks.MyTask
{
public class MyTask : Task
{
public MyTask(XElement xe, Workflow wf) : base(xe, wf)
{
// Initialize task settings from the XML element if needed.
// Example: string settingValue = GetSetting("mySetting");
}
public async override System.Threading.Tasks.Task<TaskStatus> RunAsync()
{
try
{
// Check for workflow cancellation at the start of execution.
// Always include this check in any long-running or looped logic.
Workflow.CancellationTokenSource.Token.ThrowIfCancellationRequested();
// Main task logic goes here.
Info("Running my custom task...");
// Simulate work using asynchronous delay.
await System.Threading.Tasks.Task.Delay(2000);
// Support workflow suspension. This call will block if the workflow is paused.
// Only call WaitOne if cancellation hasn't already been requested.
if (!Workflow.CancellationTokenSource.Token.IsCancellationRequested)
{
WaitOne();
}
// Return success when the task completes successfully
return new TaskStatus(Status.Success);
}
catch (OperationCanceledException)
{
// Don't suppress this exception; it allows proper workflow stop handling.
throw;
}
catch (Exception ex)
{
// Log unexpected errors and return error status.
ErrorFormat("An error occurred while executing the task.", ex);
return new TaskStatus(Status.Error);
}
}
}
}
If you don't need async/await
functionality, you can use the synchronous Run
method instead. Here's how the same task would look using Run
:
using System;
using System.Threading;
using System.Xml.Linq;
using Wexflow.Core;
using Task = Wexflow.Core.Task;
using TaskStatus = Wexflow.Core.TaskStatus;
namespace Wexflow.Tasks.MyTask
{
public class MyTask : Task
{
public MyTask(XElement xe, Workflow wf) : base(xe, wf)
{
// Initialize task settings from the XML element if needed
// Example: string settingValue = GetSetting("mySetting");
}
public override TaskStatus Run()
{
try
{
// Check for workflow cancellation at the start of execution.
// Always include this check in any long-running or looped logic.
// Required for .NET 8.0+ stable version.
Workflow.CancellationTokenSource.Token.ThrowIfCancellationRequested();
// Main task logic goes here.
Info("Running my custom task...");
// WaitOne() enables suspend/resume support in .NET 8.0+.
// Call this to pause the task when the workflow is suspended.
// Only call WaitOne if cancellation hasn't already been requested.
if (!Workflow.CancellationTokenSource.Token.IsCancellationRequested)
{
WaitOne();
}
// Return success when the task completes successfully
return new TaskStatus(Status.Success);
}
catch (ThreadInterruptedException)
{
// Required for .NET 4.8 legacy version.
// Don't suppress this exception; it allows proper workflow stop handling.
throw;
}
catch (OperationCanceledException)
{
// Required for .NET 8.0+ stable version.
// Don't suppress this exception; it allows proper workflow stop handling.
throw;
}
catch (Exception ex)
{
// Log unexpected errors and return error status.
ErrorFormat("An error occurred while executing the task.", ex);
return new TaskStatus(Status.Error);
}
}
}
}
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 (If
and While
). 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");
bool settingValue = this.GetSettingBool("settingName", defaultValue);
int settingValue = this.GetSettingInt("settingName", defaultValue);
int[] settingValues = this.GetSettingsInt("settingName", defaultValue);
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);
-
For .NET Framework 4.8 (Legacy), you can find a complete example of a custom task here:
Wexflow.Tasks.Template (.NET 4.8) -
For .NET 8.0+ (Stable), check out the full example here:
Wexflow.Tasks.Template (.NET 8.0+)
Once you've finished coding your custom task, compile the class library project and copy the Wexflow.Tasks.MyTask.dll
assembly into one of the following folders:
C:\Program Files\Wexflow\
-
C:\Wexflow\Tasks\
(default for Wexflow .NET 4.8 version)
The Tasks
folder path can be configured via the tasksFolder
setting in the configuration file: C:\Wexflow\Wexflow.xml
Important: The namespace and DLL filename of your task must start with Wexflow.Tasks
.
If you're using the .NET 8.0+ version of Wexflow, copy Wexflow.Tasks.MyTask.dll
to the appropriate platform-specific folder:
-
Windows:
.\Wexflow.Server
C:\Wexflow-netcore\Tasks
-
Linux:
/opt/wexflow/Wexflow.Server
/opt/wexflow/Wexflow/Tasks
-
macOS:
/Applications/wexflow/Wexflow.Server
/Applications/wexflow/Wexflow/Tasks
If your custom task depends on additional assemblies (DLLs), copy them as follows:
-
.NET 4.8:
C:\Program Files\Wexflow\
-
.NET 8.0+:
-
Windows:
.\Wexflow.Server
orC:\Wexflow-netcore\Tasks
-
Linux:
/opt/wexflow/Wexflow.Server
or/opt/wexflow/Wexflow/Tasks
-
macOS:
/Applications/wexflow/Wexflow.Server
or/Applications/wexflow/Wexflow/Tasks
-
Windows:
To update an existing custom task:
- Stop Wexflow Server
- Replace the old DLL and its referenced assemblies with the new versions in the correct folder.
- Start Wexflow Server:
- .NET 4.8: Start the Wexflow Windows Service
-
.NET 8.0+:
-
Windows: Run
.\run.bat
or start Wexflow Service if you installed it as a Windows Service -
Linux: Run
sudo systemctl start wexflow
-
macOS: Run
dotnet /Applications/wexflow/Wexflow.Server/Wexflow.Server.dll
-
Windows: Run
Once installed, your task can be used in workflows like this:
<Task id="$int" name="MyTask" description="My task description" enabled="true">
<Setting name="settingName" value="settingValue" />
</Task>
Important: Make sure the name
attribute matches the class name of your task (e.g., MyTask
).
You can also define settings for your task using the <Setting>
elements, which can be accessed in your task code via:
string settingValue = this.GetSetting("settingName");
string settingValue = this.GetSetting("settingName", defaultValue);
string[] settingValues = this.GetSettings("settingName");
bool settingValue = this.GetSettingBool("settingName", defaultValue);
int settingValue = this.GetSettingInt("settingName", defaultValue);
int[] settingValues = this.GetSettingsInt("settingName", defaultValue);
You can then test your custom task by creating a new workflow using either the Designer or the XML editor.
Example using the XML editor:
<Workflow xmlns="urn:wexflow-schema" id="99" name="Workflow_MyWorkflow" description="Workflow_MyWorkflow">
<Settings>
<Setting name="launchType" value="trigger" />
<Setting name="enabled" value="true" />
</Settings>
<Tasks>
<Task id="1" name="MyTask" description="My task description" enabled="true">
<Setting name="settingName" value="settingValue" />
</Task>
</Tasks>
</Workflow>
This workflow will appear in the Wexflow Manager. You can launch and monitor it from there.
That's it! You're now ready to create, install, and run your own custom tasks in Wexflow.
For .NET 8.0+, if you want to enable suspend/resume for your custom task you need to use this.WaitOne();
in your custom task. Here is an example:
using System;
using System.Xml.Linq;
using Wexflow.Core;
namespace Wexflow.Tasks.MyTask
{
public class MyTask : Task
{
public MyTask(XElement xe, Workflow wf) : base(xe, wf)
{
// Initialize task settings from the XML element if needed.
// Example: string settingValue = GetSetting("mySetting");
}
public async override System.Threading.Tasks.Task<TaskStatus> RunAsync()
{
try
{
// Check for workflow cancellation at the start of execution.
// Always include this check in any long-running or looped logic.
Workflow.CancellationTokenSource.Token.ThrowIfCancellationRequested();
// Main task logic goes here.
Info("Running my custom task...");
// Simulate work using asynchronous delay.
await System.Threading.Tasks.Task.Delay(2000);
// Support workflow suspension. This call will block if the workflow is paused.
// Only call WaitOne if cancellation hasn't already been requested.
if (!Workflow.CancellationTokenSource.Token.IsCancellationRequested)
{
WaitOne();
}
// Return success when the task completes successfully
return new TaskStatus(Status.Success);
}
catch (OperationCanceledException)
{
// Don't suppress this exception; it allows proper workflow stop handling.
throw;
}
catch (Exception ex)
{
// Log unexpected errors and return error status.
ErrorFormat("An error occurred while executing the task.", ex);
return new TaskStatus(Status.Error);
}
}
}
}
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 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();
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.
Tasks contains a Hashtable
that can be used as a shared memory between them.
To add an object to the SharedMemory
, simply proceed as follows:
this.SharedMemory.Add("myKey", myObject);
To retrieve an object from the SharedMemory
, simply proceed as follows:
var myObject = this.SharedMemory["myKey"];
To remove an object from the SharedMemory
, simply proceed as follows:
this.SharedMemory.Remove("myKey");
Important: Always access this.SharedMemory
in RunAsync
or Run
method of a task to get updated values at runtime.
To make your custom task MyTask
appear in the available tasks in the designer, simply open the file C:\Wexflow\TasksNames.json
and add MyTask
in it as follows:
[
...
{ "Name": "MyTask", "Description": "MyTask description."},
]
If you use the .NET 8.0+ version of Wexflow, you need to edit this file:
-
Windows:
C:\Wexflow-netcore\TasksNames.json
-
Linux:
/opt/wexflow/Wexflow/TasksNames.json
-
macOS:
/Applications/wexflow/Wexflow/TasksNames.json
You must also add the settings by opening the file C:\Wexflow\TasksSettings.json
and adding your custom settings as follows:
{
...
"MyTask": [ {"Name": "settingName", "Required": true, "Type": "string", "List": [], "DefaultValue": ""} ],
}
If you use the .NET 8.0+ version of Wexflow, you need to edit this file:
-
Windows:
C:\Wexflow-netcore\TasksSettings.json
-
Linux:
/opt/wexflow/Wexflow/TasksSettings.json
-
macOS:
/Applications/wexflow/Wexflow/TasksSettings.json
The available types are:
string
int
bool
password
list
user
record
user
type refers to registered users in Wexflow.
record
type refers to registered records in Wexflow.
If you choose list
type, you have to set the available list options. Here is an example:
{
...
"MyTask": [ {"Name": "protocol", "Required": true, "Type": "list", "List": ["ftp", "ftps", "sftp"], "DefaultValue": ""} ],
}
That's it. MyTask
will show up in the designer and when selected its settings will show up as well.
- Compile your custom task project with Debug configuration.
- Make sure the .pdb symbol files are generated alongside the .dll.
- Copy both .dll and .pdb files to the Wexflow Tasks folder (e.g. C:\Wexflow-netcore\Tasks).
- Launch Wexflow server.
- In Visual Studio, go to Debug → Attach to Process...
- Find and select the Wexflow process:
- Enable Show processes from all users
- If you're running Wexflow as a Windows Service (legacy), make sure the service is started and select:
Wexflow.Server.exe
- If you're running Wexflow as a Windows Service (stable via NSSM), make sure the service is started and select:
dotnet.exe
process that's runningWexflow.Server.dll
. - If you're running Wexflow from the command prompt using
dotnet Wexflow.Server.dll
, Find and select thedotnet.exe
process that's runningWexflow.Server.dll
.
- Click Attach.
- After attaching, open Modules window (Debug → Windows → Modules).
- Locate your custom DLL (e.g. Wexflow.Tasks.MyTask.dll).
- Check if symbols are loaded:
- If not loaded, right-click the module → Load Symbols and browse to your .pdb file.
- Confirm the symbol path is correct and matches your DLL.
- Open your custom task source code in Visual Studio as Admin.
- Set breakpoints inside the
RunAsync
orRun
methods or anywhere you want to debug.
- Run the workflow that uses your custom task (via Designer or API).
- When execution reaches your breakpoint, Visual Studio will break.
- Make sure your DLL in the Tasks folder is the same version you built and debugged.
- If debugging symbols don’t load automatically, try deleting older DLLs and PDBs before copying new ones.
- You can enable detailed logging inside your task with
Info()
,Debug()
, andError()
calls for extra insights.
Copyright © Akram El Assas. All rights reserved.
- Install Guide
- HTTPS/SSL
- Screenshots
- Docker
- Configuration Guide
- Persistence Providers
- Getting Started
- Android App
- Local Variables
- Global Variables
- REST Variables
- Functions
- Cron Scheduling
- Command Line Interface (CLI)
- REST API Reference
- Samples
- Logging
- Custom Tasks
-
Built-in Tasks
- File system tasks
- Encryption tasks
- Compression tasks
- Iso tasks
- Speech tasks
- Hashing tasks
- Process tasks
- Network tasks
- XML tasks
- SQL tasks
- WMI tasks
- Image tasks
- Audio and video tasks
- Email tasks
- Workflow tasks
- Social media tasks
- Waitable tasks
- Reporting tasks
- Web tasks
- Script tasks
- JSON and YAML tasks
- Entities tasks
- Flowchart tasks
- Approval tasks
- Notification tasks
- SMS tasks
- Run from Source
- Fork, Customize, and Sync