diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
deleted file mode 100644
index dd4b9ee41e..0000000000
--- a/.github/workflows/pr.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-# ------------------------------------------------------------------------------
-#
-#
-# This code was generated.
-#
-# - To turn off auto-generation set:
-#
-# [CustomGitHubActions (AutoGenerate = false)]
-#
-# - To trigger manual generation invoke:
-#
-# nuke --generate-configuration GitHubActions_pr --host GitHubActions
-#
-#
-# ------------------------------------------------------------------------------
-
-name: pr
-
-on:
- pull_request:
- branches:
- - main
- paths:
- - '**/*'
- - '!docs/**/*'
- - '!package.json'
- - '!package-lock.json'
- - '!readme.md'
-
-jobs:
- ubuntu-latest:
- name: ubuntu-latest
- runs-on: ubuntu-latest
- steps:
- - if: ${{ runner.os == 'Windows' }}
- name: 'Use GNU tar'
- shell: cmd
- run: |
- echo "Adding GNU tar to PATH"
- echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%"
- - uses: actions/checkout@v3
- - name: 'Run: Compile, Test, Pack'
- run: ./build.cmd Compile Test Pack
diff --git a/Elsa.sln b/Elsa.sln
index e1a6d97967..b0c0f47e82 100644
--- a/Elsa.sln
+++ b/Elsa.sln
@@ -292,7 +292,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipelines", "pipelines", "{
.github\workflows\elsa-server.yml = .github\workflows\elsa-server.yml
.github\workflows\elsa-studio.yml = .github\workflows\elsa-studio.yml
.github\workflows\packages.yml = .github\workflows\packages.yml
- .github\workflows\pr.yml = .github\workflows\pr.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}"
diff --git a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs
index 888060fd1b..677f09ac0a 100644
--- a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs
+++ b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs
@@ -1,6 +1,5 @@
using System.Collections;
using System.ComponentModel;
-using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Globalization;
using System.Text.Json;
diff --git a/src/modules/Elsa.Expressions/LiteralExpression.cs b/src/modules/Elsa.Expressions/LiteralExpression.cs
index b663b62be8..7ddbf58fea 100644
--- a/src/modules/Elsa.Expressions/LiteralExpression.cs
+++ b/src/modules/Elsa.Expressions/LiteralExpression.cs
@@ -1,10 +1,12 @@
using Elsa.Expressions.Contracts;
using Elsa.Expressions.Helpers;
using Elsa.Expressions.Models;
+using JetBrains.Annotations;
namespace Elsa.Expressions;
///
+[UsedImplicitly]
public class LiteralExpressionHandler : IExpressionHandler
{
private readonly IWellKnownTypeRegistry _wellKnownTypeRegistry;
diff --git a/src/modules/Elsa.Expressions/Models/ExpressionDescriptor.cs b/src/modules/Elsa.Expressions/Models/ExpressionDescriptor.cs
index 7314c78a50..8f42206606 100644
--- a/src/modules/Elsa.Expressions/Models/ExpressionDescriptor.cs
+++ b/src/modules/Elsa.Expressions/Models/ExpressionDescriptor.cs
@@ -41,4 +41,9 @@ public class ExpressionDescriptor
/// Gets or sets the memory block reference factory.
///
public Func MemoryBlockReferenceFactory { get; set; } = () => new MemoryBlockReference();
+
+ ///
+ /// Gets or sets the expression deserialization function.
+ ///
+ public Func Deserialize { get; set; } = default!;
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Expressions/Models/ExpressionSerializationContext.cs b/src/modules/Elsa.Expressions/Models/ExpressionSerializationContext.cs
new file mode 100644
index 0000000000..f366531b46
--- /dev/null
+++ b/src/modules/Elsa.Expressions/Models/ExpressionSerializationContext.cs
@@ -0,0 +1,5 @@
+using System.Text.Json;
+
+namespace Elsa.Expressions.Models;
+
+public record ExpressionSerializationContext(JsonElement JsonElement, JsonSerializerOptions Options, Type MemoryBlockType);
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Expressions/VariableExpressionHandler.cs b/src/modules/Elsa.Workflows.Core/Expressions/VariableExpressionHandler.cs
index 358f94d672..1955723398 100644
--- a/src/modules/Elsa.Workflows.Core/Expressions/VariableExpressionHandler.cs
+++ b/src/modules/Elsa.Workflows.Core/Expressions/VariableExpressionHandler.cs
@@ -1,3 +1,4 @@
+using System.Text.Json;
using Elsa.Expressions.Contracts;
using Elsa.Expressions.Models;
using Elsa.Workflows.Memory;
diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/InputJsonConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/InputJsonConverter.cs
index f7d194fca1..d259b0deb6 100644
--- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/InputJsonConverter.cs
+++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/InputJsonConverter.cs
@@ -39,27 +39,17 @@ public override Input Read(ref Utf8JsonReader reader, Type typeToConvert, Jso
var expressionElement = doc.RootElement.TryGetProperty("expression", out var expressionElementValue) ? expressionElementValue : default;
var expressionTypeNameElement = expressionElement.ValueKind != JsonValueKind.Undefined ? expressionElement.TryGetProperty("type", out var expressionTypeNameElementValue) ? expressionTypeNameElementValue : default : default;
- var expressionTypeName = expressionTypeNameElement.ValueKind != JsonValueKind.Undefined ? expressionTypeNameElement.GetString() ?? "Literal" : "Literal";
- var expressionDescriptor = _expressionDescriptorRegistry.Find(expressionTypeName);
- var memoryBlockReference = expressionDescriptor?.MemoryBlockReferenceFactory();
- var memoryBlockReferenceType = memoryBlockReference?.GetType();
- var expressionValueElement = expressionElement.TryGetProperty("value", out var expressionElementValueValue) ? expressionElementValueValue : default;
-
- var expressionValue = expressionValueElement.ValueKind switch
- {
- JsonValueKind.String => expressionValueElement.GetString(),
- JsonValueKind.False => false,
- JsonValueKind.True => true,
- JsonValueKind.Number => expressionValueElement.GetDouble(),
- JsonValueKind.Undefined => default,
- _ => memoryBlockReferenceType != null ? expressionValueElement.Deserialize(memoryBlockReferenceType, options)! : default
- };
-
- var expression = new Expression(expressionTypeName, expressionValue);
+ var expressionTypeName = expressionTypeNameElement.ValueKind != JsonValueKind.Undefined ? expressionTypeNameElement.GetString() ?? "Literal" : default;
+ var expressionDescriptor = expressionTypeName != null ? _expressionDescriptorRegistry.Find(expressionTypeName) : default;
+ var memoryBlockReference = expressionDescriptor?.MemoryBlockReferenceFactory?.Invoke();
if (memoryBlockReference == null)
return default!;
+ var memoryBlockType = memoryBlockReference.GetType();
+ var context = new ExpressionSerializationContext(expressionElement, options, memoryBlockType);
+ var expression = expressionDescriptor!.Deserialize(context);
+
return (Input)Activator.CreateInstance(typeof(Input), expression, memoryBlockReference)!;
}
@@ -73,10 +63,10 @@ public override void Write(Utf8JsonWriter writer, Input value, JsonSerializer
var expression = value.Expression;
var expressionType = expression?.Type;
var expressionDescriptor = expressionType != null ? _expressionDescriptorRegistry.Find(expressionType) : default;
-
+
if (expressionDescriptor == null)
throw new JsonException($"Could not find an expression descriptor for expression type '{expressionType}'.");
-
+
var targetType = value.Type;
var expressionValue = expressionDescriptor.IsSerializable ? expression : null;
diff --git a/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionDescriptorProvider.cs b/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionDescriptorProvider.cs
index d27da2b210..dd434f19f0 100644
--- a/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionDescriptorProvider.cs
+++ b/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionDescriptorProvider.cs
@@ -1,3 +1,4 @@
+using System.Text.Json;
using Elsa.Expressions;
using Elsa.Expressions.Contracts;
using Elsa.Expressions.Models;
@@ -21,35 +22,85 @@ public IEnumerable GetDescriptors()
yield return CreateVariableDescriptor();
}
- private ExpressionDescriptor CreateLiteralDescriptor() => CreateDescriptor("Literal", "Literal", isBrowsable: false);
+ private ExpressionDescriptor CreateLiteralDescriptor()
+ {
+ return CreateDescriptor(
+ "Literal",
+ "Literal",
+ isBrowsable: false,
+ memoryBlockReferenceFactory: () => new Literal(),
+ deserialize: (context) =>
+ {
+ var elementValue = context.JsonElement.TryGetProperty("value", out var v) ? v : default;
+
+ var value = (object?)(elementValue.ValueKind switch
+ {
+ JsonValueKind.String => elementValue.GetString(),
+ JsonValueKind.Number => elementValue.GetDecimal(),
+ JsonValueKind.True => true,
+ JsonValueKind.False => false,
+ _ => v.GetString()
+ });
+
+ return new Expression("Literal", value);
+ });
+ }
+
private ExpressionDescriptor CreateObjectDescriptor() => CreateDescriptor("Object", "Object", monacoLanguage: "json", isBrowsable: false);
[Obsolete("Use Object instead.")]
private ExpressionDescriptor CreateJsonDescriptor() => CreateDescriptor("Json", "Json", monacoLanguage: "json", isBrowsable: false);
private ExpressionDescriptor CreateDelegateDescriptor() => CreateDescriptor("Delegate", "Delegate", false, false);
- private ExpressionDescriptor CreateVariableDescriptor() => CreateDescriptor("Variable", "Variable", isBrowsable: false, memoryBlockReferenceFactory: () => new Variable());
+
+ private ExpressionDescriptor CreateVariableDescriptor()
+ {
+ return CreateDescriptor(
+ "Variable",
+ "Variable",
+ isBrowsable: false,
+ memoryBlockReferenceFactory: () => new Variable(),
+ deserialize: context =>
+ {
+ var expressionValueElement = context.JsonElement.TryGetProperty("value", out var expressionElementValueValue) ? expressionElementValueValue : default;
+ var expressionValue = expressionValueElement.Deserialize(context.MemoryBlockType, context.Options);
+ return new Expression("Variable", expressionValue);
+ }
+ );
+ }
private static ExpressionDescriptor CreateDescriptor(
- string type,
+ string expressionType,
string displayName,
bool isSerializable = true,
bool isBrowsable = true,
string? monacoLanguage = null,
- Func? memoryBlockReferenceFactory = default) where THandler : IExpressionHandler
+ Func? memoryBlockReferenceFactory = default,
+ Func? deserialize = default)
+ where THandler : IExpressionHandler
{
var descriptor = new ExpressionDescriptor
{
- Type = type,
+ Type = expressionType,
DisplayName = displayName,
IsSerializable = isSerializable,
IsBrowsable = isBrowsable,
HandlerFactory = sp => ActivatorUtilities.GetServiceOrCreateInstance(sp),
- MemoryBlockReferenceFactory = memoryBlockReferenceFactory ?? (() => new MemoryBlockReference())
+ MemoryBlockReferenceFactory = memoryBlockReferenceFactory ?? (() => new MemoryBlockReference()),
+ Deserialize = deserialize ??
+ (context =>
+ {
+ return context.JsonElement.ValueKind == JsonValueKind.Object
+ ? context.JsonElement.Deserialize((JsonSerializerOptions?)context.Options)!
+ : new Expression(expressionType, null!);
+ })
};
if (monacoLanguage != null)
- descriptor.Properties = new { MonacoLanguage = monacoLanguage }.ToDictionary();
+ descriptor.Properties = new
+ {
+ MonacoLanguage = monacoLanguage
+ }.ToDictionary();
return descriptor;
}
diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Activities.cs b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Activities.cs
new file mode 100644
index 0000000000..6f76afac7a
--- /dev/null
+++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Activities.cs
@@ -0,0 +1,41 @@
+using System.Text.Json.Serialization;
+using Elsa.Expressions.Models;
+using Elsa.Extensions;
+using Elsa.Workflows.Memory;
+using Elsa.Workflows.Models;
+
+namespace Elsa.Workflows.IntegrationTests.Serialization.VariableExpressions;
+
+///
+public class NumberActivity : CodeActivity
+{
+ ///
+ [JsonConstructor]
+ public NumberActivity()
+ {
+ }
+
+ ///
+ public NumberActivity(Variable variable)
+ {
+ Number = new(variable);
+ }
+
+ ///
+ public NumberActivity(Literal literal)
+ {
+ Number = new(literal);
+ }
+
+ ///
+ /// Gets or sets the number.
+ ///
+ public Input Number { get; set; } = default!;
+
+ ///
+ protected override void Execute(ActivityExecutionContext context)
+ {
+ var number = Number.Get(context);
+ Console.WriteLine(number.ToString());
+ }
+}
\ No newline at end of file
diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Tests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Tests.cs
index 01cc375600..71068a82e9 100644
--- a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Tests.cs
+++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Tests.cs
@@ -18,23 +18,35 @@ public class Tests
{
private readonly IWorkflowSerializer _workflowSerializer;
private readonly IWorkflowBuilder _workflowBuilder;
+ private readonly IWorkflowRunner _workflowRunner;
+ ///
+ /// Initializes a new instance of the class.
+ ///
public Tests(ITestOutputHelper testOutputHelper)
{
var serviceProvider = new TestApplicationBuilder(testOutputHelper).Build();
_workflowSerializer = serviceProvider.GetRequiredService();
IWorkflowBuilderFactory workflowBuilderFactory = serviceProvider.GetRequiredService();
_workflowBuilder = workflowBuilderFactory.CreateBuilder();
+ _workflowRunner = serviceProvider.GetRequiredService();
}
+ ///
+ /// Variable types remain intact after serialization.
+ ///
[Fact(DisplayName = "Variable types remain intact after serialization")]
public async Task Test1()
{
var workflow = await _workflowBuilder.BuildWorkflowAsync();
var serialized = _workflowSerializer.Serialize(workflow);
var deserializedWorkflow = _workflowSerializer.Deserialize(serialized);
- var rehydratedWriteLine = (WriteLine)((Flowchart)deserializedWorkflow.Root).Activities.ElementAt(0);
+ var rehydratedWriteLine1 = (WriteLine)((Sequence)deserializedWorkflow.Root).Activities.ElementAt(0);
+ var rehydratedNumberActivity1 = (NumberActivity)((Sequence)deserializedWorkflow.Root).Activities.ElementAt(2);
- Assert.IsType>(rehydratedWriteLine.Text.Expression!.Value);
+ Assert.IsType>(rehydratedWriteLine1.Text.Expression!.Value);
+ Assert.IsType>(rehydratedNumberActivity1.Number.Expression!.Value);
+
+ await _workflowRunner.RunAsync(workflow);
}
}
\ No newline at end of file
diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Workflows.cs b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Workflows.cs
index 3d13a59fab..2de1b523c5 100644
--- a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Workflows.cs
+++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/VariableExpressions/Workflows.cs
@@ -1,6 +1,5 @@
-using Elsa.Workflows;
+using Elsa.Expressions.Models;
using Elsa.Workflows.Activities;
-using Elsa.Workflows.Activities.Flowchart.Activities;
using Elsa.Workflows.Contracts;
namespace Elsa.Workflows.IntegrationTests.Serialization.VariableExpressions;
@@ -9,16 +8,24 @@ class SampleWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
- var variable1 = workflow.WithVariable("Some Value");
+ var variable1 = workflow.WithVariable("Some variable");
+ var variable2 = workflow.WithVariable(42);
+ var literal1 = new Literal("Some literal");
+ var literal2 = new Literal(84);
var writeLine1 = new WriteLine(variable1);
+ var writeLine2 = new WriteLine(literal1);
+ var numberActivity1 = new NumberActivity(variable2);
+ var numberActivity2 = new NumberActivity(literal2);
- workflow.Root = new Flowchart
+ workflow.Root = new Sequence
{
Activities =
{
- writeLine1
- },
- Start = writeLine1
+ writeLine1,
+ writeLine2,
+ numberActivity1,
+ numberActivity2
+ }
};
}
}
\ No newline at end of file