Add Caspian.WorkflowFramework & Caspian.WorkflowFramework.Sample + Config these

This commit is contained in:
2025-08-19 18:26:51 +03:30
parent b626db299c
commit dfe36b55a0
33 changed files with 2562 additions and 194 deletions
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Caspian.WorkflowFramework\Caspian.WorkflowFramework.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,6 @@
@Caspian.WorkflowFramework.Sample_HostAddress = http://localhost:5008
GET {{Caspian.WorkflowFramework.Sample_HostAddress}}/weatherforecast/
Accept: application/json
###
@@ -0,0 +1,76 @@
using Caspian.WorkflowFramework;
using Caspian.WorkflowFramework.Contracts;
using Caspian.WorkflowFramework.Models;
using Elsa.Common.Models;
using Elsa.Workflows.Runtime.Matches;
using Microsoft.AspNetCore.Mvc;
using San.MongoLogger;
using Newtonsoft.Json;
namespace Caspian.WorkflowFramework.Sample.Controllers;
public class WorkflowController : Controller, ILoggable
{
#region Fields
private readonly IWorkflowService _workflowService;
private readonly IMdbLogger<WorkflowController> _logger;
#endregion
#region Ctor
public WorkflowController(IWorkflowService workflowService,
IMdbLogger<WorkflowController> logger)
{
_workflowService = workflowService;
_logger = logger;
}
#endregion
#region Start
[HttpPost("Start")]
public async Task<IActionResult> Start(CreateAndExecuteWorkflowModel input)
{
_logger.Log("Start of StartWorkflow action", JsonConvert.SerializeObject(input));
//var model = _mapper.Map<StartWorkflowModel>(input);
var result = await _workflowService.CreateAndExecuteAsync(new CreateAndExecuteWorkflowModel()
{
Name = input.Name,
UserId = input.UserId,
Version = VersionOptions.Published
});
_logger.Log("End of StartWorkflow action", WFInstanceId: result.Data?.InstanceId, JsonConvert.SerializeObject(result));
return Json(result);
}
#endregion
#region Do
[HttpPost("Do")]
public async Task<IActionResult> Do(PublishEventModel input)
{
_logger.Log("Start of Do action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(input));
//var model = _mapper.Map<PublishEventModel>(input);
var result = await _workflowService.PublishEventAsync(input);
_logger.Log("End of Do action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(result));
return Json(result);
}
#endregion
#region GetState
[HttpPost("GetState")]
public async Task<IActionResult> GetState(InquiryStateModel input)
{
_logger.Log("Start of GetState action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(input));
//var model = _mapper.Map<GetCurrentStateModel>(input);
var result = await _workflowService.InquiryStateAsync(input);
_logger.Log("End of GetState action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(result));
return Json(result);
}
#endregion
}
@@ -0,0 +1,28 @@
using System.Reflection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Caspian.WorkflowFramework.Sample;
public class ExcludeExternalControllersFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var currentAssembly = Assembly.GetExecutingAssembly();
var keysToRemove = context.ApiDescriptions
.Where(apiDesc =>
{
var controllerType = apiDesc.ActionDescriptor?.EndpointMetadata?.OfType<Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor>()
.FirstOrDefault()?.ControllerTypeInfo?.AsType();
return controllerType != null && controllerType.Assembly != currentAssembly;
})
.Select(apiDesc => "/" + apiDesc.RelativePath.TrimEnd('/'))
.ToList();
foreach (var path in keysToRemove)
{
swaggerDoc.Paths.Remove(path);
}
}
}
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
@@ -0,0 +1,78 @@
using Caspian.WorkflowFramework;
using Caspian.WorkflowFramework.Sample;
using Microsoft.OpenApi.Models;
using San.MongoLogger;
using San.MongoLogger.Models;
using Parlot.Fluent;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.AllowAnyOrigin() // Add your client origin
.AllowAnyMethod() // Allow any HTTP method
.AllowAnyHeader()); // Allow any header
});
builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
//builder.Services.AddOpenApi();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
c.DocumentFilter<ExcludeExternalControllersFilter>();
c.CustomSchemaIds(type => type.FullName);
});
//Workflow
//var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var t = $"appsettings.workflow.{builder.Environment.EnvironmentName}.json";
builder.AddWorkflowFramework($"appsettings.workflow.json");
builder.Services.AddMongoLogger<LogModel>(builder.Configuration);
var app = builder.Build();
app.UseMongoLogger<LogModel>();
// Enable CORS before authentication and authorization
app.UseCors("AllowSpecificOrigin");
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
//app.MapOpenApi();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.RoutePrefix = string.Empty; // Set Swagger UI at the app's root URL
});
}
app.UseAuthorization();
app.MapControllers();
//Workflow
app.UseWorkflowFramework();
// Redirect to Swagger URL
app.Use(async (context, next) =>
{
// Check if current request is the root URL ("/")
if (context.Request.Path == "/")
{
// Redirect to Swagger UI
context.Response.Redirect("/swagger/index.html");
return;
}
await next(); // Proceed to the next middleware if not redirecting
});
app.Run();
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5008",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
//"Microsoft.EntityFrameworkCore.Database.Command": "Warning" // Disable SQL command logging
}
}
}
@@ -0,0 +1,322 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
//"Microsoft.EntityFrameworkCore.Database.Command": "Warning" // Disable SQL command logging
}
},
"LogDatabaseSettings": {
"ConnectionString": "mongodb://localhost",
"DatabaseName": "Caspian_WorkflowFramework_Sample_DB",
"LogsCollectionName": "Col",
"BatchSize": 0,
"RollingInterval": "Daily",
"MaskList": [
"code",
"depositNumber",
"username",
"customerNo",
"password",
"sessionId",
"mainAccount",
"accountNumber",
"owners",
"balance",
"accountNo",
"depositUniqueId",
"externalRefList",
"externalref",
"sameSignatureDepositNumber",
"mobileNumber",
"destinationNumber",
"cardNumber"
]
},
"AllowedHosts": "*",
"HostBuilder": {
"reloadConfigOnChange": true
},
"ConnectionStrings": {
"DefaultConnection": "Data Source=.;Initial Catalog=WorkflowFrameworkTest;User ID=sa;Password=123456;Integrated Security=false;TrustServerCertificate=True;",
//"Sqlite": "", //"Data Source=App_Data/elsa.sqlite.db;Cache=Shared;",
////"SqlServer": "Server=localhost,1433;Initial Catalog=Elsa;User=sa;Password=!Elsa2025@;Encrypt=false;PersistSecurityInfo=false",
//"SqlServer": "Data Source=.;Initial Catalog=WorkflowFrameworkTest;User ID=sa;Password=123456;Integrated Security=false;TrustServerCertificate=True;",
//"MySql": "", //"Server=localhost;Database=elsa;Uid=admin;Pwd=password;",
//"PostgreSql": "", //"Server=localhost;Username=elsa;Database=elsa;Port=5432;Password=elsa;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60",
//"Citus": "", //"Server=localhost;Username=citus;Database=citus;Port=9700;Password=citus;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60",
//"YugabyteDb": "", //"Host=localhost;Port=5433;Database=elsa;Username=yugabyte;Password=yugabyte;Pooling=true",
//"Oracle": "", //"Data Source=(DESCRIPTION = (ADDRESS_LIST = (FAILOVER =ON) (LOAD_BALANCE = OFF) (ADDRESS = (PROTOCOL =TCP)(HOST=localhost)(PORT=1521))) (CONNECT_DATA = (SID= FREE) ));User Id=ELSA;Password=elsa;",
//"CockroachDb": "", //"Host=localhost;Port=26257;Database=elsa;SslMode=Disable;Username=root;IncludeErrorDetail=true",
//"MongoDb": "", //"mongodb://localhost:27017/elsa-workflows",
//"AzureServiceBus": "",
//"RabbitMq": "", //"amqp://guest:guest@localhost:5672",
//"Redis": "" //"localhost:6379,abortConnect=false"
}
//"DatabaseProvider": "SqlServer", //"Sqlite",
//"Multitenancy": {
// "Tenants": [
// {
// "Id": null,
// "Name": "Default",
// "Configuration": {
// "Http": {
// "Prefix": "",
// "Host": "localhost:5001"
// },
// "ConnectionStrings": {
// "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;"
// }
// }
// },
// {
// "Id": "tenant-1",
// "Name": "Tenant 1",
// "Configuration": {
// "Http": {
// "Prefix": "tenant-1",
// "Host": "localhost:5001"
// },
// "ConnectionStrings": {
// "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;"
// }
// }
// }
// ]
//},
//"Smtp": {
// "Host": "localhost",
// "Port": 2525,
// "DefaultSender": "noreply@crmservices.com"
//},
//"Http": {
// "BaseUrl": "https://localhost:5001",
// "BasePath": "/workflows",
// "ApiRoutePrefix": "elsa/api",
// "AvailableContentTypes": [
// "application/json",
// "application/xml",
// "text/plain",
// "text/html"
// ]
//},
//"Identity": {
// "Tokens": {
// "SigningKey": "sufficiently-large-secret-signing-key",
// "AccessTokenLifetime": "1:00:00:00",
// "RefreshTokenLifetime": "7:00:00:00"
// },
// "Roles": [
// {
// "Id": "admin",
// "Name": "Administrator",
// "Permissions": [
// "*"
// ],
// "TenantId": "default"
// }
// ],
// "Users": [
// {
// "Id": "a2323f46-42db-4e15-af8b-94238717d817",
// "Name": "admin",
// //"HashedPassword": "TfKzh9RLix6FPcCNeHLkGrysFu3bYxqzGqduNdi8v1U=",
// //"HashedPasswordSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=",
// "HashedPassword": "p90gXKnVpt5Q2VZ4KkeOaM5GZN3bqjczoznWbnFMrvo=",
// "HashedPasswordSalt": "QmVzbWVsbGFoQHJSYWhtYW5AcnJhaDFt",
// "Roles": [
// "admin"
// ],
// "TenantId": ""
// },
// {
// "Id": "b0cd0e506e713a9d",
// "Name": "alice",
// "Roles": [
// "admin"
// ],
// "HashedPassword": "8B0fFK/f/kk9GkVtzXfRJ2Y6cNyYVvLTfKouWcAcuPg=",
// "HashedPasswordSalt": "xlNWvEng8fRvo0McyJopbRJ2MJ9NIYV/4IY5dOZeiiw=",
// "TenantId": "tenant-2"
// },
// {
// "Id": "789d5056cb3bb8e9",
// "Name": "bob",
// "Roles": [
// "admin"
// ],
// "HashedPassword": "GMHHvUw7RfIIBq+GAWZ/AlHR50TbgONYfGQ92Xge0EI=",
// "HashedPasswordSalt": "V4uHKg1PFBc1bsngCQoptibOu14faj9obSn5B23h1wE=",
// "TenantId": "tenant-1"
// }
// ],
// "Applications": [
// {
// "Id": "d57030226341448daff5a2935aba2d3f",
// "Name": "Postman",
// "Roles": [
// "admin"
// ],
// "ClientId": "HXr0Vzdm9KCZbwsJ",
// "ClientSecret": "a<~QGGHTEA%u4;CU&'Wga5ED:_&Gd1C)",
// "HashedApiKey": "Z5ClHs3mbzx8Pnw3+PxbMq8A/Y+VKMCCDTGYtax8JFM=",
// "HashedApiKeySalt": "kBisa1X8FwBfN2zmyGMFRgIVVBleghhQAJ4WGyTkaD0=",
// "HashedClientSecret": "jEv58d0SVbGQ3nBZM0lkzHghG4Y+lMKW80wipz+9vHk=",
// "HashedClientSecretSalt": "xRKy14Ok1/tU3kLf/8V1fcbLIegy9vcM90Peu2tzohU=",
// "TenantId": "default"
// }
// ]
//},
//"Environments": {
// "Environments": [
// {
// "Name": "Local",
// "ServerUrl": "https://localhost:5001/elsa/api"
// },
// {
// "Name": "Staging",
// "ServerUrl": "https://staging.acme.com/elsa/api"
// },
// {
// "Name": "Production",
// "ServerUrl": "https://production.acme.com/elsa/api"
// }
// ]
//},
//"AppRole": "Default",
//"Runtime": {
// "DistributedLocking": {
// "Provider": "File",
// "LockAcquisitionTimeout": "00:00:10"
// },
// "DistributedLockProvider": "File",
// "WorkflowDispatcher": {
// "Channels": [
// {
// "Name": "Low"
// },
// {
// "Name": "Medium"
// },
// {
// "Name": "High"
// }
// ]
// }
//},
//"Scripting": {
// "Python": {
// "PythonDllPath": "",
// "Scripts": [
// "def greet(name): return f'Hello {name}!'",
// "def say_hello_world(): return greet('World')"
// ]
// }
//},
//"AzureServiceBus": {
// "Queues": [
// {
// "Name": "queue1"
// },
// {
// "Name": "queue2"
// }
// ],
// "Topics": [
// {
// "Name": "topic1",
// "Subscriptions": [
// {
// "Name": "subscription1"
// },
// {
// "Name": "subscription2"
// }
// ]
// }
// ]
//},
//"Kafka": {
// "Topics": [
// {
// "Id": "topic-1",
// "Name": "topic-1"
// },
// {
// "Id": "topic-2",
// "Name": "topic-2"
// }
// ],
// "SchemaRegistries": [
// {
// "Id": "schema-registry-1",
// "Name": "Schema Registry 1",
// "Config": {
// "Url": "http://localhost:8081"
// }
// }
// ],
// "Producers": [
// {
// "Id": "producer-1",
// "Name": "Producer 1",
// "FactoryType": "Elsa.Kafka.Factories.GenericProducerFactory`2[[System.String, System.Private.CoreLib], [Elsa.Server.Web.Messages.OrderReceived, Elsa.Server.Web]], Elsa.Kafka",
// "Config": {
// "BootstrapServers": "localhost:9092"
// },
// "SchemaRegistryId": "schema-registry-1"
// }
// ],
// "Consumers": [
// {
// "Id": "consumer-1",
// "Name": "Consumer 1",
// "FactoryType": "Elsa.Kafka.Factories.GenericConsumerFactory`2[[System.String, System.Private.CoreLib], [Elsa.Server.Web.Messages.OrderReceived, Elsa.Server.Web]], Elsa.Kafka",
// "Config": {
// "BootstrapServers": "localhost:9092",
// "GroupId": "group-1",
// "AutoOffsetReset": "earliest",
// "EnableAutoCommit": "true"
// },
// "SchemaRegistryId": "schema-registry-1"
// }
// ]
//},
//"Secrets": {
// "Management": {
// "SweepInterval": "04:00:00"
// }
//},
//"Webhooks": {
// "Sinks": [
// {
// "Id": "1",
// "Name": "Run Task",
// "Filters": [
// {
// "EventType": "Elsa.RunTask"
// }
// ],
// "Url": "https://localhost:6001/webhooks/run-task"
// }
// ]
//},
//"Resilience": {
// "Strategies": [
// {
// "$type": "HttpResilienceStrategy",
// "Id": "1",
// "DisplayName": "Retry 3 times, exponential",
// "MaxRetryAttempts": 3,
// "BackoffType": "Exponential"
// },
// {
// "$type": "HttpResilienceStrategy",
// "Id": "2",
// "DisplayName": "Retry 10 times, linear",
// "MaxRetryAttempts": 10,
// "BackoffType": "Linear"
// }
// ]
//}
}
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
//"Microsoft.EntityFrameworkCore.Database.Command": "Warning" // Disable SQL command logging
}
}
}
@@ -0,0 +1,285 @@
{
"HostBuilder": {
"reloadConfigOnChange": true
},
"ConnectionStrings": {
//"Sqlite": "", //"Data Source=App_Data/elsa.sqlite.db;Cache=Shared;",
"SqlServer": "Data Source=.;Initial Catalog=WorkflowDb_3-5-0;User ID=sa;Password=123456;Integrated Security=false;TrustServerCertificate=True;"
//"MySql": "", //"Server=localhost;Database=elsa;Uid=admin;Pwd=password;",
//"PostgreSql": "", //"Server=localhost;Username=elsa;Database=elsa;Port=5432;Password=elsa;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60",
//"Citus": "", //"Server=localhost;Username=citus;Database=citus;Port=9700;Password=citus;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60",
//"YugabyteDb": "", //"Host=localhost;Port=5433;Database=elsa;Username=yugabyte;Password=yugabyte;Pooling=true",
//"Oracle": "", //"Data Source=(DESCRIPTION = (ADDRESS_LIST = (FAILOVER =ON) (LOAD_BALANCE = OFF) (ADDRESS = (PROTOCOL =TCP)(HOST=localhost)(PORT=1521))) (CONNECT_DATA = (SID= FREE) ));User Id=ELSA;Password=elsa;",
//"CockroachDb": "", //"Host=localhost;Port=26257;Database=elsa;SslMode=Disable;Username=root;IncludeErrorDetail=true",
//"MongoDb": "", //"mongodb://localhost:27017/elsa-workflows",
//"AzureServiceBus": "",
//"RabbitMq": "", //"amqp://guest:guest@localhost:5672",
//"Redis": "" //"localhost:6379,abortConnect=false"
},
"DatabaseProvider": "SqlServer", //"Sqlite",
"Multitenancy": {
"Tenants": [
{
"Id": null,
"Name": "Default",
"Configuration": {
"Http": {
"Prefix": "",
"Host": "localhost:5001"
},
"ConnectionStrings": {
"Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;"
}
}
},
{
"Id": "tenant-1",
"Name": "Tenant 1",
"Configuration": {
"Http": {
"Prefix": "tenant-1",
"Host": "localhost:5001"
},
"ConnectionStrings": {
"Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;"
}
}
}
]
},
"Smtp": {
"Host": "localhost",
"Port": 2525,
"DefaultSender": "noreply@crmservices.com"
},
"Http": {
"BaseUrl": "https://localhost:5001",
"BasePath": "/workflows",
"ApiRoutePrefix": "elsa/api",
"AvailableContentTypes": [
"application/json",
"application/xml",
"text/plain",
"text/html"
]
},
"Identity": {
"Tokens": {
"SigningKey": "sufficiently-large-secret-signing-key",
"AccessTokenLifetime": "1:00:00:00",
"RefreshTokenLifetime": "7:00:00:00"
},
"Roles": [
{
"Id": "admin",
"Name": "Administrator",
"Permissions": [
"*"
],
"TenantId": "default"
}
],
"Users": [
{
"Id": "a2323f46-42db-4e15-af8b-94238717d817",
"Name": "admin",
//"HashedPassword": "TfKzh9RLix6FPcCNeHLkGrysFu3bYxqzGqduNdi8v1U=",
//"HashedPasswordSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=",
"HashedPassword": "p90gXKnVpt5Q2VZ4KkeOaM5GZN3bqjczoznWbnFMrvo=",
"HashedPasswordSalt": "QmVzbWVsbGFoQHJSYWhtYW5AcnJhaDFt",
"Roles": [
"admin"
],
"TenantId": ""
},
{
"Id": "b0cd0e506e713a9d",
"Name": "alice",
"Roles": [
"admin"
],
"HashedPassword": "8B0fFK/f/kk9GkVtzXfRJ2Y6cNyYVvLTfKouWcAcuPg=",
"HashedPasswordSalt": "xlNWvEng8fRvo0McyJopbRJ2MJ9NIYV/4IY5dOZeiiw=",
"TenantId": "tenant-2"
},
{
"Id": "789d5056cb3bb8e9",
"Name": "bob",
"Roles": [
"admin"
],
"HashedPassword": "GMHHvUw7RfIIBq+GAWZ/AlHR50TbgONYfGQ92Xge0EI=",
"HashedPasswordSalt": "V4uHKg1PFBc1bsngCQoptibOu14faj9obSn5B23h1wE=",
"TenantId": "tenant-1"
}
],
"Applications": [
{
"Id": "d57030226341448daff5a2935aba2d3f",
"Name": "Postman",
"Roles": [
"admin"
],
"ClientId": "HXr0Vzdm9KCZbwsJ",
"ClientSecret": "a<~QGGHTEA%u4;CU&'Wga5ED:_&Gd1C)",
"HashedApiKey": "Z5ClHs3mbzx8Pnw3+PxbMq8A/Y+VKMCCDTGYtax8JFM=",
"HashedApiKeySalt": "kBisa1X8FwBfN2zmyGMFRgIVVBleghhQAJ4WGyTkaD0=",
"HashedClientSecret": "jEv58d0SVbGQ3nBZM0lkzHghG4Y+lMKW80wipz+9vHk=",
"HashedClientSecretSalt": "xRKy14Ok1/tU3kLf/8V1fcbLIegy9vcM90Peu2tzohU=",
"TenantId": "default"
}
]
},
"Environments": {
"Environments": [
{
"Name": "Local",
"ServerUrl": "https://localhost:5001/elsa/api"
},
{
"Name": "Staging",
"ServerUrl": "https://staging.acme.com/elsa/api"
},
{
"Name": "Production",
"ServerUrl": "https://production.acme.com/elsa/api"
}
]
},
"AppRole": "Default",
"Runtime": {
"DistributedLocking": {
"Provider": "File",
"LockAcquisitionTimeout": "00:00:10"
},
"DistributedLockProvider": "File",
"WorkflowDispatcher": {
"Channels": [
{
"Name": "Low"
},
{
"Name": "Medium"
},
{
"Name": "High"
}
]
}
},
"Scripting": {
"Python": {
"PythonDllPath": "",
"Scripts": [
"def greet(name): return f'Hello {name}!'",
"def say_hello_world(): return greet('World')"
]
}
},
"AzureServiceBus": {
"Queues": [
{
"Name": "queue1"
},
{
"Name": "queue2"
}
],
"Topics": [
{
"Name": "topic1",
"Subscriptions": [
{
"Name": "subscription1"
},
{
"Name": "subscription2"
}
]
}
]
},
"Kafka": {
"Topics": [
{
"Id": "topic-1",
"Name": "topic-1"
},
{
"Id": "topic-2",
"Name": "topic-2"
}
],
"SchemaRegistries": [
{
"Id": "schema-registry-1",
"Name": "Schema Registry 1",
"Config": {
"Url": "http://localhost:8081"
}
}
],
"Producers": [
{
"Id": "producer-1",
"Name": "Producer 1",
"FactoryType": "Elsa.Kafka.Factories.GenericProducerFactory`2[[System.String, System.Private.CoreLib], [Elsa.Server.Web.Messages.OrderReceived, Elsa.Server.Web]], Elsa.Kafka",
"Config": {
"BootstrapServers": "localhost:9092"
},
"SchemaRegistryId": "schema-registry-1"
}
],
"Consumers": [
{
"Id": "consumer-1",
"Name": "Consumer 1",
"FactoryType": "Elsa.Kafka.Factories.GenericConsumerFactory`2[[System.String, System.Private.CoreLib], [Elsa.Server.Web.Messages.OrderReceived, Elsa.Server.Web]], Elsa.Kafka",
"Config": {
"BootstrapServers": "localhost:9092",
"GroupId": "group-1",
"AutoOffsetReset": "earliest",
"EnableAutoCommit": "true"
},
"SchemaRegistryId": "schema-registry-1"
}
]
},
"Secrets": {
"Management": {
"SweepInterval": "04:00:00"
}
},
"Webhooks": {
"Sinks": [
{
"Id": "1",
"Name": "Run Task",
"Filters": [
{
"EventType": "Elsa.RunTask"
}
],
"Url": "https://localhost:6001/webhooks/run-task"
}
]
},
"Resilience": {
"Strategies": [
{
"$type": "HttpResilienceStrategy",
"Id": "1",
"DisplayName": "Retry 3 times, exponential",
"MaxRetryAttempts": 3,
"BackoffType": "Exponential"
},
{
"$type": "HttpResilienceStrategy",
"Id": "2",
"DisplayName": "Retry 10 times, linear",
"MaxRetryAttempts": 10,
"BackoffType": "Linear"
}
]
}
}
@@ -0,0 +1,72 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="San.MongoLogger" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\modules\Elsa.Agents.Persistence.EntityFrameworkCore.MySql\Elsa.Agents.Persistence.EntityFrameworkCore.MySql.csproj" Condition=" '$(TargetFramework)' != 'net9.0' " />
<ProjectReference Include="..\..\modules\Elsa.Caching.Distributed.MassTransit\Elsa.Caching.Distributed.MassTransit.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Caching.Distributed.ProtoActor\Elsa.Caching.Distributed.ProtoActor.csproj" />
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.MySql\Elsa.EntityFrameworkCore.MySql.csproj" Condition=" '$(TargetFramework)' != 'net9.0' " />
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.Oracle\Elsa.EntityFrameworkCore.Oracle.csproj" />
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.PostgreSql\Elsa.EntityFrameworkCore.PostgreSql.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Kafka\Elsa.Kafka.csproj" />
<ProjectReference Include="..\..\modules\Elsa.MassTransit.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj" />
<ProjectReference Include="..\..\modules\Elsa.OpenTelemetry\Elsa.OpenTelemetry.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Quartz.EntityFrameworkCore.MySql\Elsa.Quartz.EntityFrameworkCore.MySql.csproj" Condition=" '$(TargetFramework)' != 'net9.0' " />
<ProjectReference Include="..\..\modules\Elsa.Quartz.EntityFrameworkCore.PostgreSql\Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Retention\Elsa.Retention.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Secrets.Api\Elsa.Secrets.Api.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql\Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite\Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer\Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Secrets.Scripting\Elsa.Secrets.Scripting.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Sql.MySql\Elsa.Sql.MySql.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Sql.PostgreSql\Elsa.Sql.PostgreSql.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Sql.Sqlite\Elsa.Sql.Sqlite.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Sql.SqlServer\Elsa.Sql.SqlServer.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Sql\Elsa.Sql.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Tenants.AspNetCore\Elsa.Tenants.AspNetCore.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Webhooks\Elsa.Webhooks.csproj" />
<ProjectReference Include="..\..\modules\Elsa\Elsa.csproj" />
<ProjectReference Include="..\..\common\Elsa.DropIns\Elsa.DropIns.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Alterations.MassTransit\Elsa.Alterations.MassTransit.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Alterations\Elsa.Alterations.csproj" />
<ProjectReference Include="..\..\modules\Elsa.CSharp\Elsa.CSharp.csproj" />
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj" />
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.SqlServer\Elsa.EntityFrameworkCore.SqlServer.csproj" />
<ProjectReference Include="..\..\modules\Elsa.MassTransit.RabbitMq\Elsa.MassTransit.RabbitMq.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Python\Elsa.Python.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Quartz.EntityFrameworkCore.Sqlite\Elsa.Quartz.EntityFrameworkCore.Sqlite.csproj" />
<ProjectReference Include="..\..\modules\Elsa.FileStorage\Elsa.FileStorage.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Dapper.Migrations\Elsa.Dapper.Migrations.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Dapper\Elsa.Dapper.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Elasticsearch\Elsa.Elasticsearch.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Email\Elsa.Email.csproj" />
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Environments\Elsa.Environments.csproj" />
<ProjectReference Include="..\..\modules\Elsa.MassTransit\Elsa.MassTransit.csproj" />
<ProjectReference Include="..\..\modules\Elsa.MongoDb\Elsa.MongoDb.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Workflows.Runtime.ProtoActor\Elsa.Workflows.Runtime.ProtoActor.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Identity\Elsa.Identity.csproj" />
<ProjectReference Include="..\..\modules\Elsa.WorkflowProviders.BlobStorage\Elsa.WorkflowProviders.BlobStorage.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Hangfire\Elsa.Hangfire.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Http\Elsa.Http.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Labels\Elsa.Labels.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Liquid\Elsa.Liquid.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Quartz\Elsa.Quartz.csproj" />
<ProjectReference Include="..\..\modules\Elsa.WorkflowContexts\Elsa.WorkflowContexts.csproj" />
<ProjectReference Include="..\..\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
<ProjectReference Include="..\Elsa.Server.Web\Elsa.Server.Web.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,130 @@
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Caspian.WorkflowFramework</id>
<version>1.0.0-preview34</version>
<authors>Digital Banking Team</authors>
<owners>Caspian</owners>
<description>Caspian workflow framework</description>
<projectUrl>https://caspco.ir</projectUrl>
<icon>package-icon.png</icon>
<readme>readme.md</readme>
<tags>caspian, nextbank, workflow</tags>
<license type="file">LICENSE.txt</license>
<dependencies>
<group targetFramework="net9.0">
<dependency id="Microsoft.EntityFrameworkCore" version="*" />
<dependency id="ConfigureAwait.Fody" version="*" />
<dependency id="Cronos" version="*" />
<dependency id="DistributedLock.Core" version="*" />
<dependency id="Fody" version="*" />
<dependency id="IronCompress" version="*" />
<dependency id="JetBrains.Annotations" version="*" />
<dependency id="LinqKit.Core" version="*" />
<dependency id="Microsoft.AspNetCore.Http.Abstractions" version="*" />
<dependency id="Microsoft.Extensions.Configuration" version="*" />
<dependency id="Microsoft.Extensions.Configuration.Json" version="*" />
<dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="*" />
<dependency id="Microsoft.Extensions.Hosting" version="*" />
<dependency id="Microsoft.Extensions.Hosting.Abstractions" version="*" />
<dependency id="Microsoft.Extensions.Logging" version="*" />
<dependency id="Microsoft.SourceLink.GitHub" version="*" />
<dependency id="MongoLogger" version="*" />
<dependency id="Open.Linq.AsyncExtensions" version="*" />
<dependency id="Proto.Actor" version="*" />
<dependency id="Proto.Cluster" version="*" />
<dependency id="Proto.Cluster.TestProvider" version="*" />
<dependency id="Proto.OpenTelemetry" version="*" />
<dependency id="Proto.Persistence" version="*" />
<dependency id="Proto.Remote" version="*" />
<dependency id="FastEndpoints" version="*" />
<dependency id="FastEndpoints.Security" version="*" />
<dependency id="FastEndpoints.Swagger" version="*" />
<dependency id="Microsoft.Extensions.DependencyInjection" version="*" />
<dependency id="Microsoft.Extensions.Options" version="*" />
<dependency id="NuGet.Packaging" version="*" />
<dependency id="NuGet.Protocol" version="*" />
<dependency id="System.Formats.Asn1" version="*" />
<dependency id="ThrottleDebounce" version="*" />
<dependency id="Microsoft.Extensions.Options.ConfigurationExtensions" version="*" />
<dependency id="Microsoft.Extensions.Caching.Memory" version="*" />
<dependency id="Scrutor" version="*" />
<dependency id="Google.Protobuf" version="*" />
<dependency id="Grpc.Tools" version="*" />
<dependency id="Proto.Cluster.CodeGen" version="*" />
<dependency id="Microsoft.Data.Sqlite" version="*" />
<dependency id="Microsoft.EntityFrameworkCore.Sqlite" version="*" />
<!-- <dependency id="Microsoft.EntityFrameworkCore.Design" version="*" /> -->
<dependency id="MailKit" version="*" />
<dependency id="Microsoft.Extensions.Http" version="*" />
<dependency id="FluentStorage" version="*" />
<dependency id="Microsoft.Extensions.Http.Resilience" version="*" />
<dependency id="Microsoft.Extensions.Resilience" version="*" />
<dependency id="Microsoft.Extensions.Http.Polly" version="*" />
<dependency id="WebhooksCore" version="*" />
<dependency id="Azure.Identity" version="*" />
<dependency id="Dapper" version="*" />
<dependency id="FluentMigrator" version="*" />
<dependency id="FluentMigrator.Runner" version="*" />
<dependency id="Microsoft.Data.Sqlite.Core" version="*" />
<dependency id="Microsoft.Identity.Client" version="*" />
<!-- <dependency id="Npgsql" /> -->
<dependency id="System.Data.SqlClient" version="*" />
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="*" />
<dependency id="System.CommandLine" version="*" />
<dependency id="DistributedLock.FileSystem" version="*" />
<!-- <dependency id="Microsoft.CodeAnalysis.CSharp.Scripting" version="*" /> -->
<dependency id="Antlr4.Runtime.Standard" version="*" />
<dependency id="System.Linq.Async" version="*" />
<dependency id="Jint" version="*" />
<dependency id="Fluid.Core" version="*" />
<dependency id="pythonnet" version="*" />
<dependency id="Microsoft.AspNetCore.DataProtection.Abstractions" version="*" />
<dependency id="AspNetCore.Authentication.ApiKey" version="*" />
<dependency id="Azure.Messaging.ServiceBus" version="*" />
<dependency id="Azure.ResourceManager.ServiceBus" version="*" />
<dependency id="Confluent.Kafka" version="*" />
<dependency id="Confluent.SchemaRegistry.Serdes.Avro" version="*" />
<dependency id="MassTransit" version="*" />
<dependency id="MassTransit.Azure.ServiceBus.Core" version="*" />
<dependency id="MassTransit.RabbitMQ" version="*" />
<dependency id="Hangfire" version="*" />
<dependency id="Hangfire.MemoryStorage" version="*" />
<dependency id="Hangfire.Storage.SQLite" version="*" />
<dependency id="Quartz.Extensions.Hosting" version="*" />
<dependency id="AppAny.Quartz.EntityFrameworkCore.Migrations.MySql" version="*" />
<dependency id="Quartz.Serialization.Json" version="*" />
<dependency id="AppAny.Quartz.EntityFrameworkCore.Migrations.SqlServer" version="*" />
<dependency id="Humanizer.Core" version="*" />
<dependency id="Newtonsoft.Json" version="*" />
<dependency id="ShortGuid" version="*" />
<dependency id="System.Linq.Dynamic.Core" version="*" />
<dependency id="PolySharp" version="*" />
</group>
</dependencies>
</metadata>
<files>
<!-- Include the favicon -->
<file src="favicon.ico" target="\" />
<!-- Include license, icon, and readme files -->
<file src="LICENSE.txt" target="\" />
<file src="package-icon.png" target="\" />
<file src="readme.md" target="\" />
<!-- Include all required DLLs in the lib folder -->
<file src="bin\release\net9.0\*.dll" target="lib\net9.0" />
<!-- <file src="..\..\common\Elsa.Features\bin\Release\net9.0\Elsa.Features.dll" target="lib\net9.0\Elsa.Features.dll" /> -->
<!-- <file src="..\..\modules\Elsa.CSharp\bin\Release\net9.0\Elsa.CSharp.dll" target="lib\net9.0\Elsa.CSharp.dll" /> -->
<!-- <file src="..\..\modules\Elsa.Email\bin\Release\net9.0\Elsa.Email.dll" target="lib\net9.0\Elsa.Email.dll" /> -->
<!-- <file src="..\..\modules\Elsa.EntityFrameworkCore.Common\bin\Release\net9.0\Elsa.EntityFrameworkCore.Common.dll" target="lib\net9.0\Elsa.EntityFrameworkCore.Common.dll" /> -->
<!-- <file src="..\..\modules\Elsa.EntityFrameworkCore.SqlServer\bin\Release\net9.0\Elsa.EntityFrameworkCore.SqlServer.dll" target="lib\net9.0\Elsa.EntityFrameworkCore.SqlServer.dll" /> -->
<!-- <file src="..\..\modules\Elsa.EntityFrameworkCore\bin\Release\net9.0\Elsa.EntityFrameworkCore.dll" target="lib\net9.0\Elsa.EntityFrameworkCore.dll" /> -->
<!-- <file src="..\..\modules\Elsa.JavaScript\bin\Release\net9.0\Elsa.JavaScript.dll" target="lib\net9.0\Elsa.JavaScript.dll" /> -->
<!-- <file src="..\..\modules\Elsa.Scheduling\bin\Release\net9.0\Elsa.Scheduling.dll" target="lib\net9.0\Elsa.Scheduling.dll" /> -->
<!-- <file src="..\..\modules\Elsa.Workflows.Api\bin\Release\net9.0\Elsa.Workflows.Api.dll" target="lib\net9.0\Elsa.Workflows.Api.dll" /> -->
<!-- <file src="..\..\modules\Elsa.Workflows.Management\bin\Release\net9.0\Elsa.Workflows.Management.dll" target="lib\net9.0\Elsa.Workflows.Management.dll" /> -->
<!-- <file src="..\..\modules\Elsa.Workflows.Runtime\bin\Release\net9.0\Elsa.Workflows.Runtime.dll" target="lib\net9.0\Elsa.Workflows.Runtime.dll" /> -->
<!-- <file src="..\..\modules\Elsa\bin\Release\net9.0\Elsa.dll" target="lib\net9.0\Elsa.dll" /> -->
</files>
</package>
@@ -0,0 +1,685 @@
using System.IO;
using System.Text.Encodings.Web;
using Caspian.WorkflowFramework.Contracts;
using Caspian.WorkflowFramework.Services;
using Elsa.Alterations.Extensions;
using Elsa.Alterations.MassTransit.Extensions;
using Elsa.Caching.Options;
using Elsa.Common.Codecs;
using Elsa.Common.DistributedHosting.DistributedLocks;
using Elsa.Common.RecurringTasks;
using Elsa.Common.Serialization;
using Elsa.Dapper.Extensions;
using Elsa.Dapper.Services;
using Elsa.DropIns.Extensions;
using Elsa.EntityFrameworkCore.Extensions;
using Elsa.EntityFrameworkCore.Modules.Alterations;
using Elsa.EntityFrameworkCore.Modules.Identity;
using Elsa.EntityFrameworkCore.Modules.Management;
using Elsa.EntityFrameworkCore.Modules.Runtime;
using Elsa.EntityFrameworkCore.Modules.Tenants;
using Elsa.Expressions.Helpers;
using Elsa.Extensions;
using Elsa.Features.Services;
using Elsa.Identity.Multitenancy;
using Elsa.Kafka;
using Elsa.Kafka.Factories;
using Elsa.MassTransit.Extensions;
using Elsa.MongoDb.Extensions;
using Elsa.MongoDb.Modules.Alterations;
using Elsa.MongoDb.Modules.Identity;
using Elsa.MongoDb.Modules.Management;
using Elsa.MongoDb.Modules.Runtime;
using Elsa.MongoDb.Modules.Tenants;
using Elsa.OpenTelemetry.Middleware;
using Elsa.Retention.Extensions;
using Elsa.Retention.Models;
using Elsa.Secrets.Extensions;
using Elsa.Secrets.Management.Tasks;
using Elsa.Secrets.Persistence;
using Elsa.Server.Web;
using Elsa.Server.Web.Extensions;
using Elsa.Server.Web.Filters;
using Elsa.Server.Web.Messages;
using Elsa.Sql.Extensions;
using Elsa.Sql.MySql;
using Elsa.Sql.PostgreSql;
using Elsa.Sql.Sqlite;
using Elsa.Sql.SqlServer;
using Elsa.Tenants.AspNetCore;
using Elsa.Tenants.Extensions;
using Elsa.Workflows;
using Elsa.Workflows.Api;
using Elsa.Workflows.CommitStates.Strategies;
using Elsa.Workflows.IncidentStrategies;
using Elsa.Workflows.LogPersistence;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Stores;
using Elsa.Workflows.Options;
using Elsa.Workflows.Runtime.Distributed.Extensions;
using Elsa.Workflows.Runtime.Options;
using Elsa.Workflows.Runtime.Stores;
using Elsa.Workflows.Runtime.Tasks;
using Hangfire;
using Hangfire.MemoryStorage;
using Hangfire.PostgreSql;
using Hangfire.PostgreSql.Factories;
using Hangfire.SqlServer;
using Hangfire.Storage.SQLite;
using JetBrains.Annotations;
using Medallion.Threading.FileSystem;
using Medallion.Threading.Postgres;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Proto.Cluster.Kubernetes;
using Proto.Persistence.Sqlite;
using Proto.Persistence.SqlServer;
using Proto.Remote;
using Proto.Remote.GrpcNet;
using San.MongoLogger;
using San.MongoLogger.Models;
using StackExchange.Redis;
namespace Caspian.WorkflowFramework;
public static class DependencyInjection
{
const PersistenceProvider persistenceProvider = PersistenceProvider.EntityFrameworkCore;
const WorkflowRuntime workflowRuntime = WorkflowRuntime.Distributed;
const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.Memory;
const MassTransitBroker massTransitBroker = MassTransitBroker.Memory;
const bool useDbContextPooling = false;
const bool useHangfire = false;
const bool useQuartz = true;
const bool useMassTransit = false;
const bool useZipCompression = false;
const bool runEFCoreMigrations = true;
const bool useMemoryStores = false;
const bool useCaching = true;
const bool useKafka = false;
const bool useReadOnlyMode = false;
const bool useSignalR = false; // Disabled until Elsa Studio sends authenticated requests.
const bool useMultitenancy = false;
const bool useTenantsFromConfiguration = true;
const bool useSecrets = false;
const bool disableVariableWrappers = false;
const bool disableVariableCopying = false;
const bool useManualOtelInstrumentation = false;
public static void AddWorkflowFramework(this IHostApplicationBuilder builder, string appsettingName)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile(appsettingName, optional: false, reloadOnChange: true)
.Build();
//var configuration = builder.Configuration;
var heartbeatSection = configuration.GetSection("Heartbeat");
var identitySection = configuration.GetSection("Identity");
var identityTokenSection = identitySection.GetSection("Tokens");
var sqliteConnectionString = configuration.GetConnectionString("Sqlite")!;
var sqlServerConnectionString = configuration.GetConnectionString("SqlServer")!;
var postgresConnectionString = configuration.GetConnectionString("PostgreSql")!;
var citusConnectionString = configuration.GetConnectionString("Citus")!;
var yugabyteDbConnectionString = configuration.GetConnectionString("YugabyteDb")!;
var oracleConnectionString = configuration.GetConnectionString("Oracle")!;
var mySqlConnectionString = configuration.GetConnectionString("MySql")!;
var cockroachDbConnectionString = configuration.GetConnectionString("CockroachDb")!;
var mongoDbConnectionString = configuration.GetConnectionString("MongoDb")!;
var azureServiceBusConnectionString = configuration.GetConnectionString("AzureServiceBus")!;
var rabbitMqConnectionString = configuration.GetConnectionString("RabbitMq")!;
var redisConnectionString = configuration.GetConnectionString("Redis")!;
var distributedLockProviderName = configuration.GetSection("Runtime:DistributedLocking")["Provider"];
var appRole = Enum.Parse<ApplicationRole>(configuration["AppRole"]! ?? "Default");
var sqlDatabaseProvider = Enum.Parse<SqlDatabaseProvider>(configuration["DatabaseProvider"] ?? "SqlServer");
const PersistenceProvider persistenceProvider = PersistenceProvider.EntityFrameworkCore;
const bool useDbContextPooling = false;
const bool useHangfire = false;
const bool useQuartz = false;//true;
const bool useMassTransit = false;
const bool useZipCompression = false;
const bool runEFCoreMigrations = true;
const bool useMemoryStores = false;
const bool useCaching = true;
const bool useKafka = false;
const bool useReadOnlyMode = false;
const bool useSignalR = false; // Disabled until Elsa Studio sends authenticated requests.
const WorkflowRuntime workflowRuntime = WorkflowRuntime.Distributed;
const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.Memory;
const MassTransitBroker massTransitBroker = MassTransitBroker.Memory;
const bool useMultitenancy = false;
const bool useTenantsFromConfiguration = false;// true;
const bool useSecrets = false;
const bool disableVariableWrappers = false;
const bool disableVariableCopying = false;
const bool useManualOtelInstrumentation = false;
// Optionally create type aliases for easier configuration.
TypeAliasRegistry.RegisterAlias("OrderReceivedProducerFactory", typeof(GenericProducerFactory<string, OrderReceived>));
TypeAliasRegistry.RegisterAlias("OrderReceivedConsumerFactory", typeof(GenericConsumerFactory<string, OrderReceived>));
if (useManualOtelInstrumentation)
{
builder.Services
.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService("elsa-workflows", serviceVersion: "3.5.0").AddTelemetrySdk())
.WithTracing(tracing =>
{
tracing
.AddSource("*")
.SetSampler(new AlwaysOnSampler())
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.AddConsoleExporter()
.AddOtlpExporter()
;
})
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter()
.AddOtlpExporter()
;
});
// Enable OpenTelemetry Logging (optional)
builder.Logging.AddOpenTelemetry(options =>
{
options.IncludeFormattedMessage = true;
options.IncludeScopes = true;
options.ParseStateValues = true;
});
}
// Add Elsa services.
builder.Services.AddScoped<IPropertyUIHandler, CustomCheckListOptionsProvider>();
builder.Services.AddMongoLogger<LogModel>(builder.Configuration);
builder.Services.AddElsa(elsa =>
{
elsa
.UseWorkflows(workflows =>
{
workflows.WithDefaultWorkflowExecutionPipeline(pipeline => pipeline.UseWorkflowExecutionTracing());
workflows.WithDefaultActivityExecutionPipeline(pipeline => pipeline.UseActivityExecutionTracing());
workflows.UseCommitStrategies(strategies =>
{
strategies.AddStandardStrategies();
strategies.Add("Every 10 seconds", new PeriodicWorkflowStrategy(TimeSpan.FromSeconds(10)));
});
})
.UseWorkflowManagement(management =>
{
management.UseEntityFrameworkCore(ef =>
{
ef.UseSqlServer(sqlServerConnectionString);
});
if (useZipCompression)
management.SetCompressionAlgorithm(nameof(Zstd));
if (useMemoryStores)
management.UseWorkflowInstances(feature => feature.WorkflowInstanceStore = sp => sp.GetRequiredService<MemoryWorkflowInstanceStore>());
if (useMassTransit)
management.UseMassTransitDispatcher();
if (useCaching)
management.UseCache();
management.SetDefaultLogPersistenceMode(LogPersistenceMode.Inherit);
management.UseReadOnlyMode(useReadOnlyMode);
management.AddVariableTypeAndAlias<OrderReceived>("Application");
})
.UseWorkflowRuntime(runtime =>
{
runtime.UseEntityFrameworkCore(ef =>
{
ef.UseContextPooling = useDbContextPooling;
//ef.UseSqlServer(sqlServerConnectionString, new ElsaDbContextOptions);
var migrationsAssembly = typeof(Elsa.EntityFrameworkCore.SqlServer.IdentityDbContextFactory).Assembly;
var connectionString = sqlServerConnectionString;
ef.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlServer(migrationsAssembly, connectionString, null, configure => configure.CommandTimeout(60000));
ef.RunMigrations = runEFCoreMigrations;
});
if (workflowRuntime == WorkflowRuntime.Distributed)
{
runtime.UseDistributedRuntime();
}
if (workflowRuntime == WorkflowRuntime.ProtoActor)
{
runtime.UseProtoActor();
}
if (useMassTransit)
runtime.UseMassTransitDispatcher();
runtime.WorkflowDispatcherOptions = options => configuration.GetSection("Runtime:WorkflowDispatcher").Bind(options);
if (useMemoryStores)
{
runtime.ActivityExecutionLogStore = sp => sp.GetRequiredService<MemoryActivityExecutionStore>();
runtime.WorkflowExecutionLogStore = sp => sp.GetRequiredService<MemoryWorkflowExecutionLogStore>();
}
if (useCaching)
runtime.UseCache();
runtime.DistributedLockingOptions = options => configuration.GetSection("Runtime:DistributedLocking").Bind(options);
runtime.DistributedLockProvider = _ =>
{
switch (distributedLockProviderName)
{
case "Postgres":
return new PostgresDistributedSynchronizationProvider(postgresConnectionString, options =>
{
options.KeepaliveCadence(TimeSpan.FromMinutes(5));
options.UseMultiplexing();
});
case "Redis":
{
var connectionMultiplexer = ConnectionMultiplexer.Connect(redisConnectionString);
var database = connectionMultiplexer.GetDatabase();
return new RedisDistributedSynchronizationProvider(database);
}
case "File":
return new FileDistributedSynchronizationProvider(new(Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "locks")));
case "Noop":
default:
return new NoopDistributedSynchronizationProvider();
}
};
})
.UseEnvironments(environments => environments.EnvironmentsOptions = options => configuration.GetSection("Environments").Bind(options))
.UseScheduling(scheduling =>
{
if (useHangfire)
scheduling.UseHangfireScheduler();
if (useQuartz)
scheduling.UseQuartzScheduler();
})
.UseWorkflowsApi(api =>
{
api.AddFastEndpointsAssembly<Program>();
})
.UseCSharp(options =>
{
options.DisableWrappers = disableVariableWrappers;
options.AppendScript("string Greet(string name) => $\"Hello {name}!\";");
options.AppendScript("string SayHelloWorld() => Greet(\"World\");");
})
.UseJavaScript(options =>
{
options.AllowClrAccess = true;
options.DisableWrappers = disableVariableWrappers;
options.DisableVariableCopying = disableVariableCopying;
options.RegisterType<OrderReceived>();
options.ConfigureEngine(engine =>
{
engine.Execute("function greet(name) { return `Hello ${name}!`; }");
engine.Execute("function sayHelloWorld() { return greet('World'); }");
});
})
.UsePython(python =>
{
python.PythonOptions += options =>
{
// Make sure to configure the path to the python DLL. E.g. /opt/homebrew/Cellar/python@3.11/3.11.6_1/Frameworks/Python.framework/Versions/3.11/bin/python3.11
// alternatively, you can set the PYTHONNET_PYDLL environment variable.
configuration.GetSection("Scripting:Python").Bind(options);
options.AddScript(sb =>
{
sb.AppendLine("def greet():");
sb.AppendLine(" return \"Hello, welcome to Python!\"");
});
};
})
.UseLiquid(liquid => liquid.FluidOptions = options => options.Encoder = HtmlEncoder.Default)
.UseHttp(http =>
{
http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options);
if (useCaching)
http.UseCache();
})
.UseSql(options =>
{
options.Clients = client =>
{
client.Register<MySqlClient>("MySql");
client.Register<PostgreSqlClient>("PostgreSql");
client.Register<SqliteClient>("Sqlite");
client.Register<SqlServerClient>("Sql Server");
};
})
.UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options))
.UseAlterations(alterations =>
{
if (persistenceProvider == PersistenceProvider.MongoDb)
{
alterations.UseMongoDb();
}
else if (persistenceProvider == PersistenceProvider.Dapper)
{
// TODO: alterations.UseDapper();
}
else
{
alterations.UseEntityFrameworkCore(ef =>
{
ef.UseContextPooling = useDbContextPooling;
ef.UseSqlServer(sqlServerConnectionString);
ef.RunMigrations = runEFCoreMigrations;
});
}
if (useMassTransit)
{
alterations.UseMassTransitDispatcher();
}
})
.UseOpenTelemetry(otel => otel.UseNewRootActivityForRemoteParent = true)
.UseWorkflowContexts();
if (useQuartz)
{
elsa.UseQuartz(quartz =>
{
if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite)
quartz.UseSqlite(sqliteConnectionString);
else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
quartz.UsePostgreSql(postgresConnectionString);
});
}
if (useSignalR)
{
elsa.UseRealTimeWorkflows();
}
if (useMassTransit)
{
elsa.UseMassTransit(massTransit =>
{
massTransit.DisableConsumers = appRole == ApplicationRole.Api;
massTransit.AddMessageType<OrderReceived>();
if (massTransitBroker == MassTransitBroker.AzureServiceBus)
{
massTransit.UseAzureServiceBus(azureServiceBusConnectionString, serviceBusFeature => serviceBusFeature.ConfigureTransportBus = (context, bus) =>
{
bus.PrefetchCount = 50;
bus.LockDuration = TimeSpan.FromMinutes(5);
bus.MaxConcurrentCalls = 32;
bus.MaxDeliveryCount = 8;
// etc.
});
}
if (massTransitBroker == MassTransitBroker.RabbitMq)
{
massTransit.UseRabbitMq(rabbitMqConnectionString, rabbit => rabbit.ConfigureTransportBus = (context, bus) =>
{
bus.PrefetchCount = 50;
bus.Durable = true;
bus.AutoDelete = false;
bus.ConcurrentMessageLimit = 32;
// etc.
});
}
});
}
if (distributedCachingTransport != DistributedCachingTransport.None)
{
elsa.UseDistributedCache(distributedCaching =>
{
if (distributedCachingTransport == DistributedCachingTransport.MassTransit) distributedCaching.UseMassTransit();
if (distributedCachingTransport == DistributedCachingTransport.ProtoActor) distributedCaching.UseProtoActor();
});
}
if (distributedCachingTransport == DistributedCachingTransport.ProtoActor || workflowRuntime == WorkflowRuntime.ProtoActor)
{
elsa.UseProtoActor(proto =>
{
proto
.EnableMetrics()
.EnableTracing();
proto.PersistenceProvider = _ =>
{
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
return new SqlServerProvider(sqlServerConnectionString, true, "", "proto_actor");
return new SqliteProvider(new(sqliteConnectionString));
};
if (configuration["KUBERNETES_SERVICE_HOST"] != null)
{
var kubernetesConfig = new KubernetesProviderConfig();
var clusterProvider = new KubernetesProvider(kubernetesConfig);
var remoteConfig = GrpcNetRemoteConfig
.BindToAllInterfaces(advertisedHost: configuration["ProtoActor:AdvertisedHost"]) // Environment variable to be provided by Kubernetes using pod.status.podIP.
.WithLogLevelForDeserializationErrors(LogLevel.Critical)
.WithRemoteDiagnostics(true);
proto.CreateClusterProvider = _ => clusterProvider;
proto.ConfigureRemoteConfig = _ => remoteConfig;
}
});
}
if (useKafka)
{
elsa.UseKafka(kafka =>
{
kafka.ConfigureOptions(options => configuration.GetSection("Kafka").Bind(options));
});
}
if (useSecrets)
{
elsa
.UseSecrets()
.UseSecretsManagement(management =>
{
management.ConfigureOptions(options => configuration.GetSection("Secrets:Management").Bind(options));
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
management.UseEntityFrameworkCore(ef =>
{
ef.UseContextPooling = useDbContextPooling;
ef.UseSqlServer(sqlServerConnectionString);
});
else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
management.UseEntityFrameworkCore(ef =>
{
ef.UseContextPooling = useDbContextPooling;
ef.UsePostgreSql(postgresConnectionString);
});
else
management.UseEntityFrameworkCore(ef =>
{
ef.UseContextPooling = useDbContextPooling;
ef.UseSqlite(sp => sp.GetSqliteConnectionString());
});
})
.UseSecretsApi()
.UseSecretsScripting();
}
//elsa.UseRetention(r =>
//{
// r.SweepInterval = TimeSpan.FromHours(5);
// r.AddDeletePolicy("Delete all finished workflows", sp =>
// {
// var filter = new RetentionWorkflowInstanceFilter
// {
// WorkflowStatus = WorkflowStatus.Finished
// };
// return filter;
// });
//});
if (useMultitenancy)
{
elsa.UseTenants(tenants =>
{
tenants.ConfigureMultitenancy(options =>
{
options.TenantResolverPipelineBuilder
.Append<HostTenantResolver>()
.Append<RoutePrefixTenantResolver>()
.Append<HeaderTenantResolver>()
.Append<ClaimsTenantResolver>();
});
if (useTenantsFromConfiguration)
{
tenants.UseConfigurationBasedTenantsProvider(options => configuration.GetSection("Multitenancy").Bind(options));
}
else
{
tenants.UseStoreBasedTenantsProvider();
tenants.UseTenantManagement(management =>
{
if (persistenceProvider == PersistenceProvider.MongoDb)
management.UseMongoDb();
if (persistenceProvider == PersistenceProvider.Dapper)
throw new NotSupportedException("Dapper is not supported for tenant management.");
if (persistenceProvider == PersistenceProvider.EntityFrameworkCore)
{
management.UseEntityFrameworkCore(ef =>
{
ef.UseContextPooling = useDbContextPooling;
if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite) ef.UseSqlite(sqliteConnectionString);
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) ef.UseSqlServer(sqlServerConnectionString);
if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString);
if (sqlDatabaseProvider == SqlDatabaseProvider.Citus) ef.UsePostgreSql(citusConnectionString);
if (sqlDatabaseProvider == SqlDatabaseProvider.YugabyteDb) ef.UsePostgreSql(yugabyteDbConnectionString);
if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle)
ef.UseOracle(oracleConnectionString, new()
{
SchemaName = "ELSA"
});
#if !NET9_0
if (sqlDatabaseProvider == SqlDatabaseProvider.MySql)
ef.UseMySql(mySqlConnectionString);
#endif
if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) ef.UsePostgreSql(cockroachDbConnectionString);
});
}
});
tenants.UseTenantManagementEndpoints();
}
});
elsa.UseTenantHttpRouting(tenantHttpRouting =>
{
// Override the tenant header name with a custom one.
tenantHttpRouting.WithTenantHeader("X-Tenant-ID");
});
}
elsa.UseWebhooks(webhooks => webhooks.ConfigureSinks += options => builder.Configuration.GetSection("Webhooks").Bind(options));
elsa.InstallDropIns(options => options.DropInRootDirectory = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "DropIns"));
elsa.AddSwagger();
elsa.AddFastEndpointsAssembly<Program>();
//ConfigureForTest?.Invoke(elsa);
});
// Obfuscate HTTP request headers.
builder.Services.AddActivityStateFilter<HttpRequestAuthenticationHeaderFilter>();
// Optionally configure recurring tasks using alternative schedules.
builder.Services.Configure<RecurringTaskOptions>(options =>
{
options.Schedule.ConfigureTask<TriggerBookmarkQueueRecurringTask>(TimeSpan.FromSeconds(300));
options.Schedule.ConfigureTask<PurgeBookmarkQueueRecurringTask>(TimeSpan.FromSeconds(300));
options.Schedule.ConfigureTask<UpdateExpiredSecretsRecurringTask>(TimeSpan.FromHours(4));
options.Schedule.ConfigureTask<RestartInterruptedWorkflowsTask>(TimeSpan.FromSeconds(15));
});
builder.Services.Configure<RuntimeOptions>(options => { options.InactivityThreshold = TimeSpan.FromSeconds(15); });
builder.Services.Configure<BookmarkQueuePurgeOptions>(options => options.Ttl = TimeSpan.FromSeconds(10));
builder.Services.Configure<CachingOptions>(options => options.CacheDuration = TimeSpan.FromDays(1));
builder.Services.Configure<IncidentOptions>(options => options.DefaultIncidentStrategy = typeof(ContinueWithIncidentsStrategy));
builder.Services.AddHealthChecks();
builder.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("*")));
builder.Services.AddScoped<IWorkflowService, WorkflowService>();
}
///// <summary>
///// The main entry point for the application made public for end to end testing.
///// </summary>
//[UsedImplicitly]
//public partial class Program
//{
// /// <summary>
// /// Set by the test runner to configure the module for testing.
// /// </summary>
// public static Action<IModule>? ConfigureForTest { get; set; }
//}
public static void UseWorkflowFramework(this IApplicationBuilder app)
{
//try
//{
// app.UseMongoLogger<LogModel>();
//}
//catch (Exception ex)
//{
// var d = ex;
//}
// Configure the pipeline.
var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
// CORS.
app.UseCors();
// Health checks.
//app.MapHealthChecks("/");
// Routing used for SignalR.
app.UseRouting();
// Multitenancy.
if (useMultitenancy)
app.UseTenants();
// Elsa API endpoints for designer.
var routePrefix = app.ApplicationServices.GetRequiredService<IOptions<ApiEndpointOptions>>().Value.RoutePrefix;
app.UseWorkflowsApi(routePrefix);
// Captures unhandled exceptions and returns a JSON response.
app.UseJsonSerializationErrorHandler();
app.UseWorkflows();
}
}
@@ -0,0 +1,27 @@
using Caspian.WorkflowFramework.Models;
namespace Caspian.WorkflowFramework.Contracts;
public interface IWorkflowService
{
/// <summary>
/// Create and executes a new workflow instance.
/// </summary>
/// <param name="input"></param>
/// <returns>A new <see cref="WorkflowResponse<WorkflowModel>"/> object.</returns>
/// <remarks>Create a new workflow instance and executes it and waits for it to complete or reach a suspend point.</remarks>
Task<WorkflowResponse<WorkflowModel>> CreateAndExecuteAsync(CreateAndExecuteWorkflowModel input);
/// <summary>
/// Publishes the specified event.
/// </summary>
/// <param name="input"></param>
/// <returns>A new <see cref="WorkflowResponse<WorkflowModel>"/> object.</returns>
Task<WorkflowResponse<WorkflowModel>> PublishEventAsync(PublishEventModel input);
/// <summary>
/// Inquiry last state of workflow instance.
/// </summary>
/// <param name="input"></param>
/// <returns>A new <see cref="WorkflowResponse<WorkflowModel>"/> object.</returns>
Task<WorkflowResponse<WorkflowModel>> InquiryStateAsync(InquiryStateModel input);
}
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
@@ -0,0 +1,3 @@
Terms and Conditions of Use, Reproduction and Distribution are permitted only for Caspian Company.
Copyright [2025] Caspian Company
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Elsa.Common.Models;
using JetBrains.Annotations;
namespace Caspian.WorkflowFramework.Models;
[PublicAPI]
public class CreateAndExecuteWorkflowModel
{
/// <summary>
/// Workflow name
/// </summary>
[Required]
public string? Name { get; set; }
/// <summary>
/// Workflow version
/// </summary>
[Required]
public VersionOptions Version { get; set; }
/// <summary>
/// Current user's id
/// </summary>
[Required]
public long? UserId { get; set; }
}
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Caspian.WorkflowFramework.Models;
public class InquiryStateModel
{
[Required]
public string WorkflowInstanceId { get; set; }
}
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Caspian.WorkflowFramework.Models;
[PublicAPI]
public class PublishEventModel
{
[Required]
public string WorkflowInstanceId { get; set; }
[Required]
public string EventName { get; set; }
[Required]
public string? EventData { get; set; }
}
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Caspian.WorkflowFramework.Models;
[PublicAPI]
public class WorkflowModel
{
/// <summary>
/// شناسه گردش کار
/// </summary>
public string? InstanceId { get; set; }
/// <summary>
/// نام گردش کار
/// </summary>
public string? Name { get; set; }
/// <summary>
/// اتمام گردش کار؟
/// </summary>
public bool IsComplete { get; set; }
/// <summary>
/// برای کدام کاربر؟
/// </summary>
public long? UserId { get; set; }
/// <summary>
/// داده صفحه جاری
/// </summary>
public string? CurrentPageData { get; set; }
/// <summary>
/// داده صفحه بعد
/// <remark>
/// فعلا استفاده ای نداریم
/// </remark>
/// </summary>
public string? NextPageData { get; set; }
/// <summary>
/// آدرس صفحه بعدی
/// </summary>
public string? NextPageUrl { get; set; }
/// <summary>
/// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود
/// </summary>
public string? NextPageEventName { get; set; }
}
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Caspian.WorkflowFramework.Models;
[PublicAPI]
public class WorkflowResponse<T> : WorkflowResponseBase where T : class, new()
{
public WorkflowResponse()
{
Data = new T();
}
/// <summary>
/// پاسخ
/// </summary>
public T? Data { get; set; }
}
[PublicAPI]
public class WorkflowResponseBase
{
/// <summary>
/// وضعیت پاسخ
/// </summary>
public int? Status { get; set; }
/// <summary>
/// پیام پاسخ
/// </summary>
public string? Message { get; set; }
}
@@ -0,0 +1,279 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Azure;
using Caspian.WorkflowFramework.Contracts;
using Caspian.WorkflowFramework.Models;
using Caspian.WorkflowFramework.Utility;
using Elsa.Common.Models;
using Elsa.JavaScript.Handlers;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Messages;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Win32;
using San.MongoLogger;
using Newtonsoft.Json;
using System.Diagnostics;
namespace Caspian.WorkflowFramework.Services;
public class WorkflowService : IWorkflowService, ILoggable
{
#region Fields
private readonly IServiceProvider _serviceProvider;
private readonly IWorkflowRuntime _workflowRuntime;
private readonly IWorkflowDefinitionService _workflowDefinitionService;
private readonly IWorkflowInstanceVariableManager _workflowInstanceVariableManager;
private readonly IWorkflowInstanceManager _workflowInstanceManager;
private readonly IEventPublisher _eventPublisher;
private readonly IMdbLogger<WorkflowService> _logger;
#endregion
#region Ctor
public WorkflowService(IServiceProvider serviceProvider,
IMdbLogger<WorkflowService> logger)
{
_serviceProvider = serviceProvider;
_workflowRuntime = _serviceProvider.GetRequiredService<IWorkflowRuntime>();
_workflowDefinitionService = _serviceProvider.GetRequiredService<IWorkflowDefinitionService>();
_workflowInstanceVariableManager = _serviceProvider.GetRequiredService<IWorkflowInstanceVariableManager>();
_workflowInstanceManager = _serviceProvider.GetRequiredService<IWorkflowInstanceManager>();
_eventPublisher = _serviceProvider.GetRequiredService<IEventPublisher>();
_logger = logger;
}
#endregion
#region CreateAndExecuteAsync
/// <inheritdoc />
public async Task<WorkflowResponse<WorkflowModel>> CreateAndExecuteAsync(CreateAndExecuteWorkflowModel input)
{
var sw = Stopwatch.StartNew();
try
{
_logger.Log("Start of CreateAndExecuteAsync", input);
WorkflowResponse<WorkflowModel> response;
var workflowClient = await _workflowRuntime.CreateClientAsync();
var definition = await _workflowDefinitionService
.FindWorkflowDefinitionAsync(new WorkflowDefinitionFilter()
{
Name = input.Name
});
if (definition == null)
{
var msg = $"Workflow '{input.Name}' defintion not found";
response = new WorkflowResponse<WorkflowModel>()
{
Status = 1,
Message = msg,
Data = null
};
sw.Stop();
_logger.Log("End of CreateAndExecuteAsync", WFInstanceId: response.Data?.InstanceId, response, sw.ElapsedMilliseconds);
return response;
}
var instanceInput = new Dictionary<string, object> { ["UserId"] = input.UserId };
input.Version = VersionOptions.Published;
var newInstance = await workflowClient.CreateInstanceAsync(new CreateWorkflowInstanceRequest
{
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(definition.DefinitionId, input.Version),
Input = instanceInput,
ExternalUserId = input.UserId
});
var result = await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
var variables = await _workflowInstanceVariableManager.GetVariablesAsync(result.WorkflowInstanceId);
// تبدیل به دیکشنری
var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value);
var workflowModel = dictionary.ConvertToWorkflowModel();
workflowModel.InstanceId = workflowClient.WorkflowInstanceId;
workflowModel.UserId = input.UserId;
response = new WorkflowResponse<WorkflowModel>()
{
Status = 0,
Message = "Success",
Data = workflowModel
};
sw.Stop();
_logger.Log("End of CreateAndExecuteAsync", WFInstanceId: response.Data?.InstanceId, response, sw.ElapsedMilliseconds);
return response;
}
catch (Exception ex)
{
var response = new WorkflowResponse<WorkflowModel>()
{
Status = 100,
Message = ex.Message,
Data = null
};
sw.Stop();
_logger.Log("Exception occurs in CreateAndExecuteAsync", WFInstanceId: response.Data?.InstanceId, response, sw.ElapsedMilliseconds, ex);
return response;
}
}
#endregion
#region PublishEventAsync
/// <inheritdoc />
public async Task<WorkflowResponse<WorkflowModel>> PublishEventAsync(PublishEventModel input)
{
var sw = Stopwatch.StartNew();
try
{
_logger.Log("Start of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, input);
WorkflowResponse<WorkflowModel> response;
var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId);
if (instance == null)
{
var msg = $"Workflow instance with id '{input.WorkflowInstanceId}' not found";
response = new WorkflowResponse<WorkflowModel>()
{
Status = 1,
Message = msg,
Data = null
};
sw.Stop();
_logger.Log("End of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds);
return response;
}
await _eventPublisher.PublishAsync(input.EventName, null, input.WorkflowInstanceId, null, input.EventData);
var variables = await _workflowInstanceVariableManager.GetVariablesAsync(input.WorkflowInstanceId);
// تبدیل به دیکشنری
var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value);
var workflowModel = dictionary.ConvertToWorkflowModel();
workflowModel.InstanceId = instance.Id;
workflowModel.UserId = instance.ExternalUserId;
response = new WorkflowResponse<WorkflowModel>()
{
Status = 0,
Message = "Success",
Data = workflowModel
};
sw.Stop();
_logger.Log("End of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds);
return response;
}
catch (Exception ex)
{
var response = new WorkflowResponse<WorkflowModel>()
{
Status = 100,
Message = ex.Message,
Data = null
};
sw.Stop();
_logger.Log("Exception occurs in PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds, ex);
return response;
}
}
#endregion
#region InquiryStateAsync
/// <inheritdoc />
public async Task<WorkflowResponse<WorkflowModel>> InquiryStateAsync(InquiryStateModel input)
{
var sw = Stopwatch.StartNew();
try
{
_logger.Log("Start of InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, input);
WorkflowResponse<WorkflowModel> response;
var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId);
if (instance == null)
{
var msg = $"Workflow instance with id '{input.WorkflowInstanceId}' not found";
response = new WorkflowResponse<WorkflowModel>()
{
Status = 1,
Message = msg,
Data = null
};
sw.Stop();
_logger.Log("End of InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds);
return response;
}
var variables = await _workflowInstanceVariableManager.GetVariablesAsync(input.WorkflowInstanceId);
// تبدیل به دیکشنری
var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value);
var workflowModel = dictionary.ConvertToWorkflowModel();
workflowModel.InstanceId = instance.Id;
workflowModel.UserId = instance.ExternalUserId;
response = new WorkflowResponse<WorkflowModel>()
{
Status = 0,
Message = "Success",
Data = workflowModel
};
sw.Stop();
_logger.Log("End of InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds);
return response;
}
catch (Exception ex)
{
var response = new WorkflowResponse<WorkflowModel>()
{
Status = 100,
Message = ex.Message,
Data = null
};
sw.Stop();
_logger.Log("Exception occurs in InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds, ex);
return response;
}
}
#endregion
}
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Caspian.WorkflowFramework.Models;
namespace Caspian.WorkflowFramework.Utility;
public static class ConversionExtensions
{
public static WorkflowModel ConvertToWorkflowModel(this Dictionary<string, object?> dictionary)
{
var workflowModel = new WorkflowModel();
if (dictionary == null || dictionary.Count == 0)
return workflowModel;
//Map dictionary values to WorkflowModel properties
workflowModel.InstanceId = dictionary.ContainsKey(nameof(workflowModel.InstanceId))
? dictionary[nameof(workflowModel.InstanceId)]?.ToString()
: null;
workflowModel.Name = dictionary.ContainsKey(nameof(workflowModel.Name))
? dictionary[nameof(workflowModel.Name)]?.ToString()
: null;
workflowModel.IsComplete = dictionary.ContainsKey(nameof(workflowModel.IsComplete))
? bool.TryParse(dictionary[nameof(workflowModel.IsComplete)]?.ToString(), out var isComplete) && isComplete
: false;
workflowModel.UserId = dictionary.ContainsKey(nameof(workflowModel.UserId))
? long.TryParse(dictionary[nameof(workflowModel.UserId)]?.ToString(), out var userId) ? userId : (long?)null
: null;
//workflowModel.CurrentPageData = dictionary.ContainsKey(nameof(workflowModel.CurrentPageData))
// ? (object?)dictionary[nameof(workflowModel.CurrentPageData)]
// : null;
workflowModel.CurrentPageData = dictionary.ContainsKey(nameof(workflowModel.CurrentPageData))
? dictionary[nameof(workflowModel.CurrentPageData)]?.ToString()
: null;
//workflowModel.NextPageData = dictionary.ContainsKey(nameof(workflowModel.NextPageData))
// ? (object?)dictionary[nameof(workflowModel.NextPageData)]
// : null;
workflowModel.NextPageData = dictionary.ContainsKey(nameof(workflowModel.NextPageData))
? dictionary[nameof(workflowModel.NextPageData)]?.ToString()
: null;
workflowModel.NextPageUrl = dictionary.ContainsKey(nameof(workflowModel.NextPageUrl))
? dictionary[nameof(workflowModel.NextPageUrl)]?.ToString()
: null;
workflowModel.NextPageEventName = dictionary.ContainsKey(nameof(workflowModel.NextPageEventName))
? dictionary[nameof(workflowModel.NextPageEventName)]?.ToString()
: null;
return workflowModel;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1 @@
nuget pack Caspian.WorkflowFramework.nuspec -IncludeReferencedProjects
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,35 @@
# Caspian.NextBank.Workflow
Workflows for project "NextBankParsian"
## Installation
```bash
dotnet add package Caspian.NextBank.Workflow
```
```bash
install-package Caspian.NextBank.Workflow
```
## Usage Example
In your Program.cs :
```csharp
builder.Services.AddMongoLogger<LogModel>(builder.Configuration);
builder.AddWorkflowServices("DefaultConnection");
var app = builder.Build();
app.UseMongoLogger<LogModel>();
app.UseWorkflow();
```
Then use service "WfService"
## Features
- Start new instance of every workflow
- Publish event and get next step information
- Get last status of workflow instance
## Version History
### 1.0.0-alfa
- Initial prerelease
+28 -1
View File
@@ -5,6 +5,33 @@
"Microsoft.Hosting.Lifetime": "Information"
}
},
"LogDatabaseSettings": {
"ConnectionString": "mongodb://localhost",
"DatabaseName": "Elsa.Server.Web.DB",
"LogsCollectionName": "Col",
"BatchSize": 0,
"RollingInterval": "Daily",
"MaskList": [
"code",
"depositNumber",
"username",
"customerNo",
"password",
"sessionId",
"mainAccount",
"accountNumber",
"owners",
"balance",
"accountNo",
"depositUniqueId",
"externalRefList",
"externalref",
"sameSignatureDepositNumber",
"mobileNumber",
"destinationNumber",
"cardNumber"
]
},
"HostBuilder": {
"reloadConfigOnChange": true
},
@@ -24,7 +51,7 @@
"RabbitMq": "", //"amqp://guest:guest@localhost:5672",
"Redis": "" //"localhost:6379,abortConnect=false"
},
"DatabaseProvider": "SqlServer",//"Sqlite",
"DatabaseProvider": "SqlServer", //"Sqlite",
"Multitenancy": {
"Tenants": [
{
@@ -7,6 +7,33 @@
"OpenTelemetry": "Debug"
}
},
"LogDatabaseSettings": {
"ConnectionString": "mongodb://localhost",
"DatabaseName": "Elsa.ServerAndStudio.Web.DB",
"LogsCollectionName": "Col",
"BatchSize": 0,
"RollingInterval": "Daily",
"MaskList": [
"code",
"depositNumber",
"username",
"customerNo",
"password",
"sessionId",
"mainAccount",
"accountNumber",
"owners",
"balance",
"accountNo",
"depositUniqueId",
"externalRefList",
"externalref",
"sameSignatureDepositNumber",
"mobileNumber",
"destinationNumber",
"cardNumber"
]
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Sqlite": "", //"Data Source=elsa.sqlite.db;Cache=Shared;",