Hosting Elsa in an Existing App

Step-by-step guide to integrating Elsa Workflows into an existing ASP.NET Core application, including persistence setup, common pain points, and troubleshooting.

This guide walks you through adding Elsa Workflows to an existing ASP.NET Core application. Whether you're building a new feature or modernizing an existing codebase, this guide will help you integrate Elsa smoothly while avoiding common pitfalls.

Overview

Integrating Elsa into an existing ASP.NET Core app involves:

  1. Installing required NuGet packages

  2. Configuring Elsa services in Program.cs

  3. Setting up persistence (database)

  4. Addressing common integration challenges

This guide focuses on practical integration patterns and addresses common issues reported by the community (issue #6).

Prerequisites

Before you begin, ensure you have:

  • An existing ASP.NET Core application (.NET 6.0+, .NET 8.0 recommended)

  • Basic understanding of ASP.NET Core dependency injection

  • A database server (PostgreSQL, SQL Server, SQLite, or MySQL)

  • Visual Studio 2022+, Visual Studio Code, or Rider

Step 1: Install Elsa Packages

Add the core Elsa packages to your project. The exact packages depend on your needs:

Basic Workflow Runtime

For workflow execution without a UI:

dotnet add package Elsa
dotnet add package Elsa.Workflows.Runtime
dotnet add package Elsa.Workflows.Api

With Entity Framework Core Persistence

For PostgreSQL:

dotnet add package Elsa.EntityFrameworkCore.PostgreSql

For SQL Server:

dotnet add package Elsa.EntityFrameworkCore.SqlServer

For SQLite (development only):

dotnet add package Elsa.EntityFrameworkCore.Sqlite

Optional: HTTP Activities

If your workflows need HTTP endpoints:

dotnet add package Elsa.Http

Optional: Elsa Studio (Web UI)

To include the workflow designer UI in your app:

dotnet add package Elsa.Studio
dotnet add package Elsa.Studio.Core.BlazorWasm

Step 2: Configure Elsa in Program.cs

Add Elsa to your existing Program.cs configuration. Here's a complete example showing integration with an existing app:

Basic Configuration

using Elsa.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Your existing services
builder.Services.AddControllers();
builder.Services.AddRazorPages();
// ... other services ...

// Add Elsa services
builder.Services.AddElsa(elsa =>
{
    // Configure workflow management (designer, definitions)
    elsa.UseWorkflowManagement();
    
    // Configure workflow runtime (execution engine)
    elsa.UseWorkflowRuntime(runtime =>
    {
        runtime.WorkflowInboxCleanupOptions = new()
        {
            // Clean up completed workflow instances after 30 days
            BatchSize = 100,
            SweepInterval = TimeSpan.FromMinutes(60)
        };
    });
    
    // Expose workflows via REST API
    elsa.UseWorkflowsApi();
    
    // Add HTTP activities for workflow endpoints
    elsa.UseHttp();
});

var app = builder.Build();

// Your existing middleware
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

// Map Elsa workflows API endpoints
app.UseWorkflowsApi();

// Your existing endpoints
app.MapControllers();
app.MapRazorPages();

app.Run();

With Persistence (PostgreSQL Example)

using Elsa.EntityFrameworkCore.Extensions;
using Elsa.EntityFrameworkCore.Modules.Management;
using Elsa.EntityFrameworkCore.Modules.Runtime;
using Elsa.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Your existing services
builder.Services.AddControllers();

// Add Elsa with PostgreSQL persistence
builder.Services.AddElsa(elsa =>
{
    // Use PostgreSQL for workflow definitions and instances
    elsa.UseWorkflowManagement(management =>
    {
        management.UseEntityFrameworkCore(ef =>
        {
            ef.UsePostgreSql(builder.Configuration.GetConnectionString("ElsaDatabase"));
        });
    });
    
    elsa.UseWorkflowRuntime(runtime =>
    {
        runtime.UseEntityFrameworkCore(ef =>
        {
            ef.UsePostgreSql(builder.Configuration.GetConnectionString("ElsaDatabase"));
        });
    });
    
    elsa.UseWorkflowsApi();
    elsa.UseHttp();
});

var app = builder.Build();

// Run EF Core migrations on startup (development only)
// For production, run migrations separately
if (app.Environment.IsDevelopment())
{
    await app.Services.MigrateElsaDatabaseAsync();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseWorkflowsApi();
app.MapControllers();

app.Run();

Connection String (appsettings.json):

{
  "ConnectionStrings": {
    "ElsaDatabase": "Host=localhost;Database=elsa_workflows;Username=elsa;Password=your_secure_password"
  }
}

With SQL Server

elsa.UseWorkflowManagement(management =>
{
    management.UseEntityFrameworkCore(ef =>
    {
        ef.UseSqlServer(builder.Configuration.GetConnectionString("ElsaDatabase"));
    });
});

elsa.UseWorkflowRuntime(runtime =>
{
    runtime.UseEntityFrameworkCore(ef =>
    {
        ef.UseSqlServer(builder.Configuration.GetConnectionString("ElsaDatabase"));
    });
});

SQL Server Connection String:

{
  "ConnectionStrings": {
    "ElsaDatabase": "Server=localhost;Database=ElsaWorkflows;User Id=elsa_user;Password=your_secure_password;TrustServerCertificate=true"
  }
}

Step 3: Initialize Database

After configuring persistence, you need to create the database schema.

Option A: Auto-Migration (Development)

For development environments, you can auto-migrate on startup:

if (app.Environment.IsDevelopment())
{
    await app.Services.MigrateElsaDatabaseAsync();
}

This creates/updates the database schema automatically when the app starts.

Option B: Manual Migration (Production)

For production, run migrations separately using the EF Core CLI:

# Install EF Core tools if not already installed
dotnet tool install --global dotnet-ef

# Add a migration
dotnet ef migrations add InitialElsa --context ManagementElsaDbContext
dotnet ef migrations add InitialElsaRuntime --context RuntimeElsaDbContext

# Apply migrations
dotnet ef database update --context ManagementElsaDbContext
dotnet ef database update --context RuntimeElsaDbContext

Note: Elsa uses separate DbContexts for management (workflow definitions) and runtime (workflow instances). You'll need to manage migrations for both contexts.

Common Pain Points and Solutions

Based on community feedback (issue #6), here are the most common integration challenges and how to solve them:

1. DbContextOptions Registration Issue

Problem: When you have your own AppDbContext that requires DbContextOptions<AppDbContext>, you may encounter conflicts with Elsa's internal DbContext registration.

Symptoms:

System.InvalidOperationException: Unable to resolve service for type 
'Microsoft.EntityFrameworkCore.DbContextOptions`1[YourApp.Data.AppDbContext]' 
while attempting to activate 'YourApp.Data.AppDbContext'.

Solution:

Explicitly register your AppDbContext with its own connection string and options:

// Register your own DbContext first
builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseNpgsql(builder.Configuration.GetConnectionString("AppDatabase"));
    // Your DbContext configuration
});

// Then register Elsa with a different connection string
builder.Services.AddElsa(elsa =>
{
    elsa.UseWorkflowManagement(management =>
    {
        management.UseEntityFrameworkCore(ef =>
        {
            // Use separate connection string for Elsa
            ef.UsePostgreSql(builder.Configuration.GetConnectionString("ElsaDatabase"));
        });
    });
    
    elsa.UseWorkflowRuntime(runtime =>
    {
        runtime.UseEntityFrameworkCore(ef =>
        {
            ef.UsePostgreSql(builder.Configuration.GetConnectionString("ElsaDatabase"));
        });
    });
});

Key Points:

  • Use separate databases or schemas for Elsa and your app

  • Elsa's DbContexts are registered with specific lifetimes - don't try to share them

  • If you must share a database, use different connection strings with schema prefixes

2. Version Pinning Conflicts

Problem: Elsa depends on specific versions of packages like Hangfire, Microsoft.EntityFrameworkCore.Design, or Microsoft.CodeAnalysis.*, which may conflict with your existing dependencies.

Symptoms:

NU1605: Detected package downgrade: Microsoft.EntityFrameworkCore from 8.0.0 to 7.0.5
NU1608: Detected package version outside of dependency constraint

Solutions:

Strategy 1: Version Alignment

Align your EF Core versions with Elsa's requirements:

<ItemGroup>
  <!-- Explicitly specify EF Core version to match Elsa -->
  <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
  
  <!-- Elsa packages (replace x.y.z with the latest version from NuGet) -->
  <PackageReference Include="Elsa" Version="x.y.z" />
  <PackageReference Include="Elsa.EntityFrameworkCore.PostgreSql" Version="x.y.z" />
</ItemGroup>

Strategy 2: Binding Redirects (Framework Apps)

For .NET Framework apps, use binding redirects in web.config:

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="Microsoft.EntityFrameworkCore" publicKeyToken="adb9793829ddae60" />
      <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>

Strategy 3: Update Dependencies

Update your existing packages to match Elsa's requirements:

# Update all EF Core packages
dotnet add package Microsoft.EntityFrameworkCore --version 8.0.0
dotnet add package Microsoft.EntityFrameworkCore.Design --version 8.0.0
dotnet add package Microsoft.EntityFrameworkCore.Tools --version 8.0.0

# Update Hangfire if used
dotnet add package Hangfire.Core --version 1.8.0
dotnet add package Hangfire.AspNetCore --version 1.8.0

3. Swagger / Swashbuckle Schema Conflicts

Problem: When adding Swagger/Swashbuckle to document your API, you may encounter schema ID conflicts with Elsa's API endpoints.

Symptoms:

Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: 
Conflicting schemaIds: Duplicate schemaIds detected for types 
Elsa.Workflows.Core.Models.WorkflowDefinition and YourApp.Models.WorkflowDefinition

Solutions:

Solution 1: Custom Schema ID Generation

Configure Swashbuckle to generate unique schema IDs:

builder.Services.AddSwaggerGen(options =>
{
    options.CustomSchemaIds(type =>
    {
        // Include namespace to avoid conflicts
        return type.FullName?.Replace("+", ".");
    });
    
    // Or use a more sophisticated approach
    options.CustomSchemaIds(type =>
    {
        if (type.FullName?.StartsWith("Elsa") == true)
        {
            return "Elsa_" + type.Name;
        }
        return type.Name;
    });
});

Solution 2: Exclude Elsa Endpoints from Swagger

If you don't need to document Elsa's API endpoints:

builder.Services.AddSwaggerGen(options =>
{
    options.DocInclusionPredicate((docName, apiDesc) =>
    {
        // Exclude Elsa API endpoints from Swagger documentation
        var actionDescriptor = apiDesc.ActionDescriptor;
        var controllerName = actionDescriptor.RouteValues["controller"];
        
        if (controllerName?.StartsWith("Elsa") == true)
        {
            return false;
        }
        
        return true;
    });
});

Solution 3: Multiple Swagger Documents

Create separate Swagger documents for your API and Elsa:

builder.Services.AddSwaggerGen(options =>
{
    // Your API documentation
    options.SwaggerDoc("v1", new OpenApiInfo 
    { 
        Title = "My App API", 
        Version = "v1" 
    });
    
    // Elsa API documentation
    options.SwaggerDoc("elsa", new OpenApiInfo 
    { 
        Title = "Elsa Workflows API", 
        Version = "v1" 
    });
    
    options.DocInclusionPredicate((docName, apiDesc) =>
    {
        var controllerName = apiDesc.ActionDescriptor.RouteValues["controller"];
        
        if (docName == "elsa")
        {
            return controllerName?.StartsWith("Elsa") == true;
        }
        
        return controllerName?.StartsWith("Elsa") != true;
    });
});

// In the middleware pipeline
app.UseSwagger();
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/swagger/v1/swagger.json", "My App API");
    options.SwaggerEndpoint("/swagger/elsa/swagger.json", "Elsa Workflows API");
});

Authentication and Authorization

For production deployments, you'll need to secure Elsa's API endpoints. This section provides a high-level overview; see the Security & Authentication Guide for detailed configuration.

Quick Overview

Development (Disable Auth):

Production Options:

  • Elsa.Identity: Built-in identity system with user management

  • API Keys: Simple token-based authentication

  • OIDC/OAuth2: Integration with Azure AD, Auth0, Keycloak, etc.

Basic Identity Setup

builder.Services.AddElsa(elsa =>
{
    elsa
        .UseIdentity(identity =>
        {
            identity.UseConfigurationBasedIdentityProvider();
        })
        .UseDefaultAuthentication()
        .UseWorkflowManagement()
        .UseWorkflowRuntime()
        .UseWorkflowsApi();
});

// Enable authentication middleware
app.UseAuthentication();
app.UseAuthorization();

Testing Your Integration

1. Verify Elsa Services are Registered

Create a simple controller to check if Elsa is loaded:

using Elsa.Workflows.Runtime;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class HealthController : ControllerBase
{
    private readonly IWorkflowRuntime _workflowRuntime;

    public HealthController(IWorkflowRuntime workflowRuntime)
    {
        _workflowRuntime = workflowRuntime;
    }

    [HttpGet("elsa")]
    public IActionResult ElsaHealth()
    {
        return Ok(new 
        { 
            elsaLoaded = _workflowRuntime != null,
            message = "Elsa Workflows is integrated successfully"
        });
    }
}

2. Check API Endpoints

With the app running, navigate to:

  • https://localhost:5001/elsa/api/workflow-definitions - List workflow definitions

  • https://localhost:5001/swagger - Swagger UI (if configured)

3. Verify Database

Check that Elsa tables were created:

PostgreSQL:

SELECT table_name 
FROM information_schema.tables 
WHERE table_schema = 'public' 
  AND table_name LIKE 'Elsa%';

SQL Server:

SELECT TABLE_NAME 
FROM INFORMATION_SCHEMA.TABLES 
WHERE TABLE_NAME LIKE 'Elsa%';

You should see tables like:

  • Elsa_WorkflowDefinitions

  • Elsa_WorkflowInstances

  • Elsa_Bookmarks

  • And others

Next Steps

Now that Elsa is integrated into your app:

  1. Create your first workflow: Use Elsa Studio or programmatic workflow definitions

  2. Add workflow activities: Extend Elsa with Custom Activities

  3. Secure your deployment: Configure authentication and authorization

  4. Deploy to production: Follow the Kubernetes Deployment Guide or Clustering Guide

  5. Monitor workflows: Set up observability and logging

Troubleshooting

Services Not Resolving

Problem: IWorkflowRuntime or other Elsa services not found in DI.

Solution: Ensure you called AddElsa() before builder.Build():

builder.Services.AddElsa(elsa => { /* config */ });
var app = builder.Build();  // After AddElsa

Database Connection Fails

Problem: Npgsql.NpgsqlException: Connection refused

Solutions:

  • Verify database server is running

  • Check connection string format

  • Ensure firewall allows connections

  • Test connection with psql or sqlcmd

Migrations Not Applied

Problem: Tables not created after running migrations.

Solution: Ensure migrations were created for both contexts:

dotnet ef migrations add InitialElsa --context ManagementElsaDbContext
dotnet ef migrations add InitialElsaRuntime --context RuntimeElsaDbContext

Then apply both:

dotnet ef database update --context ManagementElsaDbContext
dotnet ef database update --context RuntimeElsaDbContext

Last Updated: 2025-12-02 Addresses Issues: #6

Last updated