diff --git a/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj b/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj index 542f35b82..b68695e29 100644 --- a/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj +++ b/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj @@ -6,6 +6,7 @@ enable Linux false + latest diff --git a/src/bundles/Elsa.Server.Web/Endpoints/DynamicWorkflows/Post/Endpoint.cs b/src/bundles/Elsa.Server.Web/Endpoints/DynamicWorkflows/Post/Endpoint.cs new file mode 100644 index 000000000..9375aa404 --- /dev/null +++ b/src/bundles/Elsa.Server.Web/Endpoints/DynamicWorkflows/Post/Endpoint.cs @@ -0,0 +1,38 @@ +using System.Net; +using Elsa.Abstractions; +using Elsa.Http; +using Elsa.Workflows.Activities; +using Elsa.Workflows.Models; +using Elsa.Workflows.Runtime.Contracts; +using Elsa.Workflows.Runtime.Options; + +namespace Elsa.Server.Web.Endpoints.DynamicWorkflows.Post; + +public class Post(IWorkflowRegistry workflowRegistry, IWorkflowRuntime workflowRuntime) : ElsaEndpointWithoutRequest +{ + public override void Configure() + { + Post("/dynamic-workflows"); + AllowAnonymous(); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var workflow = new Workflow + { + Identity = new WorkflowIdentity("DynamicWorkflow1", 1, "DynamicWorkflow1:v1"), + Root = new Sequence + { + Activities = + { + new WriteLine("Step 1"), + new WriteLine("Step 2"), + new WriteLine("Step 3") + } + } + }; + + await workflowRegistry.RegisterAsync(workflow, ct); + await workflowRuntime.StartWorkflowAsync("DynamicWorkflow1", new StartWorkflowRuntimeOptions()); + } +} \ No newline at end of file diff --git a/src/bundles/Elsa.Server.Web/Program.cs b/src/bundles/Elsa.Server.Web/Program.cs index 229c9380f..2bb5cc58c 100644 --- a/src/bundles/Elsa.Server.Web/Program.cs +++ b/src/bundles/Elsa.Server.Web/Program.cs @@ -228,6 +228,7 @@ services elsa.InstallDropIns(options => options.DropInRootDirectory = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "DropIns")); elsa.AddSwagger(); + elsa.AddFastEndpointsAssembly(); }); services.AddHealthChecks(); diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs index 3e1adc33f..fb428d9b1 100644 --- a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs +++ b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs @@ -167,6 +167,7 @@ public class WorkflowManagementFeature : FeatureBase .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() diff --git a/src/modules/Elsa.Workflows.Management/Materializers/TypedWorkflowMaterializer.cs b/src/modules/Elsa.Workflows.Management/Materializers/TypedWorkflowMaterializer.cs new file mode 100644 index 000000000..e27be88f9 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Materializers/TypedWorkflowMaterializer.cs @@ -0,0 +1,32 @@ +using Elsa.Workflows.Activities; +using Elsa.Workflows.Contracts; +using Elsa.Workflows.Management.Contracts; +using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Management.Mappers; +using Elsa.Workflows.Models; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Workflows.Management.Materializers; + +/// +/// A workflow materializer that deserializes workflows created in C# code. +/// +public class TypedWorkflowMaterializer(WorkflowDefinitionMapper workflowDefinitionMapper) : IWorkflowMaterializer +{ + /// + /// The name of the materializer. + /// + public const string MaterializerName = "Typed"; + + /// + public string Name => MaterializerName; + + /// + public ValueTask MaterializeAsync(WorkflowDefinition definition, CancellationToken cancellationToken) + { + var workflow = ToWorkflow(definition); + return new ValueTask(workflow); + } + + private Workflow ToWorkflow(WorkflowDefinition definition) => workflowDefinitionMapper.Map(definition); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionService.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionService.cs index e6e5f960e..88e80ce1e 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionService.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionService.cs @@ -35,12 +35,12 @@ public class WorkflowDefinitionService : IWorkflowDefinitionService public async Task MaterializeWorkflowAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default) { var materializers = _materializers(); - var provider = materializers.FirstOrDefault(x => x.Name == definition.MaterializerName); + var materializer = materializers.FirstOrDefault(x => x.Name == definition.MaterializerName); - if (provider == null) + if (materializer == null) throw new Exception("Provider not found"); - var workflow = await provider.MaterializeAsync(definition, cancellationToken); + var workflow = await materializer.MaterializeAsync(definition, cancellationToken); var graph = (await _activityVisitor.VisitAsync(workflow, cancellationToken)).Flatten().ToList(); // Assign identities. diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionStorePopulator.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionStorePopulator.cs index b144e9314..62f86a3c7 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionStorePopulator.cs +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionStorePopulator.cs @@ -1,4 +1,5 @@ using Elsa.Workflows.Management.Contracts; +using Elsa.Workflows.Runtime.Models; namespace Elsa.Workflows.Runtime.Contracts; @@ -12,4 +13,11 @@ public interface IWorkflowDefinitionStorePopulator /// /// The cancellation token. Task PopulateStoreAsync(CancellationToken cancellationToken = default); + + /// + /// Adds a workflow definition to the store. + /// + /// A materialized workflow. + /// An optional cancellation token. + Task AddAsync(MaterializedWorkflow materializedWorkflow, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowRegister.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowRegister.cs new file mode 100644 index 000000000..33519c18b --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowRegister.cs @@ -0,0 +1,16 @@ +using Elsa.Workflows.Activities; + +namespace Elsa.Workflows.Runtime.Contracts; + +/// +/// Registers workflows. +/// +public interface IWorkflowRegistry +{ + /// + /// Registers a workflow. + /// + /// The workflow to register. + /// An optional cancellation token. + Task RegisterAsync(Workflow workflow, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs index 75b9f7e6a..41c867f95 100644 --- a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs +++ b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs @@ -165,6 +165,7 @@ public class WorkflowRuntimeFeature : FeatureBase .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs index b7d978729..7cb2ff9ac 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs @@ -53,12 +53,18 @@ public class DefaultWorkflowDefinitionStorePopulator : IWorkflowDefinitionStoreP foreach (var result in results) { - await AssignIdentities(result.Workflow, cancellationToken); - await AddOrUpdateAsync(result, cancellationToken); - await IndexTriggersAsync(result, cancellationToken); + await AddAsync(result, cancellationToken); } } } + + /// + public async Task AddAsync(MaterializedWorkflow materializedWorkflow, CancellationToken cancellationToken = default) + { + await AssignIdentities(materializedWorkflow.Workflow, cancellationToken); + await AddOrUpdateAsync(materializedWorkflow, cancellationToken); + await IndexTriggersAsync(materializedWorkflow, cancellationToken); + } private async Task AssignIdentities(Workflow workflow, CancellationToken cancellationToken) { diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowRegistry.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowRegistry.cs new file mode 100644 index 000000000..775018fe6 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowRegistry.cs @@ -0,0 +1,17 @@ +using Elsa.Workflows.Activities; +using Elsa.Workflows.Management.Materializers; +using Elsa.Workflows.Runtime.Contracts; +using Elsa.Workflows.Runtime.Models; + +namespace Elsa.Workflows.Runtime.Services; + +/// +public class DefaultWorkflowRegistry(IWorkflowDefinitionStorePopulator populator) : IWorkflowRegistry +{ + /// + public async Task RegisterAsync(Workflow workflow, CancellationToken cancellationToken = default) + { + var materializedWorkflow = new MaterializedWorkflow(workflow, nameof(DefaultWorkflowRegistry), TypedWorkflowMaterializer.MaterializerName); + await populator.AddAsync(materializedWorkflow, cancellationToken); + } +} \ No newline at end of file