Fixed getting and setting variables in context (#4267)
* Fixed getting and setting variables in context * Added unit tests * Add test for named variable * Search variable in parent contexts --------- Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
This commit is contained in:
parent
6ca484f6a5
commit
d29df110d2
7
Elsa.sln
7
Elsa.sln
|
|
@ -232,6 +232,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.Dynamic
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Mediator", "src\common\Elsa.Mediator\Elsa.Mediator.csproj", "{28818676-F6AF-4203-8B65-BD33A50CB9A2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Workflows.Core.UnitTests", "test\unit\Elsa.Workflows.Core.UnitTests\Elsa.Workflows.Core.UnitTests.csproj", "{DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -582,6 +584,10 @@ Global
|
|||
{28818676-F6AF-4203-8B65-BD33A50CB9A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{28818676-F6AF-4203-8B65-BD33A50CB9A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{28818676-F6AF-4203-8B65-BD33A50CB9A2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
|
||||
|
|
@ -687,5 +693,6 @@ Global
|
|||
{2430CB5F-7D07-4A9E-BD40-EC1B111B85FF} = {08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C}
|
||||
{85E13383-7C39-4719-AAC0-0B357C3A97C7} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
{28818676-F6AF-4203-8B65-BD33A50CB9A2} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
|
||||
{DC9CCAD0-7363-4691-B964-FF5B3AEA3F95} = {18453B51-25EB-4317-A4B3-B10518252E92}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -114,25 +114,36 @@ public static class ActivityExecutionContextExtensions
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a workflow variable by name.
|
||||
/// Creates a workflow variable by name and optionally sets the value.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ActivityExecutionContext"/> being extended.</param>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="value">The value of the variable.</param>
|
||||
/// <param name="storageDriverType">The type of the storage driver to use.</param>
|
||||
/// <param name="storageDriverType">The type of storage driver to use for the variable.</param>
|
||||
/// <param name="configure">A callback to configure the memory block.</param>
|
||||
/// <returns>The created <see cref="Variable"/>.</returns>
|
||||
public static Variable SetVariable(this ActivityExecutionContext context, string name, object? value, Type? storageDriverType = default, Action<MemoryBlock>? configure = default) =>
|
||||
context.ExpressionExecutionContext.SetVariable(name, value, storageDriverType, configure);
|
||||
public static Variable CreateVariable(this ActivityExecutionContext context, string name, object? value, Type? storageDriverType = default, Action<MemoryBlock>? configure = default) =>
|
||||
context.ExpressionExecutionContext.CreateVariable(name, value, storageDriverType, configure);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a workflow variable by name.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ActivityExecutionContext"/> being extended.</param>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="value">The value of the variable.</param>
|
||||
/// <param name="configure">A callback to configure the memory block.</param>
|
||||
/// <returns>The created <see cref="Variable"/>.</returns>
|
||||
public static Variable SetVariable(this ActivityExecutionContext context, string name, object? value, Action<MemoryBlock>? configure = default) =>
|
||||
context.ExpressionExecutionContext.SetVariable(name, value, configure);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a workflow variable by name.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ActivityExecutionContext"/> being extended.</param>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <typeparam name="T">The type of the variable.</typeparam>
|
||||
/// <returns>The variable if found, otherwise null.</returns>
|
||||
public static T? GetVariable<T>(this ActivityExecutionContext context, string name) => context.ExpressionExecutionContext.GetVariable<T?>(name);
|
||||
public static T? GetVariableByName<T>(this ActivityExecutionContext context, string name) => context.ExpressionExecutionContext.GetVariableByName<T?>(name);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of variable keys and their values across scopes.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Elsa.Workflows.Core;
|
|||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Memory;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
|
@ -44,21 +45,46 @@ public static class ExpressionExecutionContextExtensions
|
|||
public static T? Get<T>(this ExpressionExecutionContext context, Input<T>? input) => input != null ? context.GetBlock(input.MemoryBlockReference).Value.ConvertTo<T>() : default;
|
||||
public static T? Get<T>(this ExpressionExecutionContext context, Output output) => context.GetBlock(output.MemoryBlockReference).Value.ConvertTo<T>();
|
||||
public static object? Get(this ExpressionExecutionContext context, Output output) => context.GetBlock(output.MemoryBlockReference).Value;
|
||||
public static T? GetVariable<T>(this ExpressionExecutionContext context, string name) => (T?)context.GetVariable(name);
|
||||
public static object? GetVariable(this ExpressionExecutionContext context, string name) => new Variable(name).Get(context);
|
||||
public static Variable SetVariable<T>(this ExpressionExecutionContext context, string name, T? value, Type? storageDriverType = default) => context.SetVariable(name, (object?)value, storageDriverType, default);
|
||||
public static T? GetVariableByName<T>(this ExpressionExecutionContext context, string name) => (T?)context.GetVariableByName(name)?.Value;
|
||||
|
||||
public static Variable SetVariable(this ExpressionExecutionContext context, string name, object? value, Type? storageDriverType, Action<MemoryBlock>? configure = default)
|
||||
private static Variable? GetVariableByName(this ExpressionExecutionContext context, string name)
|
||||
{
|
||||
foreach (var block in context.Memory.Blocks.Where(b => b.Value.Metadata is VariableBlockMetadata))
|
||||
{
|
||||
var metadata = block.Value.Metadata as VariableBlockMetadata;
|
||||
if (metadata!.Variable.Name == name)
|
||||
return metadata.Variable;
|
||||
}
|
||||
|
||||
return context.ParentContext?.GetVariableByName(name);
|
||||
}
|
||||
|
||||
public static Variable CreateVariable<T>(this ExpressionExecutionContext context, string name, T? value, Type? storageDriverType = null, Action<MemoryBlock>? configure = default)
|
||||
{
|
||||
var existingVariable = context.GetVariableByName(name);
|
||||
if(existingVariable != null)
|
||||
throw new Exception($"Variable {name} already exists in the context.");
|
||||
|
||||
var variable = new Variable(name, value)
|
||||
{
|
||||
StorageDriverType = storageDriverType
|
||||
StorageDriverType = storageDriverType ?? typeof(WorkflowStorageDriver)
|
||||
};
|
||||
|
||||
|
||||
context.Set(variable, value, configure);
|
||||
return variable;
|
||||
}
|
||||
|
||||
public static Variable SetVariable<T>(this ExpressionExecutionContext context, string name, T? value, Action<MemoryBlock>? configure = default)
|
||||
{
|
||||
var variable = context.GetVariableByName(name);
|
||||
if(variable is null)
|
||||
throw new Exception($"Variable {name} not found in the context.");
|
||||
|
||||
variable.Value = value;
|
||||
variable.Set(context, value, configure);
|
||||
return variable;
|
||||
}
|
||||
|
||||
public static void Set(this ExpressionExecutionContext context, Output? output, object? value, Action<MemoryBlock>? configure = default)
|
||||
{
|
||||
if (output != null) context.Set(output.MemoryBlockReference(), value, configure);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class Tests
|
|||
var lines = _capturingTextWriter.Lines.ToList();
|
||||
Assert.Equal(new[] { "Line 5" }, lines);
|
||||
}
|
||||
|
||||
|
||||
[Fact(DisplayName = "Workflow can reference variables set in previous activities")]
|
||||
public async Task Test2()
|
||||
{
|
||||
|
|
@ -36,4 +36,16 @@ public class Tests
|
|||
var lines = _capturingTextWriter.Lines.ToList();
|
||||
Assert.Equal(new[] { "Variable 2: The value of variable 1" }, lines);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Workflow can set and get named variables")]
|
||||
public async Task Test3()
|
||||
{
|
||||
await _workflowRunner.RunAsync<SetGetNamedVariableWorkflow>();
|
||||
var lines = _capturingTextWriter.Lines.ToList();
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
"Foo = Bar",
|
||||
"Foo = Baz"
|
||||
}, lines);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Core.Abstractions;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
|
|
@ -14,13 +15,14 @@ class SetGetVariableWorkflow : WorkflowBase
|
|||
|
||||
workflow.Root = new Sequence
|
||||
{
|
||||
Variables = {
|
||||
Variables =
|
||||
{
|
||||
variable1
|
||||
},
|
||||
|
||||
Activities =
|
||||
{
|
||||
new SetVariable<string>(variable1,"Line 5"),
|
||||
new SetVariable<string>(variable1, "Line 5"),
|
||||
new WriteLine(variable1)
|
||||
}
|
||||
};
|
||||
|
|
@ -33,17 +35,17 @@ class SetGetVariablesWorkflow : WorkflowBase
|
|||
{
|
||||
var variable1 = new Variable<string>();
|
||||
var variable2 = new Variable<string>();
|
||||
|
||||
|
||||
workflow.Root = new Sequence
|
||||
{
|
||||
{
|
||||
Variables = { variable1, variable2 },
|
||||
|
||||
|
||||
Activities =
|
||||
{
|
||||
new SetVariable
|
||||
{
|
||||
Variable = variable1,
|
||||
Value = new ("The value of variable 1")
|
||||
Value = new("The value of variable 1")
|
||||
},
|
||||
new SetVariable()
|
||||
{
|
||||
|
|
@ -55,3 +57,21 @@ class SetGetVariablesWorkflow : WorkflowBase
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
class SetGetNamedVariableWorkflow : WorkflowBase
|
||||
{
|
||||
protected override void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
workflow.Root = new Sequence
|
||||
{
|
||||
Variables = { new Variable<string>("Foo", "Bar") },
|
||||
|
||||
Activities =
|
||||
{
|
||||
new WriteLine(context => $"Foo = {context.GetVariableByName<string>("Foo")}"),
|
||||
Inline.From(context => context.SetVariable("Foo", "Baz")),
|
||||
new WriteLine(context => $"Foo = {context.GetVariableByName<string>("Foo")}"),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.2.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
using Elsa.Expressions.Models;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Core.Memory;
|
||||
|
||||
namespace Elsa.Workflows.Core.UnitTests;
|
||||
|
||||
public class ExpressionExecutionContextExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetVariableByName_ReturnsVariable_WhenVariableExists()
|
||||
{
|
||||
// Arrange
|
||||
var variable = new Variable("test", 5);
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>
|
||||
{
|
||||
{ variable.Id, new MemoryBlock(variable.Value, new VariableBlockMetadata(variable, typeof(object), true)) }
|
||||
});
|
||||
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act
|
||||
var result = context.GetVariableByName<int>("test");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(5, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVariableByName_ReturnsNull_WhenVariableDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>());
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act
|
||||
var result = context.GetVariableByName<string>("nonexistent");
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateVariable_ThrowsException_WhenVariableExists()
|
||||
{
|
||||
// Arrange
|
||||
var variable = new Variable("test", 5);
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>
|
||||
{
|
||||
{ variable.Id, new MemoryBlock(variable.Value, new VariableBlockMetadata(variable, typeof(object), true)) }
|
||||
});
|
||||
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<Exception>(() => context.CreateVariable("test", 10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateVariable_CreatesVariable_WhenVariableDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>());
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act
|
||||
context.CreateVariable("newVariable", 10);
|
||||
|
||||
// Assert
|
||||
var variable = context.GetVariableByName<int>("newVariable");
|
||||
Assert.Equal(10, variable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetVariable_ThrowsException_WhenVariableDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>());
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<Exception>(() => context.SetVariable("nonexistent", 10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetVariable_SetsValue_WhenVariableExists()
|
||||
{
|
||||
// Arrange
|
||||
var variable = new Variable("test", 5);
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>
|
||||
{
|
||||
{ variable.Id, new MemoryBlock(variable.Value, new VariableBlockMetadata(variable, typeof(object), true)) }
|
||||
});
|
||||
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act
|
||||
context.SetVariable("test", 10);
|
||||
|
||||
// Assert
|
||||
var updatedVariable = context.GetVariableByName<int>("test");
|
||||
Assert.Equal(10, updatedVariable);
|
||||
}
|
||||
}
|
||||
1
test/unit/Elsa.Workflows.Core.UnitTests/Usings.cs
Normal file
1
test/unit/Elsa.Workflows.Core.UnitTests/Usings.cs
Normal file
|
|
@ -0,0 +1 @@
|
|||
global using Xunit;
|
||||
Loading…
Reference in a new issue