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:
gurkanguran 2023-07-31 18:53:50 +02:00 committed by GitHub
parent 6ca484f6a5
commit d29df110d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 228 additions and 18 deletions

View file

@ -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

View file

@ -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.

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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")}"),
}
};
}
}

View file

@ -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>

View file

@ -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);
}
}

View file

@ -0,0 +1 @@
global using Xunit;