diff --git a/Directory.Packages.props b/Directory.Packages.props
index 8b65fbf..03dedda 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,174 +1,178 @@
-
- true
- true
-
-
- 3.5.0-preview.1092
- 9.0.7
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ true
+ true
+
+
+ 3.5.0-preview.1092
+ 9.0.7
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Elsa.sln b/Elsa.sln
index 29feedd..5baed45 100644
--- a/Elsa.sln
+++ b/Elsa.sln
@@ -1,4 +1,3 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34003.232
@@ -77,14 +76,14 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{986E5482-0482-448C-B9E4-EC67A9474B85}"
ProjectSection(SolutionItems) = preProject
docker\.dockerignore = docker\.dockerignore
+ docker\docker-compose-datadog+otel-collector.yml = docker\docker-compose-datadog+otel-collector.yml
docker\docker-compose.yml = docker\docker-compose.yml
docker\ElsaServer-Datadog.Dockerfile = docker\ElsaServer-Datadog.Dockerfile
docker\ElsaServer.Dockerfile = docker\ElsaServer.Dockerfile
docker\ElsaServerAndStudio.Dockerfile = docker\ElsaServerAndStudio.Dockerfile
docker\ElsaStudio.Dockerfile = docker\ElsaStudio.Dockerfile
- docker\otel-collector-config.yaml = docker\otel-collector-config.yaml
docker\init-db-postgres.sh = docker\init-db-postgres.sh
- docker\docker-compose-datadog+otel-collector.yml = docker\docker-compose-datadog+otel-collector.yml
+ docker\otel-collector-config.yaml = docker\otel-collector-config.yaml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Elasticsearch", "src\modules\Elsa.Elasticsearch\Elsa.Elasticsearch.csproj", "{3246883E-2FA7-4B4A-BDC5-99039A2869BC}"
@@ -313,18 +312,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "migrations", "migrations",
scripts\migrations\efcore-3.1.sh = scripts\migrations\efcore-3.1.sh
scripts\migrations\efcore-3.2.sh = scripts\migrations\efcore-3.2.sh
scripts\migrations\efcore-3.3.sh = scripts\migrations\efcore-3.3.sh
- scripts\migrations\README.md = scripts\migrations\README.md
scripts\migrations\efcore-3.4.sh = scripts\migrations\efcore-3.4.sh
scripts\migrations\efcore-3.5.sh = scripts\migrations\efcore-3.5.sh
+ scripts\migrations\README.md = scripts\migrations\README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{AA5ACF15-8647-4AC9-B9F5-73FCB92BE7E3}"
ProjectSection(SolutionItems) = preProject
scripts\docker\build-and-run-all-in-one-web-docker.sh = scripts\docker\build-and-run-all-in-one-web-docker.sh
- scripts\docker\docker-compose.yml = scripts\docker\docker-compose.yml
- scripts\docker\docker-run-all-in-one-web.ps1 = scripts\docker\docker-run-all-in-one-web.ps1
scripts\docker\docker-compose-citus.yml = scripts\docker\docker-compose-citus.yml
scripts\docker\docker-compose-yugabyte.yml = scripts\docker\docker-compose-yugabyte.yml
+ scripts\docker\docker-compose.yml = scripts\docker\docker-compose.yml
+ scripts\docker\docker-run-all-in-one-web.ps1 = scripts\docker\docker-run-all-in-one-web.ps1
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "elsa-studio", "elsa-studio", "{DFFE6199-B5A7-4BA8-A5E1-77D0426C6468}"
@@ -388,11 +387,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "adr", "adr", "{0A04B1FD-06C
ProjectSection(SolutionItems) = preProject
doc\adr\0001-record-architecture-decisions.md = doc\adr\0001-record-architecture-decisions.md
doc\adr\0002-fault-propagation-from-child-to-parent-activities.md = doc\adr\0002-fault-propagation-from-child-to-parent-activities.md
- doc\adr\toc.md = doc\adr\toc.md
- doc\adr\graph.dot = doc\adr\graph.dot
doc\adr\0003-direct-bookmark-management-in-workflowexecutioncontext.md = doc\adr\0003-direct-bookmark-management-in-workflowexecutioncontext.md
doc\adr\0004-activity-execution-snapshots.md = doc\adr\0004-activity-execution-snapshots.md
doc\adr\0005-tenant-deleted-event.md = doc\adr\0005-tenant-deleted-event.md
+ doc\adr\graph.dot = doc\adr\graph.dot
+ doc\adr\toc.md = doc\adr\toc.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bounty", "bounty", "{9B80A705-2E31-4012-964A-83963DCDB384}"
@@ -416,6 +415,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.IO.Compression", "src\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.IO.Http", "src\modules\Elsa.IO.Http\Elsa.IO.Http.csproj", "{C583AF05-D517-4B7F-8955-6B61500ED3D8}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Caspian.WorkflowFramework", "src\apps\Caspian.WorkflowFramework\Caspian.WorkflowFramework.csproj", "{A0F413B7-FA0F-472D-8BC2-6193DA440072}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Caspian.WorkflowFramework.Sample", "src\apps\Caspian.WorkflowFramework.Sample\Caspian.WorkflowFramework.Sample.csproj", "{C260D75F-19EB-4982-A98B-D8E126776910}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -709,14 +712,14 @@ Global
{99B171E6-0248-4402-836D-98947CD63772}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99B171E6-0248-4402-836D-98947CD63772}.Release|Any CPU.Build.0 = Release|Any CPU
{4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.Build.0 = Release|Any CPU
- {832675FA-C597-4554-AE6B-18F189198A1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {832675FA-C597-4554-AE6B-18F189198A1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {832675FA-C597-4554-AE6B-18F189198A1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {832675FA-C597-4554-AE6B-18F189198A1F}.Release|Any CPU.Build.0 = Release|Any CPU
- {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {832675FA-C597-4554-AE6B-18F189198A1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {832675FA-C597-4554-AE6B-18F189198A1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {832675FA-C597-4554-AE6B-18F189198A1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {832675FA-C597-4554-AE6B-18F189198A1F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Release|Any CPU.Build.0 = Release|Any CPU
@@ -864,6 +867,14 @@ Global
{C583AF05-D517-4B7F-8955-6B61500ED3D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C583AF05-D517-4B7F-8955-6B61500ED3D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C583AF05-D517-4B7F-8955-6B61500ED3D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A0F413B7-FA0F-472D-8BC2-6193DA440072}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A0F413B7-FA0F-472D-8BC2-6193DA440072}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A0F413B7-FA0F-472D-8BC2-6193DA440072}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A0F413B7-FA0F-472D-8BC2-6193DA440072}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C260D75F-19EB-4982-A98B-D8E126776910}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C260D75F-19EB-4982-A98B-D8E126776910}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C260D75F-19EB-4982-A98B-D8E126776910}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C260D75F-19EB-4982-A98B-D8E126776910}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -885,6 +896,7 @@ Global
{C9539BD8-D2AE-4A8D-8281-71A05B3FBF31} = {B08B4E00-C2AB-48F3-8389-449F42AEF179}
{169E2C9B-6687-427F-A278-30BF849BFEDC} = {B08B4E00-C2AB-48F3-8389-449F42AEF179}
{2DD5D66B-85E9-4AF9-911C-C9F963234159} = {B08B4E00-C2AB-48F3-8389-449F42AEF179}
+ {8A050229-DB79-4E0B-9AFF-7565E87F2954} = {B08B4E00-C2AB-48F3-8389-449F42AEF179}
{C237BA1A-3A7D-4AB2-BE09-2696F3C082A4} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
{80529478-A383-4FEA-B744-C71264969E9A} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76}
{39CD855E-83B1-4A96-93F7-01608211EBE3} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
@@ -958,10 +970,10 @@ Global
{94A61AD7-2A2B-40DB-81F3-C59D596958A8} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
{4B598AF7-BD7D-4544-A274-2CDDD98F4167} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
{99B171E6-0248-4402-836D-98947CD63772} = {1B8D5897-902E-4632-8698-E89CAF3DDF54}
- {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D} = {1B8D5897-902E-4632-8698-E89CAF3DDF54}
- {832675FA-C597-4554-AE6B-18F189198A1F} = {1B8D5897-902E-4632-8698-E89CAF3DDF54}
- {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
- {5ADDDFB1-E59B-4097-97B7-8C24E2D60463} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}
+ {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D} = {1B8D5897-902E-4632-8698-E89CAF3DDF54}
+ {832675FA-C597-4554-AE6B-18F189198A1F} = {1B8D5897-902E-4632-8698-E89CAF3DDF54}
+ {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
+ {5ADDDFB1-E59B-4097-97B7-8C24E2D60463} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}
{97C7E531-9D5F-43FD-AA19-BF24DA13B612} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}
{690B0274-291F-4D9E-BA76-54EFF7D3E4BC} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}
@@ -1007,7 +1019,6 @@ Global
{FA2B2B44-7E3E-444B-A157-7526B376E9CA} = {9DF81143-2F39-4CFB-9DA3-902279010622}
{DF0CA7E2-F5DF-40ED-91B6-748767084FC7} = {9DF81143-2F39-4CFB-9DA3-902279010622}
{DB20B168-CD90-41F2-BE08-3D8C6F142459} = {9DF81143-2F39-4CFB-9DA3-902279010622}
- {8A050229-DB79-4E0B-9AFF-7565E87F2954} = {B08B4E00-C2AB-48F3-8389-449F42AEF179}
{0A04B1FD-06C0-4271-A910-A08C263DBC44} = {0354F050-3992-4DD4-B0EE-5FBA04AC72B6}
{9B80A705-2E31-4012-964A-83963DCDB384} = {0354F050-3992-4DD4-B0EE-5FBA04AC72B6}
{CD7DC0D1-FFDC-417A-89BE-7F32408F583E} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
@@ -1017,6 +1028,8 @@ Global
{EB24F9FE-D7BD-4FCC-907E-AE400288C2A5} = {7FD1FD1E-5778-4065-AAA5-1F878129EF77}
{9CA02818-F7EB-4A0B-B27B-BC74ACD499C9} = {7FD1FD1E-5778-4065-AAA5-1F878129EF77}
{C583AF05-D517-4B7F-8955-6B61500ED3D8} = {7FD1FD1E-5778-4065-AAA5-1F878129EF77}
+ {A0F413B7-FA0F-472D-8BC2-6193DA440072} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}
+ {C260D75F-19EB-4982-A98B-D8E126776910} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.csproj b/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.csproj
new file mode 100644
index 0000000..9bf6c98
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.http b/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.http
new file mode 100644
index 0000000..c613358
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.http
@@ -0,0 +1,6 @@
+@Caspian.WorkflowFramework.Sample_HostAddress = http://localhost:5008
+
+GET {{Caspian.WorkflowFramework.Sample_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/Controllers/WorkflowController.cs b/src/apps/Caspian.WorkflowFramework.Sample/Controllers/WorkflowController.cs
new file mode 100644
index 0000000..379e2f4
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/Controllers/WorkflowController.cs
@@ -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 _logger;
+ #endregion
+
+ #region Ctor
+ public WorkflowController(IWorkflowService workflowService,
+ IMdbLogger logger)
+ {
+ _workflowService = workflowService;
+ _logger = logger;
+ }
+ #endregion
+
+ #region Start
+ [HttpPost("Start")]
+ public async Task Start(CreateAndExecuteWorkflowModel input)
+ {
+ _logger.Log("Start of StartWorkflow action", JsonConvert.SerializeObject(input));
+
+ //var model = _mapper.Map(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 Do(PublishEventModel input)
+ {
+ _logger.Log("Start of Do action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(input));
+
+ //var model = _mapper.Map(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 GetState(InquiryStateModel input)
+ {
+ _logger.Log("Start of GetState action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(input));
+
+ //var model = _mapper.Map(input);
+ var result = await _workflowService.InquiryStateAsync(input);
+
+ _logger.Log("End of GetState action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(result));
+
+ return Json(result);
+ }
+ #endregion
+}
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/ExcludeExternalControllersFilter.cs b/src/apps/Caspian.WorkflowFramework.Sample/ExcludeExternalControllersFilter.cs
new file mode 100644
index 0000000..540df78
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/ExcludeExternalControllersFilter.cs
@@ -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()
+ .FirstOrDefault()?.ControllerTypeInfo?.AsType();
+
+ return controllerType != null && controllerType.Assembly != currentAssembly;
+ })
+ .Select(apiDesc => "/" + apiDesc.RelativePath.TrimEnd('/'))
+ .ToList();
+
+ foreach (var path in keysToRemove)
+ {
+ swaggerDoc.Paths.Remove(path);
+ }
+ }
+}
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/FodyWeavers.xml b/src/apps/Caspian.WorkflowFramework.Sample/FodyWeavers.xml
new file mode 100644
index 0000000..00e1d9a
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/Program.cs b/src/apps/Caspian.WorkflowFramework.Sample/Program.cs
new file mode 100644
index 0000000..16d0e68
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/Program.cs
@@ -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();
+ 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(builder.Configuration);
+
+var app = builder.Build();
+
+app.UseMongoLogger();
+
+// 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();
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/Properties/launchSettings.json b/src/apps/Caspian.WorkflowFramework.Sample/Properties/launchSettings.json
new file mode 100644
index 0000000..afcfdbe
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/Properties/launchSettings.json
@@ -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"
+ }
+ }
+ }
+}
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/appsettings.Development.json b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.Development.json
new file mode 100644
index 0000000..84dfb83
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ //"Microsoft.EntityFrameworkCore.Database.Command": "Warning" // Disable SQL command logging
+ }
+ }
+}
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/appsettings.json b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.json
new file mode 100644
index 0000000..eab431b
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.json
@@ -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"
+ // }
+ // ]
+ //}
+}
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.Development.json b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.Development.json
new file mode 100644
index 0000000..84dfb83
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ //"Microsoft.EntityFrameworkCore.Database.Command": "Warning" // Disable SQL command logging
+ }
+ }
+}
diff --git a/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.json b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.json
new file mode 100644
index 0000000..5ab381e
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.json
@@ -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"
+ }
+ ]
+ }
+}
diff --git a/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.csproj b/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.csproj
new file mode 100644
index 0000000..d9a20dd
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.csproj
@@ -0,0 +1,72 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.nuspec b/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.nuspec
new file mode 100644
index 0000000..0c750ba
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.nuspec
@@ -0,0 +1,130 @@
+
+
+
+ Caspian.WorkflowFramework
+ 1.0.0-preview34
+ Digital Banking Team
+ Caspian
+ Caspian workflow framework
+ https://caspco.ir
+ package-icon.png
+ readme.md
+ caspian, nextbank, workflow
+ LICENSE.txt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/apps/Caspian.WorkflowFramework/Config/DependencyInjection.cs b/src/apps/Caspian.WorkflowFramework/Config/DependencyInjection.cs
new file mode 100644
index 0000000..1c0dbb4
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Config/DependencyInjection.cs
@@ -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(configuration["AppRole"]! ?? "Default");
+ var sqlDatabaseProvider = Enum.Parse(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));
+ TypeAliasRegistry.RegisterAlias("OrderReceivedConsumerFactory", typeof(GenericConsumerFactory));
+
+ 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();
+
+ builder.Services.AddMongoLogger(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());
+
+ if (useMassTransit)
+ management.UseMassTransitDispatcher();
+
+ if (useCaching)
+ management.UseCache();
+
+ management.SetDefaultLogPersistenceMode(LogPersistenceMode.Inherit);
+ management.UseReadOnlyMode(useReadOnlyMode);
+ management.AddVariableTypeAndAlias("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();
+ runtime.WorkflowExecutionLogStore = sp => sp.GetRequiredService();
+ }
+
+ 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();
+ })
+ .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();
+ 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("MySql");
+ client.Register("PostgreSql");
+ client.Register("Sqlite");
+ client.Register("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();
+
+ 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()
+ .Append()
+ .Append()
+ .Append();
+ });
+
+ 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();
+ //ConfigureForTest?.Invoke(elsa);
+ });
+
+ // Obfuscate HTTP request headers.
+ builder.Services.AddActivityStateFilter();
+
+ // Optionally configure recurring tasks using alternative schedules.
+ builder.Services.Configure(options =>
+ {
+ options.Schedule.ConfigureTask(TimeSpan.FromSeconds(300));
+ options.Schedule.ConfigureTask(TimeSpan.FromSeconds(300));
+ options.Schedule.ConfigureTask(TimeSpan.FromHours(4));
+ options.Schedule.ConfigureTask(TimeSpan.FromSeconds(15));
+ });
+
+ builder.Services.Configure(options => { options.InactivityThreshold = TimeSpan.FromSeconds(15); });
+ builder.Services.Configure(options => options.Ttl = TimeSpan.FromSeconds(10));
+ builder.Services.Configure(options => options.CacheDuration = TimeSpan.FromDays(1));
+ builder.Services.Configure(options => options.DefaultIncidentStrategy = typeof(ContinueWithIncidentsStrategy));
+ builder.Services.AddHealthChecks();
+ builder.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("*")));
+
+
+ builder.Services.AddScoped();
+ }
+
+ /////
+ ///// The main entry point for the application made public for end to end testing.
+ /////
+ //[UsedImplicitly]
+ //public partial class Program
+ //{
+ // ///
+ // /// Set by the test runner to configure the module for testing.
+ // ///
+ // public static Action? ConfigureForTest { get; set; }
+ //}
+
+ public static void UseWorkflowFramework(this IApplicationBuilder app)
+ {
+ //try
+ //{
+ // app.UseMongoLogger();
+ //}
+ //catch (Exception ex)
+ //{
+ // var d = ex;
+ //}
+
+ // Configure the pipeline.
+ var env = app.ApplicationServices.GetRequiredService();
+ 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>().Value.RoutePrefix;
+ app.UseWorkflowsApi(routePrefix);
+
+ // Captures unhandled exceptions and returns a JSON response.
+ app.UseJsonSerializationErrorHandler();
+
+ app.UseWorkflows();
+ }
+}
+
diff --git a/src/apps/Caspian.WorkflowFramework/Contracts/IWorkflowService.cs b/src/apps/Caspian.WorkflowFramework/Contracts/IWorkflowService.cs
new file mode 100644
index 0000000..0b2db48
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Contracts/IWorkflowService.cs
@@ -0,0 +1,27 @@
+using Caspian.WorkflowFramework.Models;
+
+namespace Caspian.WorkflowFramework.Contracts;
+public interface IWorkflowService
+{
+ ///
+ /// Create and executes a new workflow instance.
+ ///
+ ///
+ /// A new object.
+ /// Create a new workflow instance and executes it and waits for it to complete or reach a suspend point.
+ Task> CreateAndExecuteAsync(CreateAndExecuteWorkflowModel input);
+
+ ///
+ /// Publishes the specified event.
+ ///
+ ///
+ /// A new object.
+ Task> PublishEventAsync(PublishEventModel input);
+
+ ///
+ /// Inquiry last state of workflow instance.
+ ///
+ ///
+ /// A new object.
+ Task> InquiryStateAsync(InquiryStateModel input);
+}
diff --git a/src/apps/Caspian.WorkflowFramework/FodyWeavers.xml b/src/apps/Caspian.WorkflowFramework/FodyWeavers.xml
new file mode 100644
index 0000000..00e1d9a
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/apps/Caspian.WorkflowFramework/LICENSE.txt b/src/apps/Caspian.WorkflowFramework/LICENSE.txt
new file mode 100644
index 0000000..df73626
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/LICENSE.txt
@@ -0,0 +1,3 @@
+Terms and Conditions of Use, Reproduction and Distribution are permitted only for Caspian Company.
+
+Copyright [2025] Caspian Company
\ No newline at end of file
diff --git a/src/apps/Caspian.WorkflowFramework/Models/CreateAndExecuteWorkflowModel.cs b/src/apps/Caspian.WorkflowFramework/Models/CreateAndExecuteWorkflowModel.cs
new file mode 100644
index 0000000..917adc5
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Models/CreateAndExecuteWorkflowModel.cs
@@ -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
+{
+ ///
+ /// Workflow name
+ ///
+ [Required]
+ public string? Name { get; set; }
+
+ ///
+ /// Workflow version
+ ///
+ [Required]
+ public VersionOptions Version { get; set; }
+
+ ///
+ /// Current user's id
+ ///
+ [Required]
+ public long? UserId { get; set; }
+}
diff --git a/src/apps/Caspian.WorkflowFramework/Models/InquiryStateModel.cs b/src/apps/Caspian.WorkflowFramework/Models/InquiryStateModel.cs
new file mode 100644
index 0000000..a549da8
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Models/InquiryStateModel.cs
@@ -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; }
+}
diff --git a/src/apps/Caspian.WorkflowFramework/Models/PublishEventModel.cs b/src/apps/Caspian.WorkflowFramework/Models/PublishEventModel.cs
new file mode 100644
index 0000000..10cd5ad
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Models/PublishEventModel.cs
@@ -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; }
+}
diff --git a/src/apps/Caspian.WorkflowFramework/Models/WorkflowModel.cs b/src/apps/Caspian.WorkflowFramework/Models/WorkflowModel.cs
new file mode 100644
index 0000000..36fadfe
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Models/WorkflowModel.cs
@@ -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
+{
+ ///
+ /// شناسه گردش کار
+ ///
+ public string? InstanceId { get; set; }
+
+ ///
+ /// نام گردش کار
+ ///
+ public string? Name { get; set; }
+
+ ///
+ /// اتمام گردش کار؟
+ ///
+ public bool IsComplete { get; set; }
+
+ ///
+ /// برای کدام کاربر؟
+ ///
+ public long? UserId { get; set; }
+
+ ///
+ /// داده صفحه جاری
+ ///
+ public string? CurrentPageData { get; set; }
+
+ ///
+ /// داده صفحه بعد
+ ///
+ /// فعلا استفاده ای نداریم
+ ///
+ ///
+ public string? NextPageData { get; set; }
+
+ ///
+ /// آدرس صفحه بعدی
+ ///
+ public string? NextPageUrl { get; set; }
+
+ ///
+ /// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود
+ ///
+ public string? NextPageEventName { get; set; }
+}
diff --git a/src/apps/Caspian.WorkflowFramework/Models/WorkflowResponse.cs b/src/apps/Caspian.WorkflowFramework/Models/WorkflowResponse.cs
new file mode 100644
index 0000000..494b5b3
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Models/WorkflowResponse.cs
@@ -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 : WorkflowResponseBase where T : class, new()
+{
+ public WorkflowResponse()
+ {
+ Data = new T();
+ }
+
+ ///
+ /// پاسخ
+ ///
+ public T? Data { get; set; }
+}
+
+[PublicAPI]
+public class WorkflowResponseBase
+{
+ ///
+ /// وضعیت پاسخ
+ ///
+ public int? Status { get; set; }
+
+ ///
+ /// پیام پاسخ
+ ///
+ public string? Message { get; set; }
+}
diff --git a/src/apps/Caspian.WorkflowFramework/Services/WorkflowService.cs b/src/apps/Caspian.WorkflowFramework/Services/WorkflowService.cs
new file mode 100644
index 0000000..7ca42ac
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Services/WorkflowService.cs
@@ -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 _logger;
+ #endregion
+
+ #region Ctor
+ public WorkflowService(IServiceProvider serviceProvider,
+ IMdbLogger logger)
+ {
+ _serviceProvider = serviceProvider;
+ _workflowRuntime = _serviceProvider.GetRequiredService();
+ _workflowDefinitionService = _serviceProvider.GetRequiredService();
+ _workflowInstanceVariableManager = _serviceProvider.GetRequiredService();
+ _workflowInstanceManager = _serviceProvider.GetRequiredService();
+ _eventPublisher = _serviceProvider.GetRequiredService();
+ _logger = logger;
+ }
+ #endregion
+
+ #region CreateAndExecuteAsync
+ ///
+ public async Task> CreateAndExecuteAsync(CreateAndExecuteWorkflowModel input)
+ {
+ var sw = Stopwatch.StartNew();
+ try
+ {
+ _logger.Log("Start of CreateAndExecuteAsync", input);
+
+ WorkflowResponse 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()
+ {
+ 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 { ["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()
+ {
+ 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()
+ {
+ 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
+ ///
+ public async Task> PublishEventAsync(PublishEventModel input)
+ {
+ var sw = Stopwatch.StartNew();
+ try
+ {
+ _logger.Log("Start of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, input);
+
+ WorkflowResponse response;
+
+ var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId);
+
+ if (instance == null)
+ {
+ var msg = $"Workflow instance with id '{input.WorkflowInstanceId}' not found";
+ response = new WorkflowResponse()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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
+ ///
+ public async Task> InquiryStateAsync(InquiryStateModel input)
+ {
+ var sw = Stopwatch.StartNew();
+ try
+ {
+ _logger.Log("Start of InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, input);
+
+ WorkflowResponse response;
+
+ var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId);
+
+ if (instance == null)
+ {
+ var msg = $"Workflow instance with id '{input.WorkflowInstanceId}' not found";
+ response = new WorkflowResponse()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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
+}
diff --git a/src/apps/Caspian.WorkflowFramework/Utility/ConversionExtensions.cs b/src/apps/Caspian.WorkflowFramework/Utility/ConversionExtensions.cs
new file mode 100644
index 0000000..b917ae1
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/Utility/ConversionExtensions.cs
@@ -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 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;
+ }
+}
diff --git a/src/apps/Caspian.WorkflowFramework/favicon.ico b/src/apps/Caspian.WorkflowFramework/favicon.ico
new file mode 100644
index 0000000..f39a7b9
Binary files /dev/null and b/src/apps/Caspian.WorkflowFramework/favicon.ico differ
diff --git a/src/apps/Caspian.WorkflowFramework/gallery-icon.png b/src/apps/Caspian.WorkflowFramework/gallery-icon.png
new file mode 100644
index 0000000..c401de4
Binary files /dev/null and b/src/apps/Caspian.WorkflowFramework/gallery-icon.png differ
diff --git a/src/apps/Caspian.WorkflowFramework/nuget_command.txt b/src/apps/Caspian.WorkflowFramework/nuget_command.txt
new file mode 100644
index 0000000..7e09d5e
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/nuget_command.txt
@@ -0,0 +1 @@
+nuget pack Caspian.WorkflowFramework.nuspec -IncludeReferencedProjects
\ No newline at end of file
diff --git a/src/apps/Caspian.WorkflowFramework/package-icon.png b/src/apps/Caspian.WorkflowFramework/package-icon.png
new file mode 100644
index 0000000..05adb12
Binary files /dev/null and b/src/apps/Caspian.WorkflowFramework/package-icon.png differ
diff --git a/src/apps/Caspian.WorkflowFramework/readme.md b/src/apps/Caspian.WorkflowFramework/readme.md
new file mode 100644
index 0000000..c1954a1
--- /dev/null
+++ b/src/apps/Caspian.WorkflowFramework/readme.md
@@ -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(builder.Configuration);
+
+builder.AddWorkflowServices("DefaultConnection");
+
+var app = builder.Build();
+
+app.UseMongoLogger();
+
+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
\ No newline at end of file
diff --git a/src/apps/Elsa.Server.Web/appsettings.json b/src/apps/Elsa.Server.Web/appsettings.json
index 737250e..818f03e 100644
--- a/src/apps/Elsa.Server.Web/appsettings.json
+++ b/src/apps/Elsa.Server.Web/appsettings.json
@@ -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": [
{
diff --git a/src/apps/Elsa.ServerAndStudio.Web/appsettings.json b/src/apps/Elsa.ServerAndStudio.Web/appsettings.json
index 6878f72..c2f42a4 100644
--- a/src/apps/Elsa.ServerAndStudio.Web/appsettings.json
+++ b/src/apps/Elsa.ServerAndStudio.Web/appsettings.json
@@ -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;",