diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Elsa.Samples.AspNet.WorkflowServer.csproj b/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Elsa.Samples.AspNet.WorkflowServer.csproj
index 9c538db64..4a446863f 100644
--- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Elsa.Samples.AspNet.WorkflowServer.csproj
+++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Elsa.Samples.AspNet.WorkflowServer.csproj
@@ -14,4 +14,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Program.cs b/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Program.cs
index a093abdf5..ead62e546 100644
--- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Program.cs
+++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Program.cs
@@ -40,6 +40,9 @@ builder.Services.AddElsa(elsa =>
// Register custom activities.
elsa.AddActivitiesFrom();
+
+ // Register custom workflows.
+ elsa.AddWorkflowsFrom();
});
// Configure CORS to allow designer app hosted on a different origin to invoke the APIs.
diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Workflows/MysteriousPondWorkflow.cs b/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Workflows/MysteriousPondWorkflow.cs
new file mode 100644
index 000000000..fd7cd1c78
--- /dev/null
+++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Workflows/MysteriousPondWorkflow.cs
@@ -0,0 +1,56 @@
+using System.Net.Mime;
+using Elsa.Expressions.Models;
+using Elsa.Http;
+using Elsa.Workflows;
+using Elsa.Workflows.Activities;
+using Elsa.Workflows.Contracts;
+
+namespace Elsa.Samples.AspNet.WorkflowServer.Workflows;
+
+///
+/// Represents a mysterious pond workflow.
+///
+public class MysteriousPondWorkflow : WorkflowBase
+{
+ protected override void Build(IWorkflowBuilder builder)
+ {
+ var investment = builder.WithVariable();
+
+ builder.Root = new Sequence
+ {
+ Activities =
+ {
+ new HttpEndpoint
+ {
+ Path = new("mysterious_pond"),
+ SupportedMethods = new([HttpMethod.Post.Method]),
+ ParsedContent = new(investment),
+ CanStartWorkflow = true
+ },
+ new WriteLine(context => $"Received {GetRupees(context)} rupees"),
+ new Switch
+ {
+ Cases =
+ {
+ new SwitchCase("Great Luck", context => GetRupees(context) >= 1000, WriteHttpResponse("For today, you will have great luck.")),
+ new SwitchCase("Good Luck", context => GetRupees(context) >= 500, WriteHttpResponse("For today, you will have good luck.")),
+ new SwitchCase("A Little Luck", context => GetRupees(context) >= 250, WriteHttpResponse("For today, you will have a little luck.")),
+ new SwitchCase("Bad Luck", context => GetRupees(context) < 250, WriteHttpResponse("For today, you will have bad luck.")),
+
+ }
+ }
+ }
+ };
+ return;
+
+ int GetRupees(ExpressionExecutionContext context) => investment.Get(context)!.Rupees;
+ }
+
+ private static WriteHttpResponse WriteHttpResponse(string message) => new()
+ {
+ Content = new(message),
+ ContentType = new(MediaTypeNames.Text.Plain),
+ };
+
+ private record Investment(int Rupees);
+};
\ No newline at end of file
diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/mysterious_pond.http b/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/mysterious_pond.http
new file mode 100644
index 000000000..1a5c9f72c
--- /dev/null
+++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/mysterious_pond.http
@@ -0,0 +1,31 @@
+### POST 1000 rupees to the mysterious pond.
+POST https://localhost:5001/workflows/mysterious_pond
+Content-Type: application/json
+
+{
+ "rupees": 1000
+}
+
+### POST 500 rupees to the mysterious pond.
+POST https://localhost:5001/workflows/mysterious_pond
+Content-Type: application/json
+
+{
+ "rupees": 500
+}
+
+### POST 250 rupees to the mysterious pond.
+POST https://localhost:5001/workflows/mysterious_pond
+Content-Type: application/json
+
+{
+ "rupees": 250
+}
+
+### POST 100 rupees to the mysterious pond.
+POST https://localhost:5001/workflows/mysterious_pond
+Content-Type: application/json
+
+{
+ "rupees": 100
+}
diff --git a/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj b/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj
index 1b9a8a9bc..189840cc2 100644
--- a/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj
+++ b/src/bundles/Elsa.Server.Web/Elsa.Server.Web.csproj
@@ -59,6 +59,7 @@
+
diff --git a/src/modules/Elsa.Expressions/Extensions/JsonElementExtensions.cs b/src/modules/Elsa.Expressions/Extensions/JsonElementExtensions.cs
new file mode 100644
index 000000000..e1975c2d8
--- /dev/null
+++ b/src/modules/Elsa.Expressions/Extensions/JsonElementExtensions.cs
@@ -0,0 +1,31 @@
+using System.Text.Json;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+///
+/// Parses a into a .NET object.
+///
+public static class JsonElementExtensions
+{
+ ///
+ /// Parses a into a .NET object.
+ ///
+ /// The JSON element to parse.
+ /// The parsed object.
+ public static object? GetValue(this JsonElement jsonElement)
+ {
+ return jsonElement.ValueKind switch
+ {
+ JsonValueKind.String => jsonElement.GetString(),
+ JsonValueKind.Number => jsonElement.GetDecimal(),
+ JsonValueKind.True => true,
+ JsonValueKind.False => false,
+ JsonValueKind.Undefined => null,
+ JsonValueKind.Null => null,
+ JsonValueKind.Object => jsonElement.GetRawText(),
+ JsonValueKind.Array => jsonElement.GetRawText(),
+ _ => jsonElement.GetRawText()
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Expressions/Models/ExpressionDescriptor.cs b/src/modules/Elsa.Expressions/Models/ExpressionDescriptor.cs
index b63ac9d7a..084c3612e 100644
--- a/src/modules/Elsa.Expressions/Models/ExpressionDescriptor.cs
+++ b/src/modules/Elsa.Expressions/Models/ExpressionDescriptor.cs
@@ -1,5 +1,6 @@
using System.Text.Json;
using Elsa.Expressions.Contracts;
+using Elsa.Extensions;
namespace Elsa.Expressions.Models;
@@ -16,9 +17,13 @@ public class ExpressionDescriptor
// Default deserialization function.
Deserialize = context =>
{
- return context.JsonElement.ValueKind == JsonValueKind.Object
- ? context.JsonElement.Deserialize((JsonSerializerOptions?)context.Options)!
- : new Expression(context.ExpressionType, null!);
+ var expression = new Expression(context.ExpressionType, null);
+
+ if (context.JsonElement.ValueKind == JsonValueKind.Object)
+ if (context.JsonElement.TryGetProperty("value", out var expressionValueElement))
+ expression.Value = expressionValueElement.GetValue();
+
+ return expression;
};
}
diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/ActivityJsonConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/ActivityJsonConverter.cs
index 4859f023f..62b6a2223 100644
--- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/ActivityJsonConverter.cs
+++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/ActivityJsonConverter.cs
@@ -217,6 +217,7 @@ public class ActivityJsonConverter : JsonConverter
var newOptions = new JsonSerializerOptions(options);
newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider));
newOptions.Converters.Add(new OutputJsonConverterFactory(_serviceProvider));
+ newOptions.Converters.Add(new ExpressionJsonConverterFactory(_expressionDescriptorRegistry));
return _options = newOptions;
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/ExpressionJsonConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/ExpressionJsonConverter.cs
new file mode 100644
index 000000000..70fe6ddf5
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/ExpressionJsonConverter.cs
@@ -0,0 +1,74 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Elsa.Expressions.Contracts;
+using Elsa.Expressions.Models;
+using Elsa.Workflows.Models;
+
+namespace Elsa.Workflows.Serialization.Converters;
+
+///
+/// Serializes objects.
+///
+public class ExpressionJsonConverter : JsonConverter
+{
+ private readonly IExpressionDescriptorRegistry _expressionDescriptorRegistry;
+
+ ///
+ public ExpressionJsonConverter(IExpressionDescriptorRegistry expressionDescriptorRegistry)
+ {
+ _expressionDescriptorRegistry = expressionDescriptorRegistry;
+ }
+
+ ///
+ public override bool CanConvert(Type typeToConvert) => typeof(Input).IsAssignableFrom(typeToConvert);
+
+ ///
+ public override Expression Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (!JsonDocument.TryParseValue(ref reader, out var doc))
+ return default!;
+
+ var expressionElement = doc.RootElement;
+ var expressionTypeNameElement = expressionElement.TryGetProperty("type", out var expressionTypeNameElementValue) ? expressionTypeNameElementValue : default;
+ 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(expressionTypeName!, expressionElement, options, memoryBlockType);
+ var expression = expressionDescriptor!.Deserialize(context);
+
+ return expression;
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, Expression value, JsonSerializerOptions options)
+ {
+ var expression = value;
+
+ if(expression == null)
+ {
+ writer.WriteNullValue();
+ return;
+ }
+
+ var expressionType = expression.Type;
+ var expressionDescriptor = expressionType != null ? _expressionDescriptorRegistry.Find(expressionType) : null;
+
+ if (expressionDescriptor == null)
+ throw new JsonException($"Could not find an expression descriptor for expression type '{expressionType}'.");
+
+ var expressionValue = expressionDescriptor.IsSerializable ? expression.Value : null;
+
+ var model = new
+ {
+ Type = expressionType,
+ Value = expressionValue
+ };
+
+ JsonSerializer.Serialize(writer, model, options);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/ExpressionJsonConverterFactory.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/ExpressionJsonConverterFactory.cs
new file mode 100644
index 000000000..0af35b397
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/ExpressionJsonConverterFactory.cs
@@ -0,0 +1,29 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Elsa.Expressions.Contracts;
+using Elsa.Expressions.Models;
+
+namespace Elsa.Workflows.Serialization.Converters;
+
+///
+/// A JSON converter factory that creates instances.
+///
+public class ExpressionJsonConverterFactory : JsonConverterFactory
+{
+ private readonly IExpressionDescriptorRegistry _expressionDescriptorRegistry;
+
+ ///
+ public ExpressionJsonConverterFactory(IExpressionDescriptorRegistry expressionDescriptorRegistry)
+ {
+ _expressionDescriptorRegistry = expressionDescriptorRegistry;
+ }
+
+ ///
+ public override bool CanConvert(Type typeToConvert) => typeof(Expression).IsAssignableFrom(typeToConvert);
+
+ ///
+ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new ExpressionJsonConverter(_expressionDescriptorRegistry);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/InputJsonConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/InputJsonConverter.cs
index f0f58f70f..f1c6c80d9 100644
--- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/InputJsonConverter.cs
+++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/InputJsonConverter.cs
@@ -49,7 +49,6 @@ public class InputJsonConverter : JsonConverter>
var memoryBlockType = memoryBlockReference.GetType();
var context = new ExpressionSerializationContext(expressionTypeName!, expressionElement, options, memoryBlockType);
var expression = expressionDescriptor!.Deserialize(context);
-
return (Input)Activator.CreateInstance(typeof(Input), expression, memoryBlockReference)!;
}
@@ -61,14 +60,14 @@ public class InputJsonConverter : JsonConverter>
public override void Write(Utf8JsonWriter writer, Input value, JsonSerializerOptions options)
{
var expression = value.Expression;
-
- if(expression == null)
+
+ if (expression == null)
{
writer.WriteNullValue();
return;
}
-
- var expressionType = expression?.Type;
+
+ var expressionType = expression.Type;
var expressionDescriptor = expressionType != null ? _expressionDescriptorRegistry.Find(expressionType) : default;
if (expressionDescriptor == null)
diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonActivitySerializer.cs b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonActivitySerializer.cs
index c206fefd6..704b48bca 100644
--- a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonActivitySerializer.cs
+++ b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonActivitySerializer.cs
@@ -39,6 +39,8 @@ public class JsonActivitySerializer(IServiceProvider serviceProvider) : Configur
{
options.Converters.Add(CreateInstance());
options.Converters.Add(CreateInstance());
+ options.Converters.Add(CreateInstance());
+ options.Converters.Add(CreateInstance());
}
private JsonSerializerOptions GetOptionsInternal()
diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/SafeSerializer.cs b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/SafeSerializer.cs
index 12a79f64b..a9437dba0 100644
--- a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/SafeSerializer.cs
+++ b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/SafeSerializer.cs
@@ -2,9 +2,11 @@ using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Common.Serialization;
+using Elsa.Expressions.Contracts;
using Elsa.Expressions.Services;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Serialization.Converters;
+using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.Serialization.Serializers;
@@ -51,8 +53,11 @@ public class SafeSerializer : ConfigurableSerializer, ISafeSerializer
///
protected override void AddConverters(JsonSerializerOptions options)
{
+ var expressionDescriptorRegistry = ServiceProvider.GetRequiredService();
+
options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
options.Converters.Add(new TypeJsonConverter(WellKnownTypeRegistry.CreateDefault()));
options.Converters.Add(new SafeValueConverterFactory());
+ options.Converters.Add(new ExpressionJsonConverterFactory(expressionDescriptorRegistry));
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionDescriptorProvider.cs b/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionDescriptorProvider.cs
index c0d738ba5..e85742a7c 100644
--- a/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionDescriptorProvider.cs
+++ b/src/modules/Elsa.Workflows.Management/Providers/DefaultExpressionDescriptorProvider.cs
@@ -32,20 +32,7 @@ public class DefaultExpressionDescriptorProvider : IExpressionDescriptorProvider
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,
- JsonValueKind.Undefined => null,
- JsonValueKind.Null => null,
- JsonValueKind.Object => elementValue.GetRawText(),
- JsonValueKind.Array => elementValue.GetRawText(),
- _ => v.GetRawText()
- });
-
+ var value = elementValue.GetValue();
return new Expression("Literal", value);
});
}
@@ -93,7 +80,7 @@ public class DefaultExpressionDescriptorProvider : IExpressionDescriptorProvider
MemoryBlockReferenceFactory = memoryBlockReferenceFactory ?? (() => new MemoryBlockReference())
};
- if (deserialize != null)
+ if (deserialize != null)
descriptor.Deserialize = deserialize;
if (monacoLanguage != null)
diff --git a/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowProvider.cs b/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowProvider.cs
index c20d8cbdd..7125fabcf 100644
--- a/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowProvider.cs
+++ b/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowProvider.cs
@@ -51,6 +51,7 @@ public class ClrWorkflowProvider : IWorkflowProvider
var workflowBuilderType = workflowBuilder.GetType();
builder.DefinitionId = workflowBuilderType.Name;
+ builder.Id = $"{workflowBuilderType.Name}:1.0";
await workflowBuilder.BuildAsync(builder, cancellationToken);
var workflow = await builder.BuildWorkflowAsync(cancellationToken);