From 9365328f0bceaeb975b89e3fb2d467d8bc8b6204 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sat, 25 Jan 2025 23:26:37 +0100 Subject: [PATCH] Add commit state behavior support in workflows Introduced `ActivityCommitStateBehavior` and `WorkflowCommitStateOptions` to enable flexible state commit handling in workflows. Integrated commit logic into activity and workflow execution contexts and middleware. This improves control over when state is committed during workflow execution. --- .../Extensions/ActivityExtensions.cs | 11 +++ .../Extensions/JsonObjectExtensions.cs | 79 +++++++++++-------- .../Models/ActivityCommitStateBehavior.cs | 29 +++++++ .../Models/WorkflowCommitStateOptions.cs | 19 +++++ .../Models/WorkflowOptions.cs | 5 ++ .../Contexts/WorkflowExecutionContext.cs | 15 +++- .../Contracts/ICommitStateHandler.cs | 1 + .../Elsa.Workflows.Core.csproj.DotSettings | 2 + .../Enums/ActivityCommitStateBehavior.cs | 29 +++++++ .../Extensions/ActivityPropertyExtensions.cs | 15 +++- .../DefaultActivityInvokerMiddleware.cs | 44 +++++++++++ .../DefaultActivitySchedulerMiddleware.cs | 10 +++ .../Models/WorkflowCommitStateOptions.cs | 19 +++++ .../Models/WorkflowOptions.cs | 5 ++ .../Services/NoopCommitStateHandler.cs | 5 ++ .../Services/StoreCommitStateHandler.cs | 6 ++ 16 files changed, 258 insertions(+), 36 deletions(-) create mode 100644 src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/ActivityCommitStateBehavior.cs create mode 100644 src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowCommitStateOptions.cs create mode 100644 src/modules/Elsa.Workflows.Core/Enums/ActivityCommitStateBehavior.cs create mode 100644 src/modules/Elsa.Workflows.Core/Models/WorkflowCommitStateOptions.cs diff --git a/src/clients/Elsa.Api.Client/Extensions/ActivityExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/ActivityExtensions.cs index 6ae48eab8..f39fc90bc 100644 --- a/src/clients/Elsa.Api.Client/Extensions/ActivityExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/ActivityExtensions.cs @@ -1,4 +1,5 @@ using System.Text.Json.Nodes; +using Elsa.Api.Client.Resources.WorkflowDefinitions.Models; using Elsa.Api.Client.Shared.Models; namespace Elsa.Api.Client.Extensions; @@ -182,4 +183,14 @@ public static class ActivityExtensions /// Sets a value indicating whether the specified activity can trigger the workflow. /// public static void SetRunAsynchronously(this JsonObject activity, bool value) => activity.SetProperty(JsonValue.Create(value), "customProperties", "runAsynchronously"); + + /// + /// Gets the commit state behavior for the specified activity. + /// + public static ActivityCommitStateBehavior GetCommitStateBehavior(this JsonObject activity) => activity.TryGetProperty("customProperties", "commitStateBehavior") ?? ActivityCommitStateBehavior.Default; + + /// + /// Sets the commit state behavior for the specified activity. + /// + public static void SetCommitStateBehavior(this JsonObject activity, ActivityCommitStateBehavior value) => activity.SetProperty(JsonValue.Create(value.ToString()), "customProperties", "commitStateBehavior"); } \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Extensions/JsonObjectExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/JsonObjectExtensions.cs index a1612f9c2..b65fcf394 100644 --- a/src/clients/Elsa.Api.Client/Extensions/JsonObjectExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/JsonObjectExtensions.cs @@ -15,52 +15,43 @@ public static class JsonObjectExtensions { return obj.ContainsKey("type") && obj.ContainsKey("id") && obj.ContainsKey("version"); } - + /// /// Serializes the specified value to a . /// /// The value to serialize. /// The to use. /// A representing the specified value. - public static JsonNode SerializeToNode(this object value, JsonSerializerOptions? options = default) + public static JsonNode SerializeToNode(this object value, JsonSerializerOptions? options = null) { - options ??= new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - + options ??= new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + return JsonSerializer.SerializeToNode(value, options)!; } - + /// /// Serializes the specified value to a . /// /// The value to serialize. /// The to use. /// A representing the specified value. - public static JsonArray SerializeToArray(this object value, JsonSerializerOptions? options = default) + public static JsonArray SerializeToArray(this object value, JsonSerializerOptions? options = null) { - options ??= new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - + options ??= new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + return JsonSerializer.SerializeToNode(value, options)!.AsArray(); } - + /// /// Serializes the specified value to a . /// /// The value to serialize. /// The to use. /// A representing the specified value. - public static JsonArray SerializeToArray(this IEnumerable value, JsonSerializerOptions? options = default) + public static JsonArray SerializeToArray(this IEnumerable value, JsonSerializerOptions? options = null) { - options ??= new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - + options ??= new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + return JsonSerializer.SerializeToNode(value, options)!.AsArray(); } @@ -71,19 +62,23 @@ public static class JsonObjectExtensions /// The to use. /// The type to deserialize to. /// The deserialized value. - public static T Deserialize(this JsonNode value, JsonSerializerOptions? options = default) + public static T Deserialize(this JsonNode value, JsonSerializerOptions? options = null) { - options ??= new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - + options ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + if (value is JsonObject jsonObject) return JsonSerializer.Deserialize(jsonObject, options)!; if (value is JsonArray jsonArray) return JsonSerializer.Deserialize(jsonArray, options)!; + if (typeof(T).IsEnum || (Nullable.GetUnderlyingType(typeof(T))?.IsEnum ?? false)) + { + if (value.GetValueKind() == JsonValueKind.Null) + return default!; + return (T)Enum.Parse(Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), value.ToString()); + } + if (value is JsonValue jsonValue) return jsonValue.GetValue(); @@ -101,7 +96,7 @@ public static class JsonObjectExtensions model = GetPropertyContainer(model, path); model[path.Last()] = value?.SerializeToNode(); } - + /// /// Sets the property value of the specified model. /// @@ -113,7 +108,7 @@ public static class JsonObjectExtensions model = GetPropertyContainer(model, path); model[path.Last()] = value?.SerializeToNode(); } - + /// /// Sets the property value of the specified model. /// @@ -125,7 +120,7 @@ public static class JsonObjectExtensions model = GetPropertyContainer(model, path); model[path.Last()] = new JsonArray(value.Select(x => x.SerializeToNode()).ToArray()); } - + /// /// Gets the property value of the specified model. /// @@ -139,7 +134,7 @@ public static class JsonObjectExtensions foreach (var prop in path.SkipLast(1)) { if (currentModel[prop] is not JsonObject value) - return default; + return null; currentModel = value; } @@ -147,6 +142,25 @@ public static class JsonObjectExtensions return currentModel[path.Last()]; } + /// + /// Gets the property value of the specified model. + /// + /// The model to get the property value from. + /// The path to the property. + /// The type to deserialize to. + /// The property value. + public static T? TryGetProperty(this JsonObject model, params string[] path) + { + try + { + return model.GetProperty(path); + } + catch (Exception e) + { + return default; + } + } + /// /// Gets the property value of the specified model. /// @@ -173,7 +187,7 @@ public static class JsonObjectExtensions var property = GetProperty(model, path); return property != null ? property.Deserialize(options) : default; } - + /// /// Returns the property container of the specified model. /// @@ -190,5 +204,4 @@ public static class JsonObjectExtensions return model; } - } \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/ActivityCommitStateBehavior.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/ActivityCommitStateBehavior.cs new file mode 100644 index 000000000..66d6dbdf6 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/ActivityCommitStateBehavior.cs @@ -0,0 +1,29 @@ +namespace Elsa.Api.Client.Resources.WorkflowDefinitions.Models; + +public enum ActivityCommitStateBehavior +{ + /// + /// Never commit state, regardless of the workflow commit state options. + /// + Never, + + /// + /// Look at the workflow commit state options to determine if state should be committed. + /// + Default, + + /// + /// Commit state before the activity starts. + /// + Executing, + + /// + /// Commit state after the activity executes. + /// + Executed, + + /// + /// Commit state before the activity starts and after the activity executes. + /// + BeforeAndAfterExecution +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowCommitStateOptions.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowCommitStateOptions.cs new file mode 100644 index 000000000..8e14345c6 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowCommitStateOptions.cs @@ -0,0 +1,19 @@ +namespace Elsa.Api.Client.Resources.WorkflowDefinitions.Models; + +public class WorkflowCommitStateOptions +{ + /// + /// Commit workflow state before the workflow starts. + /// + public bool Starting { get; set; } + + /// + /// Commit workflow state before an activity executes, unless the activity is configured to not commit state. + /// + public bool ActivityExecuting { get; set; } + + /// + /// Commit workflow state after an activity executes, unless the activity is configured to not commit state. + /// + public bool ActivityExecuted { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowOptions.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowOptions.cs index ec28ac197..c3f38f381 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowOptions.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowOptions.cs @@ -29,4 +29,9 @@ public class WorkflowOptions /// The type of IIncidentStrategy to use when a fault occurs in the workflow. /// public string? IncidentStrategyType { get; set; } + + /// + /// The options for committing workflow state. + /// + public WorkflowCommitStateOptions CommitStateOptions { get; set; } = new(); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs index 56788e661..df38d706d 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs @@ -37,6 +37,7 @@ public partial class WorkflowExecutionContext : IExecutionContext private readonly IList _completionCallbackEntries = new List(); private IList _activityExecutionContexts; private readonly IHasher _hasher; + private readonly ICommitStateHandler _commitStateHandler; /// /// Initializes a new instance of . @@ -61,6 +62,7 @@ public partial class WorkflowExecutionContext : IExecutionContext ActivityRegistry = serviceProvider.GetRequiredService(); ActivityRegistryLookup = serviceProvider.GetRequiredService(); _hasher = serviceProvider.GetRequiredService(); + _commitStateHandler = serviceProvider.GetRequiredService(); SubStatus = WorkflowSubStatus.Pending; Id = id; CorrelationId = correlationId; @@ -238,6 +240,11 @@ public partial class WorkflowExecutionContext : IExecutionContext /// The current sub status of the workflow. public WorkflowSubStatus SubStatus { get; internal set; } + /// + /// The previous sub status of the workflow. + /// + public WorkflowSubStatus PreviousSubStatus { get; internal set; } + /// The root associated with the execution context. public MemoryRegister MemoryRegister { get; private set; } = null!; @@ -510,8 +517,9 @@ public partial class WorkflowExecutionContext : IExecutionContext internal void TransitionTo(WorkflowSubStatus subStatus) { if (!ValidateStatusTransition()) - throw new Exception($"Cannot transition from {SubStatus} to {subStatus}"); + throw new($"Cannot transition from {SubStatus} to {subStatus}"); + PreviousSubStatus = SubStatus; SubStatus = subStatus; UpdatedAt = SystemClock.UtcNow; @@ -614,4 +622,9 @@ public partial class WorkflowExecutionContext : IExecutionContext var currentMainStatus = GetMainStatus(SubStatus); return currentMainStatus != WorkflowStatus.Finished; } + + public Task CommitAsync() + { + return _commitStateHandler.CommitAsync(this, CancellationToken); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Contracts/ICommitStateHandler.cs b/src/modules/Elsa.Workflows.Core/Contracts/ICommitStateHandler.cs index 3d24ee6fd..a73659ea5 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/ICommitStateHandler.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/ICommitStateHandler.cs @@ -4,5 +4,6 @@ namespace Elsa.Workflows; public interface ICommitStateHandler { + Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken = default); Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowState workflowState, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj.DotSettings b/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj.DotSettings index f05e636df..300c67063 100644 --- a/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj.DotSettings +++ b/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj.DotSettings @@ -7,6 +7,8 @@ True True True + True + True True True True diff --git a/src/modules/Elsa.Workflows.Core/Enums/ActivityCommitStateBehavior.cs b/src/modules/Elsa.Workflows.Core/Enums/ActivityCommitStateBehavior.cs new file mode 100644 index 000000000..3ae3e3062 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/Enums/ActivityCommitStateBehavior.cs @@ -0,0 +1,29 @@ +namespace Elsa.Workflows; + +public enum ActivityCommitStateBehavior +{ + /// + /// Never commit state, regardless of the workflow commit state options. + /// + Never, + + /// + /// Look at the workflow commit state options to determine if state should be committed. + /// + Default, + + /// + /// Commit state before the activity starts. + /// + Executing, + + /// + /// Commit state after the activity executes. + /// + Executed, + + /// + /// Commit state before the activity starts and after the activity executes. + /// + BeforeAndAfterExecution +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs index 4d657080a..b4c240157 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs @@ -11,6 +11,7 @@ public static class ActivityPropertyExtensions private static readonly string[] CanStartWorkflowPropertyName = ["canStartWorkflow", "CanStartWorkflow"]; private static readonly string[] RunAsynchronouslyPropertyName = ["runAsynchronously", "RunAsynchronously"]; private static readonly string[] SourcePropertyName = ["source", "Source"]; + private static readonly string[] CommitStateBehaviorName = ["commitStateBehavior", "CommitStateBehavior"]; /// /// Gets a flag indicating whether this activity can be used for starting a workflow. @@ -46,6 +47,16 @@ public static class ActivityPropertyExtensions /// Sets the source file and line number where this activity was instantiated, if any. /// public static void SetSource(this IActivity activity, string value) => activity.CustomProperties[SourcePropertyName[0]] = value; + + /// + /// Gets the commit state behavior for the specified activity. + /// + public static ActivityCommitStateBehavior GetCommitStateBehavior(this IActivity activity) => activity.CustomProperties.GetValueOrDefault(CommitStateBehaviorName, () => ActivityCommitStateBehavior.Default); + + /// + /// Sets the commit state behavior for the specified activity. + /// + public static void SetCommitStateBehavior(this IActivity activity, ActivityCommitStateBehavior value) => activity.CustomProperties[CommitStateBehaviorName[0]] = value; /// /// Sets the source file and line number where this activity was instantiated, if any. @@ -62,7 +73,7 @@ public static class ActivityPropertyExtensions /// /// Gets the display text for the specified activity. /// - public static string? GetDisplayText(this IActivity activity) => activity.Metadata.TryGetValue("displayText", out var value) ? value.ToString() : default; + public static string? GetDisplayText(this IActivity activity) => activity.Metadata.TryGetValue("displayText", out var value) ? value.ToString() : null; /// /// Sets the display text for the specified activity. @@ -72,7 +83,7 @@ public static class ActivityPropertyExtensions /// /// Gets the description for the specified activity. /// - public static string? GetDescription(this IActivity activity) => activity.Metadata.TryGetValue("description", out var value) ? value.ToString() : default; + public static string? GetDescription(this IActivity activity) => activity.Metadata.TryGetValue("description", out var value) ? value.ToString() : null; /// /// Sets the description for the specified activity. diff --git a/src/modules/Elsa.Workflows.Core/Middleware/Activities/DefaultActivityInvokerMiddleware.cs b/src/modules/Elsa.Workflows.Core/Middleware/Activities/DefaultActivityInvokerMiddleware.cs index 65652ea04..a7bd74c6b 100644 --- a/src/modules/Elsa.Workflows.Core/Middleware/Activities/DefaultActivityInvokerMiddleware.cs +++ b/src/modules/Elsa.Workflows.Core/Middleware/Activities/DefaultActivityInvokerMiddleware.cs @@ -47,6 +47,10 @@ public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, I context.AddExecutionLogEntry("Precondition Failed", "Cannot execute at this time"); return; } + + // Conditionally commit the workflow state. + if(ShouldCommitWhenStarting(context)) + await context.WorkflowExecutionContext.CommitAsync(); context.TransitionTo(ActivityStatus.Running); @@ -78,6 +82,10 @@ public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, I workflowExecutionContext.Bookmarks.AddRange(context.Bookmarks); logger.LogDebug("Added {BookmarkCount} bookmarks to the workflow execution context", context.Bookmarks.Count); } + + // Conditionally commit the workflow state. + if(ShouldCommitWhenExecuted(context)) + await context.WorkflowExecutionContext.CommitAsync(); } /// @@ -111,4 +119,40 @@ public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, I // Evaluate input properties. await context.EvaluateInputPropertiesAsync(); } + + private bool ShouldCommitWhenStarting(ActivityExecutionContext context) + { + var behavior = context.Activity.GetCommitStateBehavior(); + + if (behavior == ActivityCommitStateBehavior.Executing) + return true; + + if (behavior == ActivityCommitStateBehavior.Default) + { + var workflowOptions = context.WorkflowExecutionContext.Workflow.Options.CommitStateOptions; + + if(workflowOptions.ActivityExecuting) + return true; + } + + return false; + } + + private bool ShouldCommitWhenExecuted(ActivityExecutionContext context) + { + var behavior = context.Activity.GetCommitStateBehavior(); + + if (behavior == ActivityCommitStateBehavior.Executed) + return true; + + if (behavior == ActivityCommitStateBehavior.Default) + { + var workflowOptions = context.WorkflowExecutionContext.Workflow.Options.CommitStateOptions; + + if(workflowOptions.ActivityExecuted) + return true; + } + + return false; + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultActivitySchedulerMiddleware.cs b/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultActivitySchedulerMiddleware.cs index f9d1645d9..078474b68 100644 --- a/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultActivitySchedulerMiddleware.cs +++ b/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultActivitySchedulerMiddleware.cs @@ -36,6 +36,8 @@ public class DefaultActivitySchedulerMiddleware : WorkflowExecutionMiddleware context.TransitionTo(WorkflowSubStatus.Executing); + await ConditionallyCommitStateAsync(context); + while (scheduler.HasAny) { // Do not start a workflow if cancellation has been requested. @@ -65,4 +67,12 @@ public class DefaultActivitySchedulerMiddleware : WorkflowExecutionMiddleware await _activityInvoker.InvokeAsync(context, workItem.Activity, options); } + + private async Task ConditionallyCommitStateAsync(WorkflowExecutionContext context) + { + var shouldCommit = context.Workflow.Options.CommitStateOptions.Starting; + + if (shouldCommit) + await context.CommitAsync(); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Models/WorkflowCommitStateOptions.cs b/src/modules/Elsa.Workflows.Core/Models/WorkflowCommitStateOptions.cs new file mode 100644 index 000000000..514a255db --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/Models/WorkflowCommitStateOptions.cs @@ -0,0 +1,19 @@ +namespace Elsa.Workflows.Models; + +public class WorkflowCommitStateOptions +{ + /// + /// Commit workflow state before the workflow starts. + /// + public bool Starting { get; set; } + + /// + /// Commit workflow state before an activity executes, unless the activity is configured to not commit state. + /// + public bool ActivityExecuting { get; set; } + + /// + /// Commit workflow state after an activity executes, unless the activity is configured to not commit state. + /// + public bool ActivityExecuted { get; set; } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Models/WorkflowOptions.cs b/src/modules/Elsa.Workflows.Core/Models/WorkflowOptions.cs index e4b82b116..f2ac78484 100644 --- a/src/modules/Elsa.Workflows.Core/Models/WorkflowOptions.cs +++ b/src/modules/Elsa.Workflows.Core/Models/WorkflowOptions.cs @@ -29,4 +29,9 @@ public class WorkflowOptions /// The type of to use when a fault occurs in the workflow. /// public Type? IncidentStrategyType { get; set; } + + /// + /// The options for committing workflow state. + /// + public WorkflowCommitStateOptions CommitStateOptions { get; set; } = new(); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Services/NoopCommitStateHandler.cs b/src/modules/Elsa.Workflows.Core/Services/NoopCommitStateHandler.cs index 8503bddb7..e0a6b7f81 100644 --- a/src/modules/Elsa.Workflows.Core/Services/NoopCommitStateHandler.cs +++ b/src/modules/Elsa.Workflows.Core/Services/NoopCommitStateHandler.cs @@ -4,6 +4,11 @@ namespace Elsa.Workflows; public class NoopCommitStateHandler : ICommitStateHandler { + public Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + public Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowState workflowState, CancellationToken cancellationToken = default) { return Task.CompletedTask; diff --git a/src/modules/Elsa.Workflows.Runtime/Services/StoreCommitStateHandler.cs b/src/modules/Elsa.Workflows.Runtime/Services/StoreCommitStateHandler.cs index 8f3d33466..5db9391ba 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/StoreCommitStateHandler.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/StoreCommitStateHandler.cs @@ -5,6 +5,12 @@ namespace Elsa.Workflows.Runtime; public class StoreCommitStateHandler(IWorkflowInstanceManager workflowInstanceManager) : ICommitStateHandler { + public async Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken = default) + { + var workflowState = workflowInstanceManager.ExtractWorkflowState(workflowExecutionContext); + await CommitAsync(workflowExecutionContext, workflowState, cancellationToken); + } + public async Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowState workflowState, CancellationToken cancellationToken = default) { await workflowInstanceManager.SaveAsync(workflowState, cancellationToken);