Add new sample project for ASP.NET Hangfire Integration

Introduced a new sample project to demonstrate ASP.NET integration with Hangfire, including setup configurations in `appsettings.json`, program initialization, and a background job activity. This will serve as a reference implementation for integrating Elsa with Hangfire in ASP.NET applications.
This commit is contained in:
Sipke Schoorstra 2024-11-04 11:59:47 +01:00
parent d46d0b53bc
commit a397b6799e
10 changed files with 251 additions and 0 deletions

View file

@ -383,6 +383,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Tenants.AspNetCore", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql\Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql.csproj", "{2B939AC9-03A4-479E-AA0D-CB58F4A7F480}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{0350351C-E347-41D8-8E9C-ECF90653418B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.HangfireIntegration", "samples\Elsa.Samples.AspNet.HangfireIntegration\Elsa.Samples.AspNet.HangfireIntegration.csproj", "{FAE7B132-8AD3-44C0-B45B-1B2BA64B1DB4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -815,6 +819,10 @@ Global
{2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Release|Any CPU.Build.0 = Release|Any CPU
{FAE7B132-8AD3-44C0-B45B-1B2BA64B1DB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FAE7B132-8AD3-44C0-B45B-1B2BA64B1DB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FAE7B132-8AD3-44C0-B45B-1B2BA64B1DB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FAE7B132-8AD3-44C0-B45B-1B2BA64B1DB4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -959,6 +967,7 @@ Global
{D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C} = {2F3E1026-5054-4E1F-899B-F1A7F70F9912}
{2B939AC9-03A4-479E-AA0D-CB58F4A7F480} = {50470834-4CD8-479A-8B58-0A1869BA5D37}
{2CDF3E1C-267D-4198-B1C7-7E1F548FC120} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{FAE7B132-8AD3-44C0-B45B-1B2BA64B1DB4} = {0350351C-E347-41D8-8E9C-ECF90653418B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}

View file

@ -0,0 +1,28 @@
using Elsa.Extensions;
using Elsa.Samples.AspNet.HangfireIntegration.Jobs;
using Elsa.Samples.AspNet.HangfireIntegration.Models;
using Elsa.Samples.AspNet.HangfireIntegration.Stimuli;
using Elsa.Workflows;
using Elsa.Workflows.Attributes;
using Hangfire;
namespace Elsa.Samples.AspNet.HangfireIntegration.Activities;
[Activity("Samples", "Samples", "Enqueues a background job and resumes the workflow when the job is done.")]
public class ExecuteSomeJob : Activity<SomeJobResult>
{
protected override void Execute(ActivityExecutionContext context)
{
var stimulus = new SomeJobStimulus();
var bookmark = context.CreateBookmark(stimulus, OnResume);
var backgroundJobClient = context.GetRequiredService<IBackgroundJobClient>();
backgroundJobClient.Enqueue<SomeJob>(x => x.RunAsync(bookmark.Id, default));
}
private async ValueTask OnResume(ActivityExecutionContext context)
{
var jobResult = context.GetWorkflowInput<SomeJobResult>("JobResult");
Result.Set(context, jobResult);
await context.CompleteActivityAsync();
}
}

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj" />
<ProjectReference Include="..\..\src\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\src\modules\Elsa.Hangfire\Elsa.Hangfire.csproj" />
<ProjectReference Include="..\..\src\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
<ProjectReference Include="..\..\src\modules\Elsa\Elsa.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,34 @@
using Elsa.Samples.AspNet.HangfireIntegration.Models;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Filters;
using Elsa.Workflows.Runtime.Options;
namespace Elsa.Samples.AspNet.HangfireIntegration.Jobs;
public class SomeJob(IBookmarkResumer bookmarkResumer)
{
public async Task RunAsync(string bookmarkId, CancellationToken cancellationToken)
{
Console.Write("Executing some job...");
await Task.Delay(5000, cancellationToken);
Console.WriteLine("Done!");
await ResumeBookmarkAsync(bookmarkId, cancellationToken);
}
private async Task ResumeBookmarkAsync(string bookmarkId, CancellationToken cancellationToken)
{
var resumeOptions = new ResumeBookmarkOptions
{
Input = new Dictionary<string, object>
{
["JobResult"] = new SomeJobResult { Message = "Hello from SomeJob!" }
}
};
var filter = new BookmarkFilter
{
BookmarkId = bookmarkId
};
await bookmarkResumer.ResumeAsync(filter, resumeOptions, cancellationToken);
}
}

View file

@ -0,0 +1,6 @@
namespace Elsa.Samples.AspNet.HangfireIntegration.Models;
public class SomeJobResult
{
public string Message { get; set; }
}

View file

@ -0,0 +1,69 @@
using Elsa.EntityFrameworkCore.Modules.Management;
using Elsa.EntityFrameworkCore.Modules.Runtime;
using Elsa.Extensions;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
var identitySection = configuration.GetSection("Identity");
var identityTokenSection = identitySection.GetSection("Tokens");
// Add Elsa to the container.
builder.Services.AddElsa(elsa =>
{
// Configure management feature to use EF Core.
elsa.UseWorkflowManagement(management =>
{
management
.AddActivitiesFrom<Program>()
.UseEntityFrameworkCore();
});
elsa.UseWorkflowRuntime(runtime =>
{
runtime.UseEntityFrameworkCore();
// Use Hangfire to schedule background activities.
runtime.UseHangfireBackgroundActivityScheduler();
});
// Expose API endpoints.
elsa.UseWorkflowsApi();
// Use Hangfire.
elsa.UseHangfire(hangfire =>
{
hangfire.UseSqliteStorage(sqlite => sqlite.NameOrConnectionString = "elsa.sqlite.db");
});
// Use hangfire for scheduling timer events.
elsa.UseScheduling(scheduling => scheduling.UseHangfireScheduler());
// Configure identity.
elsa.UseIdentity(identity =>
{
identity.TokenOptions = options => identityTokenSection.Bind(options);
identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options));
identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
});
// Use default authentication (JWT).
elsa.UseDefaultAuthentication();
});
// Configure CORS to allow designer app hosted on a different origin to invoke the APIs.
builder.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));
// Build the web app.
var app = builder.Build();
// Configure the web app's request pipeline.
app.UseHttpsRedirection();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseWorkflowsApi();
app.UseWorkflows();
// Run the web app.
app.Run();

View file

@ -0,0 +1,37 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:3978",
"sslPort": 44367
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5090",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7020;http://localhost:5090",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -0,0 +1,6 @@
## Secrets
The following are the secrets stored in hashed form in appsettings.json:
**API key**: `4E753976726458745954355043687772-e54d5a2c-33a3-4c05-a216-b09569062aed`
**Admin user**: `admin`
**Admin password**: `password`

View file

@ -0,0 +1,3 @@
namespace Elsa.Samples.AspNet.HangfireIntegration.Stimuli;
public record SomeJobStimulus;

View file

@ -0,0 +1,44 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.Hosting": "Information",
"Hangfire": "Warning"
}
},
"AllowedHosts": "*",
"Identity": {
"Tokens": {
"SigningKey": "secret-signing-key-of-at-least-256-bits",
"AccessTokenLifetime": "1:00:00:00",
"RefreshTokenLifetime": "1:00:10:00"
},
"Roles": [{
"Id": "admin",
"Name": "Administrator",
"Permissions": ["*"]
}],
"Users": [
{
"Id": "a2323f46-42db-4e15-af8b-94238717d817",
"Name": "admin",
"HashedPassword": "TfKzh9RLix6FPcCNeHLkGrysFu3bYxqzGqduNdi8v1U=",
"HashedPasswordSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=",
"Roles": ["admin"]
}
],
"Applications": [{
"id": "529572c2df854b13807b8bf23f1784cd",
"name": "Postman",
"roles": [
"admin"
],
"clientId": "Nu9vrdXtYT5PChwr",
"clientSecret": "011pp2C$|j01-qrMZpC9VC0F00XCJq(5",
"hashedApiKey": "d0rDld3A+ugKmdctGtMzOLTYjQFkOlUWN+kt0VyW9D0=",
"hashedApiKeySalt": "EnutGOyy5MuJWV0fF5jCQiciK7a8PU/DRF+fr6nekSY=",
"hashedClientSecret": "ERia2zBcCSWb/9dvB0grQ9yf7fWgFrClNeR8A5RMTzk=",
"hashedClientSecretSalt": "z3z8KmzHt+xkAj/zYTXcB8I7y0xAkLm95v4Er/oNqiY="
}]
}
}