diff --git a/src/bundles/Elsa.Server.Web/SampleWorkflow.cs b/src/bundles/Elsa.Server.Web/SampleWorkflow.cs new file mode 100644 index 000000000..44c8a15ca --- /dev/null +++ b/src/bundles/Elsa.Server.Web/SampleWorkflow.cs @@ -0,0 +1,41 @@ +using Elsa.Expressions.Models; +using Elsa.Extensions; +using Elsa.Scheduling.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Activities; +using Elsa.Workflows.Contracts; + +namespace Elsa.Server.Web; + +public class SampleWorkflow : WorkflowBase +{ + protected override void Build(IWorkflowBuilder workflow) + { + // The WithVariable method ensures that the created variable will be added to the Workflow's Variables collection, which is required for persistent variables. + var variable1 = workflow.WithVariable("Foo").WithWorkflowStorage(); + + workflow.Variables = + [ + variable1 + ]; + + workflow.Root = new Sequence + { + Activities = + { + new StartAt(DateTimeOffset.UtcNow + TimeSpan.FromSeconds(5)) + { + CanStartWorkflow = true + }, + new WriteLine(variable1), + new SetVariable + { + Variable = variable1, + Value = new (Literal.From("Bar")) + }, + new Delay(TimeSpan.FromSeconds(1)), + new WriteLine(variable1) + } + }; + } +} \ No newline at end of file diff --git a/src/bundles/Elsa.Server.Web/appsettings.json b/src/bundles/Elsa.Server.Web/appsettings.json index a6d14c54d..549fea290 100644 --- a/src/bundles/Elsa.Server.Web/appsettings.json +++ b/src/bundles/Elsa.Server.Web/appsettings.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Debug", + "Default": "Warning", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "OpenTelemetry": "Debug" diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs index 6ba1e6757..aba418fc6 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperActivityExecutionRecordStore.cs @@ -38,14 +38,14 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore /// public async Task SaveAsync(ActivityExecutionRecord record, CancellationToken cancellationToken = default) { - var mappedRecord = await Map(record, cancellationToken); + var mappedRecord = Map(record, cancellationToken); await _store.SaveAsync(mappedRecord, PrimaryKeyName, cancellationToken); } /// public async Task SaveManyAsync(IEnumerable records, CancellationToken cancellationToken = default) { - var mappedRecords = await Task.WhenAll(records.Select(async x => await Map(x, cancellationToken))); + var mappedRecords = records.Select(x => Map(x, cancellationToken)); await _store.SaveManyAsync(mappedRecords, PrimaryKeyName, cancellationToken); } @@ -53,21 +53,21 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore public async Task FindAsync(ActivityExecutionRecordFilter filter, CancellationToken cancellationToken = default) { var record = await _store.FindAsync(q => ApplyFilter(q, filter), cancellationToken); - return record == null ? null : await MapAsync(record, cancellationToken); + return record == null ? null : Map(record, cancellationToken); } /// public async Task> FindManyAsync(ActivityExecutionRecordFilter filter, ActivityExecutionRecordOrder order, CancellationToken cancellationToken = default) { var records = await _store.FindManyAsync(q => ApplyFilter(q, filter), order.KeySelector.GetPropertyName(), order.Direction, cancellationToken); - return await Task.WhenAll(records.Select(async x => await MapAsync(x, cancellationToken))); + return records.Select( x => Map(x, cancellationToken)).ToList(); } /// public async Task> FindManyAsync(ActivityExecutionRecordFilter filter, CancellationToken cancellationToken = default) { var records = await _store.FindManyAsync(q => ApplyFilter(q, filter), cancellationToken); - return await Task.WhenAll(records.Select(async x => await MapAsync(x, cancellationToken))); + return records.Select( x => Map(x, cancellationToken)).ToList(); } /// @@ -115,7 +115,7 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore } } - private async ValueTask Map(ActivityExecutionRecord source, CancellationToken cancellationToken) + private ActivityExecutionRecordRecord Map(ActivityExecutionRecord source, CancellationToken cancellationToken) { return new ActivityExecutionRecordRecord { @@ -130,15 +130,15 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore HasBookmarks = source.HasBookmarks, Status = source.Status.ToString(), ActivityTypeVersion = source.ActivityTypeVersion, - SerializedActivityState = source.ActivityState != null ? await _safeSerializer.SerializeAsync(source.ActivityState, cancellationToken) : null, - SerializedPayload = source.Payload != null ? await _safeSerializer.SerializeAsync(source.Payload, cancellationToken) : null, - SerializedOutputs = source.Outputs?.Any() == true ? await _safeSerializer.SerializeAsync(source.Outputs, cancellationToken) : null, + SerializedActivityState = source.ActivityState != null ? _safeSerializer.Serialize(source.ActivityState, cancellationToken) : null, + SerializedPayload = source.Payload != null ? _safeSerializer.Serialize(source.Payload, cancellationToken) : null, + SerializedOutputs = source.Outputs?.Any() == true ? _safeSerializer.Serialize(source.Outputs, cancellationToken) : null, SerializedException = source.Exception != null ? _payloadSerializer.Serialize(source.Exception) : null, - SerializedProperties = source.Properties.Any() ? await _safeSerializer.SerializeAsync(source.Properties, cancellationToken) : null + SerializedProperties = source.Properties.Any() ? _safeSerializer.Serialize(source.Properties, cancellationToken) : null }; } - private async ValueTask MapAsync(ActivityExecutionRecordRecord source, CancellationToken cancellationToken) + private ActivityExecutionRecord Map(ActivityExecutionRecordRecord source, CancellationToken cancellationToken) { return new ActivityExecutionRecord { @@ -153,11 +153,11 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore HasBookmarks = source.HasBookmarks, Status = Enum.Parse(source.Status), ActivityTypeVersion = source.ActivityTypeVersion, - ActivityState = source.SerializedActivityState != null ? _payloadSerializer.Deserialize>(source.SerializedActivityState) : default, - Payload = source.SerializedPayload != null ? await _safeSerializer.DeserializeAsync>(source.SerializedPayload, cancellationToken) : default, - Outputs = source.SerializedOutputs != null ? await _safeSerializer.DeserializeAsync>(source.SerializedOutputs, cancellationToken) : default, - Exception = source.SerializedException != null ? _payloadSerializer.Deserialize(source.SerializedException) : default, - Properties = source.SerializedProperties != null ? await _safeSerializer.DeserializeAsync>(source.SerializedProperties, cancellationToken) : default + ActivityState = source.SerializedActivityState != null ? _payloadSerializer.Deserialize>(source.SerializedActivityState) : null, + Payload = source.SerializedPayload != null ? _safeSerializer.Deserialize>(source.SerializedPayload, cancellationToken) : null, + Outputs = source.SerializedOutputs != null ? _safeSerializer.Deserialize>(source.SerializedOutputs, cancellationToken) : null, + Exception = source.SerializedException != null ? _payloadSerializer.Deserialize(source.SerializedException) : null, + Properties = source.SerializedProperties != null ? _safeSerializer.Deserialize>(source.SerializedProperties, cancellationToken) : null }; } diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/ActivityExecutionLogStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/ActivityExecutionLogStore.cs index da49cb3f1..68652db65 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/ActivityExecutionLogStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/ActivityExecutionLogStore.cs @@ -83,12 +83,12 @@ public class EFCoreActivityExecutionStore( { entity = entity.SanitizeLogMessage(); var compressionAlgorithm = options.Value.CompressionAlgorithm ?? nameof(None); - var serializedActivityState = entity.ActivityState != null ? await safeSerializer.SerializeAsync(entity.ActivityState, cancellationToken) : null; + var serializedActivityState = entity.ActivityState != null ? safeSerializer.Serialize(entity.ActivityState, cancellationToken) : null; var compressedSerializedActivityState = serializedActivityState != null ? await compressionCodecResolver.Resolve(compressionAlgorithm).CompressAsync(serializedActivityState, cancellationToken) : null; dbContext.Entry(entity).Property("SerializedActivityState").CurrentValue = compressedSerializedActivityState; dbContext.Entry(entity).Property("SerializedActivityStateCompressionAlgorithm").CurrentValue = compressionAlgorithm; - dbContext.Entry(entity).Property("SerializedOutputs").CurrentValue = entity.Outputs?.Any() == true ? await safeSerializer.SerializeAsync(entity.Outputs, cancellationToken) : null; + dbContext.Entry(entity).Property("SerializedOutputs").CurrentValue = entity.Outputs?.Any() == true ? safeSerializer.Serialize(entity.Outputs, cancellationToken) : null; dbContext.Entry(entity).Property("SerializedProperties").CurrentValue = entity.Properties.Any() ? payloadSerializer.Serialize(entity.Properties) : null; dbContext.Entry(entity).Property("SerializedException").CurrentValue = entity.Exception != null ? payloadSerializer.Serialize(entity.Exception) : null; dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload?.Any() == true ? payloadSerializer.Serialize(entity.Payload) : null; diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowExecutionLogStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowExecutionLogStore.cs index 5ff56801b..6c6aa563b 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowExecutionLogStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowExecutionLogStore.cs @@ -87,8 +87,8 @@ public class EFCoreWorkflowExecutionLogStore : IWorkflowExecutionLogStore private async ValueTask OnSaveAsync(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord entity, CancellationToken cancellationToken) { entity = entity.SanitizeLogMessage(); - dbContext.Entry(entity).Property("SerializedActivityState").CurrentValue = entity.ActivityState?.Any() == true ? await _safeSerializer.SerializeAsync(entity.ActivityState, cancellationToken) : default; - dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload != null ? await _safeSerializer.SerializeAsync(entity.Payload, cancellationToken) : default; + dbContext.Entry(entity).Property("SerializedActivityState").CurrentValue = entity.ActivityState?.Any() == true ? _safeSerializer.Serialize(entity.ActivityState, cancellationToken) : null; + dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload != null ? _safeSerializer.Serialize(entity.Payload, cancellationToken) : null; } private async ValueTask OnLoadAsync(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord? entity, CancellationToken cancellationToken) diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Export/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Export/Endpoint.cs index 5a925a207..4ce168a0b 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Export/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Export/Endpoint.cs @@ -121,10 +121,10 @@ internal class Export : ElsaEndpointWithMapper var executionLogRecords = request.IncludeWorkflowExecutionLog ? await LoadWorkflowExecutionLogRecordsAsync(workflowState.Id, cancellationToken) : default; var activityExecutionLogRecords = request.IncludeActivityExecutionLog ? await LoadActivityExecutionLogRecordsAsync(workflowState.Id, cancellationToken) : default; var bookmarks = request.IncludeBookmarks ? await LoadBookmarksAsync(workflowState.Id, cancellationToken) : null; - var workflowStateElement = await _workflowStateSerializer.SerializeToElementAsync(workflowState, cancellationToken); + var workflowStateElement = _workflowStateSerializer.SerializeToElement(workflowState, cancellationToken); var bookmarksElement = bookmarks != null ? SerializeBookmarks(bookmarks) : default(JsonElement?); - var executionLogRecordsElement = executionLogRecords != null ? await _safeSerializer.SerializeToElementAsync(executionLogRecords, cancellationToken) : default(JsonElement?); - var activityExecutionLogRecordsElement = activityExecutionLogRecords != null ? await _safeSerializer.SerializeToElementAsync(activityExecutionLogRecords, cancellationToken) : default(JsonElement?); + var executionLogRecordsElement = executionLogRecords != null ? _safeSerializer.SerializeToElement(executionLogRecords, cancellationToken) : default(JsonElement?); + var activityExecutionLogRecordsElement = activityExecutionLogRecords != null ? _safeSerializer.SerializeToElement(activityExecutionLogRecords, cancellationToken) : default(JsonElement?); var model = new ExportedWorkflowState(workflowStateElement, bookmarksElement, activityExecutionLogRecordsElement, executionLogRecordsElement); return model; } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Import/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Import/Endpoint.cs index 1a8170769..295a577df 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Import/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Import/Endpoint.cs @@ -137,13 +137,13 @@ internal class Import : ElsaEndpointWithoutRequest if (model.ActivityExecutionRecords != null) { - var activityExecutionRecords = await _safeSerializer.DeserializeAsync>(model.ActivityExecutionRecords.Value, cancellationToken); + var activityExecutionRecords = _safeSerializer.Deserialize>(model.ActivityExecutionRecords.Value, cancellationToken); await _activityExecutionStore.SaveManyAsync(activityExecutionRecords, cancellationToken); } if (model.WorkflowExecutionLogRecords != null) { - var workflowExecutionLogRecords = await _safeSerializer.DeserializeAsync>(model.WorkflowExecutionLogRecords.Value, cancellationToken); + var workflowExecutionLogRecords = _safeSerializer.Deserialize>(model.WorkflowExecutionLogRecords.Value, cancellationToken); await _workflowExecutionLogStore.SaveManyAsync(workflowExecutionLogRecords, cancellationToken); } } diff --git a/src/modules/Elsa.Workflows.Core/Contracts/ISafeSerializer.cs b/src/modules/Elsa.Workflows.Core/Contracts/ISafeSerializer.cs index e241694d3..79f18e7b2 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/ISafeSerializer.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/ISafeSerializer.cs @@ -11,24 +11,52 @@ public interface ISafeSerializer /// /// Serializes the specified state. /// + [Obsolete("Use the non-async Serialize instead.")] [RequiresUnreferencedCode("The type T may be trimmed.")] ValueTask SerializeAsync(object? value, CancellationToken cancellationToken = default); /// /// Serializes the specified state to a object. /// + [Obsolete("Use the non-async SerializeToElement instead.")] [RequiresUnreferencedCode("The type T may be trimmed.")] ValueTask SerializeToElementAsync(object? value, CancellationToken cancellationToken = default); /// /// Deserializes the specified state. /// + [Obsolete("Use the non-async Deserialize instead.")] [RequiresUnreferencedCode("The type T may be trimmed.")] ValueTask DeserializeAsync(string json, CancellationToken cancellationToken = default); /// /// Deserializes the specified state. /// + [Obsolete("Use the non-async Deserialize instead.")] [RequiresUnreferencedCode("The type T may be trimmed.")] ValueTask DeserializeAsync(JsonElement element, CancellationToken cancellationToken = default); + + /// + /// Serializes the specified state. + /// + [RequiresUnreferencedCode("The type T may be trimmed.")] + string Serialize(object? value, CancellationToken cancellationToken = default); + + /// + /// Serializes the specified state to a object. + /// + [RequiresUnreferencedCode("The type T may be trimmed.")] + JsonElement SerializeToElement(object? value, CancellationToken cancellationToken = default); + + /// + /// Deserializes the specified state. + /// + [RequiresUnreferencedCode("The type T may be trimmed.")] + T Deserialize(string json, CancellationToken cancellationToken = default); + + /// + /// Deserializes the specified state. + /// + [RequiresUnreferencedCode("The type T may be trimmed.")] + T Deserialize(JsonElement element, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowStateSerializer.cs b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowStateSerializer.cs index 5a113bef3..89325b77d 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowStateSerializer.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowStateSerializer.cs @@ -16,8 +16,9 @@ public interface IWorkflowStateSerializer /// The cancellation token. /// The serialized workflow state. [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [Obsolete("Use the non-async version Serialize instead.")] Task SerializeAsync(WorkflowState workflowState, CancellationToken cancellationToken = default); - + /// /// Serializes the specified workflow state. /// @@ -25,8 +26,18 @@ public interface IWorkflowStateSerializer /// The cancellation token. /// The serialized workflow state. [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + string Serialize(WorkflowState workflowState, CancellationToken cancellationToken = default); + + /// + /// Serializes the specified workflow state. + /// + /// The workflow state to serialize. + /// The cancellation token. + /// The serialized workflow state. + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [Obsolete("Use the non-async version SerializeToUtfBytes instead.")] Task SerializeToUtfBytesAsync(WorkflowState workflowState, CancellationToken cancellationToken = default); - + /// /// Serializes the specified workflow state. /// @@ -34,8 +45,18 @@ public interface IWorkflowStateSerializer /// The cancellation token. /// The serialized workflow state. [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + byte[] SerializeToUtfBytes(WorkflowState workflowState, CancellationToken cancellationToken = default); + + /// + /// Serializes the specified workflow state. + /// + /// The workflow state to serialize. + /// The cancellation token. + /// The serialized workflow state. + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [Obsolete("Use the non-async version SerializeToElement instead.")] Task SerializeToElementAsync(WorkflowState workflowState, CancellationToken cancellationToken = default); - + /// /// Serializes the specified workflow state. /// @@ -43,32 +64,81 @@ public interface IWorkflowStateSerializer /// The cancellation token. /// The serialized workflow state. [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + JsonElement SerializeToElement(WorkflowState workflowState, CancellationToken cancellationToken = default); + + /// + /// Serializes the specified workflow state. + /// + /// The workflow state to serialize. + /// The cancellation token. + /// The serialized workflow state. + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [Obsolete("Use the non-async version Serialize instead.")] Task SerializeAsync(object workflowState, CancellationToken cancellationToken = default); - + + /// + /// Serializes the specified workflow state. + /// + /// The workflow state to serialize. + /// The cancellation token. + /// The serialized workflow state. + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + string Serialize(object workflowState, CancellationToken cancellationToken = default); + /// /// Deserializes the specified serialized state. /// /// The serialized state. /// The cancellation token. /// The deserialized workflow state. - [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")] + [Obsolete("Use the non-async version Deserialize instead.")] Task DeserializeAsync(string serializedState, CancellationToken cancellationToken = default); - + /// /// Deserializes the specified serialized state. /// /// The serialized state. /// The cancellation token. /// The deserialized workflow state. - [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")] + WorkflowState Deserialize(string serializedState, CancellationToken cancellationToken = default); + + /// + /// Deserializes the specified serialized state. + /// + /// The serialized state. + /// The cancellation token. + /// The deserialized workflow state. + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")] + [Obsolete("Use the non-async version Deserialize instead.")] Task DeserializeAsync(JsonElement serializedState, CancellationToken cancellationToken = default); - + /// /// Deserializes the specified serialized state. /// /// The serialized state. /// The cancellation token. /// The deserialized workflow state. - [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")] + WorkflowState Deserialize(JsonElement serializedState, CancellationToken cancellationToken = default); + + /// + /// Deserializes the specified serialized state. + /// + /// The serialized state. + /// The cancellation token. + /// The deserialized workflow state. + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")] + [Obsolete("Use the non-async version Deserialize instead.")] Task DeserializeAsync(string serializedState, CancellationToken cancellationToken = default); + + /// + /// Deserializes the specified serialized state. + /// + /// The serialized state. + /// The cancellation token. + /// The deserialized workflow state. + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")] + T Deserialize(string serializedState, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs index 5c18306fc..9c7f5c2c4 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs @@ -253,7 +253,7 @@ public static class ActivityExecutionContextExtensions // Serializing the value ensures we store a copy of the value and not a reference to the input, which may change over time. if (inputDescriptor.IsSerializable != false) { - var serializedValue = await context.GetRequiredService().SerializeToElementAsync(value); + var serializedValue = context.GetRequiredService().SerializeToElement(value); context.ActivityState[inputDescriptor.Name] = serializedValue; } @@ -409,7 +409,7 @@ public static class ActivityExecutionContextExtensions if (outputValue == null!) continue; - var serializedOutputValue = await serializer.SerializeAsync(outputValue, cancellationToken); + var serializedOutputValue = serializer.Serialize(outputValue, cancellationToken); context.JournalData[outputName] = serializedOutputValue; } diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonWorkflowStateSerializer.cs b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonWorkflowStateSerializer.cs index 3d71dd61c..341f2ff5f 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonWorkflowStateSerializer.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonWorkflowStateSerializer.cs @@ -31,62 +31,100 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat /// [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [Obsolete("Use the non-async version Serialize instead.")] public Task SerializeAsync(WorkflowState workflowState, CancellationToken cancellationToken = default) { - var options = GetOptions(); - return Task.FromResult(JsonSerializer.Serialize(workflowState, options)); + return Task.FromResult(Serialize(workflowState, cancellationToken)); } /// [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [Obsolete("Use the non-async version SerializeToUtfBytes instead.")] public Task SerializeToUtfBytesAsync(WorkflowState workflowState, CancellationToken cancellationToken = default) { - var options = GetOptions(); - return Task.FromResult(JsonSerializer.SerializeToUtf8Bytes(workflowState, options)); + return Task.FromResult(SerializeToUtfBytes(workflowState, cancellationToken)); } /// [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [Obsolete("Use the non-async version SerializeToElement instead.")] public Task SerializeToElementAsync(WorkflowState workflowState, CancellationToken cancellationToken = default) { - var options = GetOptions(); - return Task.FromResult(JsonSerializer.SerializeToElement(workflowState, options)); + return Task.FromResult(SerializeToElement(workflowState, cancellationToken)); } /// - [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")] + [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")] + [Obsolete("Use the non-async version Serialize instead.")] public Task SerializeAsync(object workflowState, CancellationToken cancellationToken = default) { - var options = GetOptions(); - var json = JsonSerializer.Serialize(workflowState, workflowState.GetType(), options); - return Task.FromResult(json); + return Task.FromResult(Serialize(workflowState, cancellationToken)); } /// [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")] + [Obsolete("Use the non-async version Deserialize instead.")] public Task DeserializeAsync(string serializedState, CancellationToken cancellationToken = default) { - var options = GetOptions(); - var workflowState = JsonSerializer.Deserialize(serializedState, options)!; - return Task.FromResult(workflowState); + return Task.FromResult(Deserialize(serializedState, cancellationToken)); } /// [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")] + [Obsolete("Use the non-async version Deserialize instead.")] public Task DeserializeAsync(JsonElement serializedState, CancellationToken cancellationToken = default) { - var options = GetOptions(); - var workflowState = serializedState.Deserialize(options)!; - return Task.FromResult(workflowState); + return Task.FromResult(Deserialize(serializedState, cancellationToken)); } /// [RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")] + [Obsolete("Use the non-async version Deserialize instead.")] public Task DeserializeAsync(string serializedState, CancellationToken cancellationToken = default) + { + return Task.FromResult(Deserialize(serializedState, cancellationToken)); + } + + public string Serialize(WorkflowState workflowState, CancellationToken cancellationToken = default) { var options = GetOptions(); - var workflowState = JsonSerializer.Deserialize(serializedState, options)!; - return Task.FromResult(workflowState); + return JsonSerializer.Serialize(workflowState, options); + } + + public byte[] SerializeToUtfBytes(WorkflowState workflowState, CancellationToken cancellationToken = default) + { + var options = GetOptions(); + return JsonSerializer.SerializeToUtf8Bytes(workflowState, options); + } + + public JsonElement SerializeToElement(WorkflowState workflowState, CancellationToken cancellationToken = default) + { + var options = GetOptions(); + return JsonSerializer.SerializeToElement(workflowState, options); + } + + public string Serialize(object workflowState, CancellationToken cancellationToken = default) + { + var options = GetOptions(); + return JsonSerializer.Serialize(workflowState, workflowState.GetType(), options); + } + + public WorkflowState Deserialize(string serializedState, CancellationToken cancellationToken = default) + { + var options = GetOptions(); + return JsonSerializer.Deserialize(serializedState, options)!; + } + + public WorkflowState Deserialize(JsonElement serializedState, CancellationToken cancellationToken = default) + { + var options = GetOptions(); + return serializedState.Deserialize(options)!; + } + + public T Deserialize(string serializedState, CancellationToken cancellationToken = default) + { + var options = GetOptions(); + return JsonSerializer.Deserialize(serializedState, options)!; } /// diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/SafeSerializer.cs b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/SafeSerializer.cs index a9437dba0..6a868c337 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/SafeSerializer.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/SafeSerializer.cs @@ -22,39 +22,59 @@ public class SafeSerializer : ConfigurableSerializer, ISafeSerializer [RequiresUnreferencedCode("The type T may be trimmed.")] public ValueTask SerializeAsync(object? value, CancellationToken cancellationToken = default) { - var options = GetOptions(); - return ValueTask.FromResult(JsonSerializer.Serialize(value, options)); + return ValueTask.FromResult(Serialize(value, cancellationToken)); } /// [RequiresUnreferencedCode("The type T may be trimmed.")] public ValueTask SerializeToElementAsync(object? value, CancellationToken cancellationToken = default) { - var options = GetOptions(); - return new(JsonSerializer.SerializeToElement(value, options)); + return new(SerializeToElement(value, cancellationToken)); } /// [RequiresUnreferencedCode("The type T may be trimmed.")] public ValueTask DeserializeAsync(string json, CancellationToken cancellationToken = default) { - var options = GetOptions(); - return new(JsonSerializer.Deserialize(json, options)!); + return new(Deserialize(json, cancellationToken)); } /// [RequiresUnreferencedCode("The type T may be trimmed.")] public ValueTask DeserializeAsync(JsonElement element, CancellationToken cancellationToken = default) + { + return new(Deserialize(element, cancellationToken)); + } + + public string Serialize(object? value, CancellationToken cancellationToken = default) { var options = GetOptions(); - return new(element.Deserialize(options)!); + return JsonSerializer.Serialize(value, options); + } + + public JsonElement SerializeToElement(object? value, CancellationToken cancellationToken = default) + { + var options = GetOptions(); + return JsonSerializer.SerializeToElement(value, options); + } + + public T Deserialize(string json, CancellationToken cancellationToken = default) + { + var options = GetOptions(); + return JsonSerializer.Deserialize(json, options)!; + } + + public T Deserialize(JsonElement element, CancellationToken cancellationToken = default) + { + var options = GetOptions(); + return element.Deserialize(options)!; } /// 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()); diff --git a/test/unit/Elsa.Workflows.Core.UnitTests/SerializerEncodingTests.cs b/test/unit/Elsa.Workflows.Core.UnitTests/SerializerEncodingTests.cs index 803cb7f9b..2e9901668 100644 --- a/test/unit/Elsa.Workflows.Core.UnitTests/SerializerEncodingTests.cs +++ b/test/unit/Elsa.Workflows.Core.UnitTests/SerializerEncodingTests.cs @@ -32,17 +32,17 @@ public class SerializerUnicodeEncodingTests(ITestOutputHelper testOutputHelper) } [Fact] - public async Task TestSafeSerializer() + public void TestSafeSerializer() { var serializer = _serviceProvider.GetRequiredService(); - await TestSerializerAsync(input => serializer.SerializeAsync(input).AsTask()); + TestSerializer(input => serializer.Serialize(input)); } [Fact] - public async Task TestWorkflowStateSerializer() + public void TestWorkflowStateSerializer() { var serializer = _serviceProvider.GetRequiredService(); - await TestSerializerAsync(input => serializer.SerializeAsync(input)); + TestSerializer(input => serializer.Serialize(input)); } private void TestSerializer(Func serialize) @@ -56,18 +56,6 @@ public class SerializerUnicodeEncodingTests(ITestOutputHelper testOutputHelper) var serializedStringValue = GetSerializedTextValue(serializedJson); Assert.Equal(unicodeString, serializedStringValue); } - - private async Task TestSerializerAsync(Func> serialize) - { - var unicodeString = UnicodeRangeGenerator.GenerateUnicodeString(); - var anonymousObject = new - { - Text = unicodeString - }; - var serializedJson = await serialize(anonymousObject); - var serializedStringValue = GetSerializedTextValue(serializedJson); - Assert.Equal(unicodeString, serializedStringValue); - } private string GetSerializedTextValue(string serializedJson) {