WGC.WorkflowEngine.Core 1.0.0

dotnet add package WGC.WorkflowEngine.Core --version 1.0.0
                    
NuGet\Install-Package WGC.WorkflowEngine.Core -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="WGC.WorkflowEngine.Core" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="WGC.WorkflowEngine.Core" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="WGC.WorkflowEngine.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add WGC.WorkflowEngine.Core --version 1.0.0
                    
#r "nuget: WGC.WorkflowEngine.Core, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package WGC.WorkflowEngine.Core@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=WGC.WorkflowEngine.Core&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=WGC.WorkflowEngine.Core&version=1.0.0
                    
Install as a Cake Tool

WGC.WorkflowEngine

A lightweight, durable workflow engine for .NET 8. Define state-machine workflows with a fluent API, persist them with Entity Framework Core, and run them with built-in retry logic, timeout handling, and human override capabilities.

Features

  • Fluent Workflow Definition - Define states, transitions, and steps in clean C# code
  • Durable State Persistence - EF Core code-first with full migration support
  • Database Agnostic - SQL Server, PostgreSQL, MongoDB, and Redis providers included; extensible to any EF Core provider
  • Event-Driven Transitions - Trigger state changes from external events
  • Time-Based Transitions - Automatic timeouts that move workflows to a target state
  • Transition Guards - Conditional transitions with custom guard logic
  • Workflow Hooks - React to state changes, step completion, and workflow lifecycle events
  • Step Execution - Run custom logic on state entry/exit with dependency injection
  • Retry with Exponential Backoff - Failed steps are automatically retried
  • Concurrency Safe - Atomic step claiming prevents duplicate execution by parallel processors
  • Unit of Work - All state transitions are committed in a single atomic transaction
  • Transactional Outbox - Reliable outbound messaging pattern built in
  • Human Overrides - Skip steps, retry failed steps, force state transitions, suspend/resume workflows
  • Query Builder - Fluent query API for dashboards with filtering, sorting, and pagination
  • Health Checks - Monitor workflow engine health (stuck steps, failed steps, connectivity)
  • Full Audit Trail - Every action is logged for traceability
  • Dead Letter Queue - Failed messages are captured for manual review

Installation

# For SQL Server
dotnet add package WGC.WorkflowEngine.SqlServer

# For PostgreSQL
dotnet add package WGC.WorkflowEngine.PostgreSQL

# For MongoDB
dotnet add package WGC.WorkflowEngine.MongoDB

# For Redis
dotnet add package WGC.WorkflowEngine.Redis

Quick Start

1. Define a Workflow

using WGC.WorkflowEngine.Core.Builders;
using WGC.WorkflowEngine.Core.Interfaces;

public class OrderWorkflow : WorkflowDefinition
{
    public override string Name => "OrderProcessing";

    public override void Define(IWorkflowBuilder builder)
    {
        builder.InitialState("Pending");

        builder.State("Pending")
            .On("PaymentReceived").TransitionTo("Processing")
            .On("Cancelled").TransitionTo("Cancelled")
            .OnTimeout(TimeSpan.FromDays(3), "Expired")
            .OnEnter(step => step.Execute<SendConfirmationEmailStep>());

        builder.State("Processing")
            .On("Shipped").TransitionTo("Shipped")
            .OnEnter(step => step.Execute<NotifyWarehouseStep>());

        builder.State("Shipped")
            .On("Delivered").TransitionTo("Completed")
            .OnEnter(step => step.Execute<SendTrackingEmailStep>());

        builder.State("Completed").AsFinal();
        builder.State("Cancelled").AsFinal().AllowReopen("Pending");
        builder.State("Expired").AsFinal();
    }
}

2. Implement Step Handlers

using WGC.WorkflowEngine.Core.Interfaces;

public class SendConfirmationEmailStep : IStepHandler
{
    private readonly IEmailService _emailService;

    public SendConfirmationEmailStep(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public async Task<StepResult> HandleAsync(StepExecutionContext context, CancellationToken ct)
    {
        var orderId = context.WorkflowInstance.CorrelationId;

        await _emailService.SendAsync(orderId, "Your order has been received!");

        return StepResult.Completed();
    }
}

3. Register Services

// Program.cs
builder.Services
    .AddWorkflowEngine(options =>
    {
        options.AddWorkflow<OrderWorkflow>();
        options.AddStepHandler<SendConfirmationEmailStep>();
        options.AddStepHandler<NotifyWarehouseStep>();
        options.AddStepHandler<SendTrackingEmailStep>();
    })
    .UseWorkflowSqlServer(connectionString);
    // or .UseWorkflowPostgreSql(connectionString);

4. Apply Migrations

dotnet ef migrations add InitWorkflow --context WorkflowDbContext
dotnet ef database update --context WorkflowDbContext

5. Use the Engine

public class OrderController : ControllerBase
{
    private readonly IWorkflowEngine _engine;

    public OrderController(IWorkflowEngine engine) => _engine = engine;

    [HttpPost]
    public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
    {
        // Start a new workflow
        var instance = await _engine.StartAsync(
            workflowName: "OrderProcessing",
            correlationId: request.OrderId,
            data: new { request.CustomerEmail, request.Items },
            triggeredBy: User.Identity?.Name);

        return Ok(new { WorkflowId = instance.Id });
    }

    [HttpPost("{id}/pay")]
    public async Task<IActionResult> ProcessPayment(Guid id)
    {
        // Trigger a state transition
        var instance = await _engine.TriggerEventAsync(id, "PaymentReceived");
        return Ok(new { State = instance.CurrentState });
    }
}

Transition Guards

Add conditions to transitions that must pass before a state change is allowed:

// Define a guard
public class HasQuoteGuard : ITransitionGuard
{
    public Task<GuardResult> EvaluateAsync(TransitionContext context, CancellationToken ct)
    {
        var hasQuote = context.WorkflowData.ContainsKey("QuoteId");
        return Task.FromResult(hasQuote
            ? GuardResult.Allow()
            : GuardResult.Deny("No quote has been created yet."));
    }
}

// Use in workflow definition
builder.State("Opportunity")
    .On("QuoteCreated")
        .WithGuard<HasQuoteGuard>()
        .TransitionTo("Quote");

// Register the guard
options.AddGuard<HasQuoteGuard>();

Workflow Hooks

React to workflow lifecycle events (send emails, trigger integrations, update external systems):

public class SalesNotificationHook : IWorkflowHook
{
    private readonly IEmailService _email;

    public SalesNotificationHook(IEmailService email) => _email = email;

    public async Task OnStateEnteredAsync(WorkflowInstance instance, string state, CancellationToken ct)
    {
        if (state == "Lead")
            await _email.SendAsync("sales@company.com", $"New lead: {instance.CorrelationId}");
    }

    public async Task OnWorkflowCompletedAsync(WorkflowInstance instance, CancellationToken ct)
    {
        await _email.SendAsync("manager@company.com", $"Workflow {instance.Id} completed.");
    }
}

// Register
options.AddHook<SalesNotificationHook>();

Available hook methods (all optional):

  • OnWorkflowStartedAsync - Workflow created
  • OnStateEnteredAsync - After transitioning to a new state
  • OnStateExitingAsync - Before leaving the current state
  • OnWorkflowCompletedAsync - Workflow reached a final state
  • OnStepCompletedAsync - A step finished successfully
  • OnStepFailedAsync - A step permanently failed (no more retries)

Query Builder (Dashboards)

Query workflow instances with a fluent API for dashboards and reporting:

// Inject IWorkflowQueryService
private readonly IWorkflowQueryService _queryService;

// Paginated, filtered query
var result = await _queryService.QueryAsync(q => q
    .ForWorkflow("SalesLifecycle")
    .InStatus(WorkflowStatus.Active)
    .InState("Quote")
    .CreatedAfter(DateTime.UtcNow.AddDays(-30))
    .OrderBy(WorkflowSortField.UpdatedAt, descending: true)
    .Page(1, 20));

// result.Items         -> List<WorkflowInstance>
// result.TotalCount    -> Total matching records
// result.TotalPages    -> Total pages
// result.HasNextPage   -> Pagination helper

// Dashboard summary (counts by status and state)
var summary = await _queryService.GetSummaryAsync("SalesLifecycle");
// summary.CountByStatus  -> { "Active": 42, "Completed": 100, ... }
// summary.CountByState   -> { "Contact": 10, "Lead": 15, "Quote": 8, ... }

Health Checks

Monitor the workflow engine in production:

// Program.cs
builder.Services.AddHealthChecks()
    .AddWorkflowEngineHealthCheck(options =>
    {
        options.StuckStepThreshold = TimeSpan.FromMinutes(30);
        options.MaxStuckStepsBeforeUnhealthy = 5;
        options.MaxFailedStepsBeforeDegraded = 10;
    });

Reports:

  • Healthy - Engine operating normally
  • Degraded - Stuck or permanently failed steps detected
  • Unhealthy - Database unreachable or too many stuck steps

Background Processing

The engine requires periodic background processing for scheduled steps, retries, and outbox messages. Integrate with your preferred scheduler:

// Example with a hosted service
public class WorkflowBackgroundService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    public WorkflowBackgroundService(IServiceProvider serviceProvider)
        => _serviceProvider = serviceProvider;

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            using var scope = _serviceProvider.CreateScope();

            var stepProcessor = scope.ServiceProvider.GetRequiredService<IStepProcessor>();
            await stepProcessor.ProcessPendingStepsAsync(ct: ct);
            await stepProcessor.ProcessScheduledStepsAsync(ct: ct);
            await stepProcessor.ProcessRetryStepsAsync(ct: ct);

            var outboxProcessor = scope.ServiceProvider.GetRequiredService<IOutboxProcessor>();
            await outboxProcessor.ProcessBatchAsync(ct: ct);

            await Task.Delay(TimeSpan.FromSeconds(10), ct);
        }
    }
}

Human Override Operations

All operations are logged in the audit trail:

// Force a state transition (bypass normal rules and guards)
await engine.ForceStateAsync(instanceId, "Processing", "Manager approved skip", "admin@company.com");

// Suspend a workflow
await engine.SuspendAsync(instanceId, "Waiting for customer response", "agent@company.com");

// Resume a suspended workflow
await engine.ResumeAsync(instanceId, "agent@company.com");

// Cancel a workflow
await engine.CancelAsync(instanceId, "Customer requested cancellation", "agent@company.com");

// Retry a failed step
await engine.RetryStepAsync(instanceId, stepId, "admin@company.com");

// Skip a step
await engine.SkipStepAsync(instanceId, stepId, "Not applicable", "admin@company.com");

Transactional Outbox

Send reliable messages from your step handlers:

public class NotifyWarehouseStep : IStepHandler
{
    private readonly IOutboxRepository _outbox;

    public NotifyWarehouseStep(IOutboxRepository outbox) => _outbox = outbox;

    public async Task<StepResult> HandleAsync(StepExecutionContext context, CancellationToken ct)
    {
        // Message is persisted in the same transaction as the step update
        await _outbox.CreateAsync(new OutboxMessage
        {
            WorkflowInstanceId = context.WorkflowInstance.Id,
            MessageType = "WarehouseNotification",
            Payload = JsonSerializer.Serialize(new { OrderId = context.WorkflowInstance.CorrelationId }),
            Destination = "warehouse-queue"
        }, ct);

        return StepResult.Completed();
    }
}

Embedded Dashboard

The dashboard provides a web UI for monitoring and managing workflows, accessible at a configurable route prefix (like Hangfire Dashboard).

Features

  • Overview - Status cards (active, completed, faulted, etc.), per-workflow summaries, recent transitions, stuck/failed step alerts
  • Instances - Filterable list with pagination, detail view with data, steps, event log timeline, and action buttons (force state, suspend, resume, cancel)
  • Definitions - View all registered workflows (code-first and database), read-only view for code-first, form-based editor for database definitions
  • Definition Editor - Create/edit workflow definitions stored in the database with states, transitions, guards, timeouts, and steps — all through a visual form

Setup

// Program.cs
builder.Services
    .AddWorkflowEngine(options =>
    {
        options.AddWorkflow<SalesWorkflow>(); // code-first still works
    })
    .UseWorkflowSqlServer(connectionString);

// Add the dashboard (configurable route prefix, optional auth policy)
builder.Services.AddWorkflowDashboard(options =>
{
    options.RoutePrefix = "/workflowengine";     // default
    options.AuthorizationPolicy = "AdminOnly";   // optional
    options.Title = "My Workflow Dashboard";      // optional
});

// Middleware
app.UseStaticFiles(); // required for dashboard CSS/JS
app.MapWorkflowDashboard(); // maps Razor Pages + API endpoints

Dashboard Pages

Page Route Description
Overview {prefix} Health status, counters, workflow summaries, recent transitions
Instances {prefix}/instances Filterable, paginated list of workflow instances
Instance Detail {prefix}/instances/{id} Full detail with data, steps, event log, actions
Definitions {prefix}/definitions List all workflows with source (Code/Database) badge
Definition View {prefix}/definitions/view/{name} Read-only view of states and transitions
Definition Editor {prefix}/definitions/editor/{id?} Form-based editor for database definitions

Database Definitions

Workflow definitions can be created via the dashboard form editor and stored in the database alongside code-first definitions. Both sources coexist — code-first definitions cannot be overwritten from the dashboard.

Dashboard API

All dashboard data is served via a REST API at {prefix}/api/:

Endpoint Method Description
/api/overview GET Dashboard overview data
/api/recent-transitions GET Recent event log entries
/api/instances GET Paginated, filtered instances
/api/instances/{id} GET Instance detail
/api/instances/{id}/force-state POST Force a state transition
/api/instances/{id}/suspend POST Suspend a workflow
/api/instances/{id}/resume POST Resume a suspended workflow
/api/instances/{id}/cancel POST Cancel a workflow
/api/definitions GET All definitions
/api/definitions/{id} GET Get definition by ID
/api/definitions/by-name/{name} GET Get definition config by name
/api/definitions POST Create a new database definition
/api/definitions/{id} PUT Update a database definition
/api/definitions/{id} DELETE Deactivate a database definition
/api/definitions/validate POST Validate a definition without saving

Project Structure

WGC.WorkflowEngine/
├── src/
│   ├── WGC.WorkflowEngine.Core/          # Core engine, entities, interfaces, builders
│   ├── WGC.WorkflowEngine.Persistence/   # EF Core DbContext, configurations, repositories
│   ├── WGC.WorkflowEngine.SqlServer/     # SQL Server provider
│   ├── WGC.WorkflowEngine.PostgreSQL/    # PostgreSQL provider
│   ├── WGC.WorkflowEngine.MongoDB/       # MongoDB provider
│   ├── WGC.WorkflowEngine.Redis/         # Redis provider
│   └── WGC.WorkflowEngine.Dashboard/     # Embedded web dashboard (Razor Class Library)
├── tests/
│   └── WGC.WorkflowEngine.Tests/         # Unit and integration tests
├── samples/
│   ├── Sample.Sql/                       # SQL Server sample app
│   ├── Sample.PostgreSQL/                # PostgreSQL sample app
│   ├── Sample.MongoDB/                   # MongoDB sample app
│   ├── Sample.Redis/                     # Redis sample app
│   ├── Sample.SqlFromNuget/              # Sample using NuGet packages
│   └── Sample.Shared/                    # Shared workflows, seeds, and step handlers
├── WGC.WorkflowEngine.sln
├── README.md
└── LICENSE

Database Tables

The engine creates the following tables (via EF Core migrations):

Table Description
WorkflowInstances Active and completed workflow instances
WorkflowSteps Individual steps with execution status
WorkflowEventLogs Full audit trail of every action
OutboxMessages Transactional outbox for reliable messaging
WorkflowDefinitions Database-stored workflow definitions (JSON)

Requirements

  • .NET 8.0 or later
  • One of: SQL Server 2016+, PostgreSQL 12+, MongoDB 5.0+, or Redis 6.0+

License

MIT License - see LICENSE for details.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on WGC.WorkflowEngine.Core:

Package Downloads
WGC.WorkflowEngine.Persistence

EF Core persistence layer for WGC.WorkflowEngine — database context, repositories, and migrations.

WGC.WorkflowEngine.MongoDB

MongoDB persistence provider for WGC.WorkflowEngine.

WGC.WorkflowEngine.Redis

Redis persistence provider for WGC.WorkflowEngine.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 196 5/21/2026