From 75b8122fd96b47eb30ca9fae7d643299cb4bb622 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sat, 20 Dec 2025 14:39:09 +0100 Subject: [PATCH 1/3] Updates package versions Updates Elsa Studio, Elsa Extensions, and Microsoft package versions to their latest stable releases for the 3.5.3 patch. --- Directory.Packages.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0bf216921..790bee96e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,9 +4,9 @@ true - 3.5.1-preview.1276 - 3.5.1-preview.148 - 9.0.9 + 3.5.2 + 3.5.2 + 9.0.11 From 7646867ac5365af35f0507fde72b2df98790bec9 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sat, 20 Dec 2025 14:59:00 +0100 Subject: [PATCH 2/3] Add `ClearCompletionCallbacks` method to `WorkflowExecutionContext` and update `Finish` activity to use it (#7154) Fixes #6707 --- src/modules/Elsa.Workflows.Core/Activities/Finish.cs | 2 +- .../Contexts/WorkflowExecutionContext.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/modules/Elsa.Workflows.Core/Activities/Finish.cs b/src/modules/Elsa.Workflows.Core/Activities/Finish.cs index 4650f9441..f4b205eaf 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/Finish.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/Finish.cs @@ -19,7 +19,7 @@ public class Finish : CodeActivity, ITerminalNode /// protected override void Execute(ActivityExecutionContext context) { - context.ClearCompletionCallbacks(); + context.WorkflowExecutionContext.ClearCompletionCallbacks(); context.WorkflowExecutionContext.Scheduler.Clear(); context.WorkflowExecutionContext.Bookmarks.Clear(); context.WorkflowExecutionContext.TransitionTo(WorkflowSubStatus.Finished); diff --git a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs index d664b5e83..7a5bfc5a0 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs @@ -609,6 +609,14 @@ public partial class WorkflowExecutionContext : IExecutionContext { RemoveActivityExecutionContexts(x => x is { IsCompleted: true, ParentActivityExecutionContext: not null }); } + + /// + /// Clears all activity completion callback entries from the workflow execution context. + /// + public void ClearCompletionCallbacks() + { + _completionCallbackEntries.Clear(); + } public IEnumerable GetActiveActivityExecutionContexts() { From 37921b5ee0dd9721b6c046f579e71d2443181df0 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 10:29:55 +0100 Subject: [PATCH 3/3] Restore Literal handling in ActivityExecutionContext.TryGet (#7075) * Initial plan * Add Literal handling back to ActivityExecutionContext.TryGet This restores support for dynamic Literal inputs that was removed in version 3.5.2. When an Input is created with a Literal, the Literal becomes the MemoryBlockReference. Since Literals hold values directly rather than in the memory register, they need special handling in TryGet to return their value. Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> * Improve test coverage for Literal handling in ActivityExecutionContext - Removed ineffective unit tests that only checked type relationships - Added explicit integration test for TryGet with Literal references - Added integration test for Get with Input containing Literal - All tests now directly verify the fixed TryGet behavior Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> --- .../Contexts/ActivityExecutionContext.cs | 7 ++ .../Core/LiteralInputTests.cs | 119 ++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 test/integration/Elsa.Workflows.IntegrationTests/Core/LiteralInputTests.cs diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs index fea34ac25..1a2934140 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs @@ -748,6 +748,13 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// True if the memory block exists, false otherwise. public bool TryGet(MemoryBlockReference blockReference, out object? value) { + // Handle Literal references directly - they hold their value and don't need to be in the memory register + if (blockReference is Literal literal) + { + value = literal.Value; + return true; + } + var memoryBlock = GetMemoryBlock(blockReference); if (memoryBlock != null) diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Core/LiteralInputTests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Core/LiteralInputTests.cs new file mode 100644 index 000000000..4570790f6 --- /dev/null +++ b/test/integration/Elsa.Workflows.IntegrationTests/Core/LiteralInputTests.cs @@ -0,0 +1,119 @@ +using Elsa.Expressions.Models; +using Elsa.Testing.Shared; +using Elsa.Workflows.Activities; +using Elsa.Workflows.Memory; +using Elsa.Workflows.Models; +using Microsoft.Extensions.DependencyInjection; +using Xunit.Abstractions; + +namespace Elsa.Workflows.IntegrationTests.Core; + +/// +/// Tests for ActivityExecutionContext handling of Literal inputs in real scenarios +/// +public class LiteralInputTests +{ + private readonly IServiceProvider _services; + private readonly IWorkflowRunner _workflowRunner; + + public LiteralInputTests(ITestOutputHelper testOutputHelper) + { + _services = new TestApplicationBuilder(testOutputHelper).Build(); + _workflowRunner = _services.GetRequiredService(); + } + + [Fact(DisplayName = "Activity should be able to access Input created with Literal")] + public async Task ActivityShouldAccessLiteralInput() + { + // Arrange - Create a custom activity that uses another activity with literal input + await _services.PopulateRegistriesAsync(); + + var workflow = new TestWorkflow(builder => + { + builder.Root = new CompositeActivityWithLiteralInput(); + }); + + // Act & Assert - Should not throw + var result = await _workflowRunner.RunAsync(workflow); + Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus); + } + + [Fact(DisplayName = "ActivityExecutionContext.TryGet should return true and value for Literal")] + public async Task TryGet_ShouldHandleLiteralDirectly() + { + // Arrange - Create a workflow and activity execution context + await _services.PopulateRegistriesAsync(); + + var activity = new WriteLine("Test"); + var workflow = new Workflow { Root = activity }; + + var workflowGraphBuilder = _services.GetRequiredService(); + var workflowGraph = await workflowGraphBuilder.BuildAsync(workflow); + var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(_services, workflowGraph, "test"); + var activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(activity); + + // Create a Literal and use it as a MemoryBlockReference + var expectedValue = "Hello World"; + var literal = new Literal(expectedValue); + var blockReference = (MemoryBlockReference)literal; + + // Act - Call TryGet directly with the Literal + var success = activityExecutionContext.TryGet(blockReference, out var actualValue); + + // Assert - Should succeed and return the literal's value + Assert.True(success, "TryGet should return true for Literal references"); + Assert.Equal(expectedValue, actualValue); + } + + [Fact(DisplayName = "ActivityExecutionContext.Get with Input containing Literal should work")] + public async Task Get_ShouldWorkWithInputContainingLiteral() + { + // Arrange + await _services.PopulateRegistriesAsync(); + + var activity = new WriteLine("Test"); + var workflow = new Workflow { Root = activity }; + + var workflowGraphBuilder = _services.GetRequiredService(); + var workflowGraph = await workflowGraphBuilder.BuildAsync(workflow); + var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(_services, workflowGraph, "test"); + var activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(activity); + + // Create an Input with a Literal value + var expectedValue = 42; + var literal = new Literal(expectedValue); + var input = new Input(literal); + + // Act - Get the value through the Input (which internally uses TryGet) + var actualValue = activityExecutionContext.Get(input); + + // Assert + Assert.Equal(expectedValue, actualValue); + } +} + +/// +/// A composite activity that creates inputs with literal values and tries to read them +/// This simulates the use case described in the issue where activities re-use other activities' execute methods +/// +public class CompositeActivityWithLiteralInput : CodeActivity +{ + protected override ValueTask ExecuteAsync(ActivityExecutionContext context) + { + // Create an Input with a Literal value - this is a common pattern when programmatically + // creating activities and setting their inputs + var literal = new Literal("Test Value"); + var input = new Input(literal); + + // Try to get the value - this should work but will fail without the Literal handling in TryGet + // The issue is that when Input is created with a Literal, the Literal becomes the MemoryBlockReference + // When Get is called, it tries to find this in the memory register, but Literals hold values directly + var value = context.Get(input); + + // If we got here without exception, the test passes + if (value != "Test Value") + throw new Exception($"Expected 'Test Value' but got '{value}'"); + + return ValueTask.CompletedTask; + } +}