diff --git a/src/core/Elsa.Core/Activities/Primitives/Inline.cs b/src/core/Elsa.Core/Activities/Primitives/Inline.cs index dcb557fe9..021fe1a8c 100644 --- a/src/core/Elsa.Core/Activities/Primitives/Inline.cs +++ b/src/core/Elsa.Core/Activities/Primitives/Inline.cs @@ -54,7 +54,7 @@ public class Inline : Activity public Inline(Func> activity, RegisterLocationReference? output = default) { _activity = activity; - if (output != null) Result = new Output(output); + if (output != null) Result = new Output(output); } public Inline(Func> activity, RegisterLocationReference? output = default) : this(_ => activity(), output) diff --git a/src/core/Elsa.Core/Extensions/ActivityExtensions.cs b/src/core/Elsa.Core/Extensions/ActivityExtensions.cs index 4374b80be..9b31f6d49 100644 --- a/src/core/Elsa.Core/Extensions/ActivityExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ActivityExtensions.cs @@ -37,7 +37,7 @@ public static class ActivityExtensions /// /// Creates an input from the activity's result. /// - public static Input CreateInput(this Activity activity) => activity.Result.CreateInput(); + public static Input CreateInput(this Activity activity) => activity.Result.CreateInput(); public static IEnumerable GetVariables(this IActivity activity) { diff --git a/src/core/Elsa.Core/Extensions/DictionaryExtensions.cs b/src/core/Elsa.Core/Extensions/DictionaryExtensions.cs index ec3869311..18ef1a434 100644 --- a/src/core/Elsa.Core/Extensions/DictionaryExtensions.cs +++ b/src/core/Elsa.Core/Extensions/DictionaryExtensions.cs @@ -23,7 +23,7 @@ public static class DictionaryExtensions return ConvertValue(dictionary[key]); } - public static T GetOrAdd(this IDictionary dictionary, string key, Func valueFactory) where T : notnull + public static T? GetOrAdd(this IDictionary dictionary, string key, Func valueFactory) where T : notnull { if (dictionary.TryGetValue(key, out var value)) return value; diff --git a/src/core/Elsa.Core/Models/Activity.cs b/src/core/Elsa.Core/Models/Activity.cs index 3428866d5..dd7b36b23 100644 --- a/src/core/Elsa.Core/Models/Activity.cs +++ b/src/core/Elsa.Core/Models/Activity.cs @@ -33,10 +33,26 @@ public abstract class Activity : IActivity public abstract class ActivityWithResult : Activity { + protected ActivityWithResult() + { + } + + protected ActivityWithResult(string activityType) : base(activityType) + { + } + public Output? Result { get; set; } } public abstract class Activity : ActivityWithResult { - public new Output? Result { get; set; } + protected Activity() : base() + { + } + + protected Activity(string activityType) : base(activityType) + { + } + + public new Output? Result { get; set; } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs b/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs index 91a566414..7619fa132 100644 --- a/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs +++ b/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs @@ -6,7 +6,13 @@ public class ExpressionExecutionContext { private readonly IServiceProvider _serviceProvider; - public ExpressionExecutionContext(IServiceProvider serviceProvider, Register register, Workflow workflow, IDictionary transientProperties, ExpressionExecutionContext? parentContext, CancellationToken cancellationToken) + public ExpressionExecutionContext( + IServiceProvider serviceProvider, + Register register, + Workflow workflow, + IDictionary transientProperties, + ExpressionExecutionContext? parentContext, + CancellationToken cancellationToken) { _serviceProvider = serviceProvider; Register = register; @@ -30,7 +36,6 @@ public class ExpressionExecutionContext public object? Get(Output? output) => output != null ? GetLocation(output.LocationReference).Value : default; public T? GetVariable(string name) => (T?)GetVariable(name); public T? GetVariable() => (T?)GetVariable(typeof(T).Name); - public object? GetVariable(string name) => new Variable(name).Get(this); public Variable SetVariable(T? value) => SetVariable(typeof(T).Name, value); diff --git a/src/core/Elsa.Core/Models/Trigger.cs b/src/core/Elsa.Core/Models/Trigger.cs index c5cd3418a..13260a3a6 100644 --- a/src/core/Elsa.Core/Models/Trigger.cs +++ b/src/core/Elsa.Core/Models/Trigger.cs @@ -28,6 +28,38 @@ public abstract class Trigger : Activity, ITrigger /// protected virtual IEnumerable GetTriggerData(TriggerIndexingContext context) => new[]{ GetTriggerDatum(context) }; + /// + /// Override this method to return a trigger datum. + /// + protected virtual object GetTriggerDatum(TriggerIndexingContext context) => new(); +} + +public abstract class Trigger : Activity, ITrigger +{ + protected Trigger() + { + } + + protected Trigger(string activityType) : base(activityType) + { + } + + ValueTask> ITrigger.GetTriggerDataAsync(TriggerIndexingContext context) => GetTriggerDataAsync(context); + + /// + /// Override this method to return trigger data. + /// + protected virtual ValueTask> GetTriggerDataAsync(TriggerIndexingContext context) + { + var hashes = GetTriggerData(context); + return ValueTask.FromResult(hashes); + } + + /// + /// Override this method to return trigger data. + /// + protected virtual IEnumerable GetTriggerData(TriggerIndexingContext context) => new[]{ GetTriggerDatum(context) }; + /// /// Override this method to return a trigger datum. /// diff --git a/src/modules/Elsa.Modules.Http/Activities/HttpEndpoint.cs b/src/modules/Elsa.Modules.Http/Activities/HttpEndpoint.cs index 5b26d4470..3886d50f6 100644 --- a/src/modules/Elsa.Modules.Http/Activities/HttpEndpoint.cs +++ b/src/modules/Elsa.Modules.Http/Activities/HttpEndpoint.cs @@ -9,11 +9,11 @@ using Elsa.Modules.Http.Models; namespace Elsa.Modules.Http; -[Activity("Elsa.Http.HttpEndpoint", "Waits for an inbound HTTP request that matches the specified path and methods", "HTTP")] -public class HttpEndpoint : Trigger +[Activity("Http", "Waits for an inbound HTTP request that matches the specified path and methods", category: "HTTP")] +public class HttpEndpoint : Trigger { public const string InputKey = "HttpRequest"; - + [Input] public Input Path { get; set; } = default!; [Input( @@ -22,21 +22,32 @@ public class HttpEndpoint : Trigger )] public Input> SupportedMethods { get; set; } = new(new[] { HttpMethod.Get.Method }); - [Output] public Output? Request { get; set; } + [Input( + Description = "Allow authenticated requests only", + Category = "Security" + )] + public Input Authorize { get; set; } = new(false); + + [Input( + Description = "Provide a policy to evaluate. If the policy fails, the request is forbidden.", + Category = "Security" + )] + public Input Policy { get; set; } = new(default(string?)); protected override IEnumerable GetTriggerData(TriggerIndexingContext context) => GetBookmarkData(context.ExpressionExecutionContext); + protected override void Execute(ActivityExecutionContext context) { // If we did not receive external input, it means we are just now encountering this activity. - if (!context.TryGetInput(InputKey, out var request )) + if (!context.TryGetInput(InputKey, out var request)) { // Create bookmarks for when we receive the expected HTTP request. context.CreateBookmarks(GetBookmarkData(context.ExpressionExecutionContext)); return; } - + // Provide the received HTTP request as output. - context.Set(Request, request); + context.Set(Result, request); } private IEnumerable GetBookmarkData(ExpressionExecutionContext context) diff --git a/src/modules/Elsa.Modules.Http/Contracts/IHttpEndpointAuthorizationHandler.cs b/src/modules/Elsa.Modules.Http/Contracts/IHttpEndpointAuthorizationHandler.cs new file mode 100644 index 000000000..881c6f504 --- /dev/null +++ b/src/modules/Elsa.Modules.Http/Contracts/IHttpEndpointAuthorizationHandler.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Elsa.Modules.Http.Models; + +namespace Elsa.Modules.Http.Contracts +{ + public interface IHttpEndpointAuthorizationHandler + { + ValueTask AuthorizeAsync(AuthorizeHttpEndpointContext context); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Modules.Http/Contracts/IHttpEndpointWorkflowFaultHandler.cs b/src/modules/Elsa.Modules.Http/Contracts/IHttpEndpointWorkflowFaultHandler.cs new file mode 100644 index 000000000..b8f5c12be --- /dev/null +++ b/src/modules/Elsa.Modules.Http/Contracts/IHttpEndpointWorkflowFaultHandler.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using Elsa.Modules.Http.Models; + +namespace Elsa.Modules.Http.Contracts; + +/// +/// Implement this to control what to return to the client in case an unhandled exception occurs while executing the workflow. +/// +public interface IHttpEndpointWorkflowFaultHandler +{ + ValueTask HandleAsync(HttpEndpointFaultedWorkflowContext context); +} \ No newline at end of file diff --git a/src/modules/Elsa.Modules.Http/Elsa.Modules.Http.csproj b/src/modules/Elsa.Modules.Http/Elsa.Modules.Http.csproj index 573244768..4c7d89919 100644 --- a/src/modules/Elsa.Modules.Http/Elsa.Modules.Http.csproj +++ b/src/modules/Elsa.Modules.Http/Elsa.Modules.Http.csproj @@ -7,13 +7,15 @@ - - + + - - + + + + diff --git a/src/modules/Elsa.Modules.Http/Handlers/AllowAnonymousHttpEndpointAuthorizationHandler.cs b/src/modules/Elsa.Modules.Http/Handlers/AllowAnonymousHttpEndpointAuthorizationHandler.cs new file mode 100644 index 000000000..7f84b3d31 --- /dev/null +++ b/src/modules/Elsa.Modules.Http/Handlers/AllowAnonymousHttpEndpointAuthorizationHandler.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Elsa.Modules.Http.Contracts; +using Elsa.Modules.Http.Models; + +namespace Elsa.Modules.Http.Handlers +{ + public class AllowAnonymousHttpEndpointAuthorizationHandler : IHttpEndpointAuthorizationHandler + { + public ValueTask AuthorizeAsync(AuthorizeHttpEndpointContext context) => new(true); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Modules.Http/Handlers/AuthenticationBasedHttpEndpointAuthorizationHandler.cs b/src/modules/Elsa.Modules.Http/Handlers/AuthenticationBasedHttpEndpointAuthorizationHandler.cs new file mode 100644 index 000000000..b1ef918d1 --- /dev/null +++ b/src/modules/Elsa.Modules.Http/Handlers/AuthenticationBasedHttpEndpointAuthorizationHandler.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using Elsa.Extensions; +using Elsa.Modules.Http.Contracts; +using Elsa.Modules.Http.Models; +using Microsoft.AspNetCore.Authorization; + +namespace Elsa.Modules.Http.Handlers +{ + public class AuthenticationBasedHttpEndpointAuthorizationHandler : IHttpEndpointAuthorizationHandler + { + private readonly IAuthorizationService _authorizationService; + public AuthenticationBasedHttpEndpointAuthorizationHandler(IAuthorizationService authorizationService) => _authorizationService = authorizationService; + + public async ValueTask AuthorizeAsync(AuthorizeHttpEndpointContext context) + { + var httpContext = context.HttpContext; + var user = httpContext.User; + var identity = user.Identity; + + if (identity == null) + return false; + + if (identity.IsAuthenticated == false) + return false; + + var httpEndpoint = context.Activity; + var expressionExecutionContext = context.ExpressionExecutionContext; + var policyName = httpEndpoint.Policy.Get(expressionExecutionContext); + + if (string.IsNullOrWhiteSpace(policyName)) + return identity.IsAuthenticated; + + var resource = new HttpWorkflowResource(expressionExecutionContext, httpEndpoint, context.WorkflowInstanceId); + var authorizationResult = await _authorizationService.AuthorizeAsync(user, resource, policyName); + return authorizationResult.Succeeded; + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Modules.Http/Handlers/DefaultHttpEndpointWorkflowFaultHandler.cs b/src/modules/Elsa.Modules.Http/Handlers/DefaultHttpEndpointWorkflowFaultHandler.cs new file mode 100644 index 000000000..3f29f189c --- /dev/null +++ b/src/modules/Elsa.Modules.Http/Handlers/DefaultHttpEndpointWorkflowFaultHandler.cs @@ -0,0 +1,34 @@ +using System.Net.Mime; +using System.Text.Json; +using System.Threading.Tasks; +using Elsa.Modules.Http.Contracts; +using Elsa.Modules.Http.Models; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Modules.Http.Handlers; + +public class DefaultHttpEndpointWorkflowFaultHandler : IHttpEndpointWorkflowFaultHandler +{ + public virtual async ValueTask HandleAsync(HttpEndpointFaultedWorkflowContext context) + { + var httpContext = context.HttpContext; + var workflowInstance = context.WorkflowInstance; + + httpContext.Response.ContentType = MediaTypeNames.Application.Json; + httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; + + var faultedResponse = JsonSerializer.Serialize(new + { + errorMessage = $"Workflow faulted at {workflowInstance.FaultedAt!} with error: {workflowInstance.Fault!.Message}", + exception = workflowInstance.Fault?.Exception, + workflow = new + { + name = workflowInstance.Name, + version = workflowInstance.Version, + instanceId = workflowInstance.Id + } + }); + + await httpContext.Response.WriteAsync(faultedResponse, context.CancellationToken); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Modules.Http/Models/AuthorizeHttpEndpointContext.cs b/src/modules/Elsa.Modules.Http/Models/AuthorizeHttpEndpointContext.cs new file mode 100644 index 000000000..f87369e8a --- /dev/null +++ b/src/modules/Elsa.Modules.Http/Models/AuthorizeHttpEndpointContext.cs @@ -0,0 +1,8 @@ +using System.Threading; +using Elsa.Models; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Modules.Http.Models +{ + public record AuthorizeHttpEndpointContext(ExpressionExecutionContext ExpressionExecutionContext, HttpContext HttpContext, HttpEndpoint Activity, string WorkflowInstanceId); +} \ No newline at end of file diff --git a/src/modules/Elsa.Modules.Http/Models/HttpEndpointFaultedWorkflowContext.cs b/src/modules/Elsa.Modules.Http/Models/HttpEndpointFaultedWorkflowContext.cs new file mode 100644 index 000000000..ff406f635 --- /dev/null +++ b/src/modules/Elsa.Modules.Http/Models/HttpEndpointFaultedWorkflowContext.cs @@ -0,0 +1,8 @@ +using System; +using System.Threading; +using Elsa.Persistence.Entities; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Modules.Http.Models; + +public record HttpEndpointFaultedWorkflowContext(HttpContext HttpContext, WorkflowInstance WorkflowInstance, Exception? Exception, CancellationToken CancellationToken); \ No newline at end of file diff --git a/src/modules/Elsa.Modules.Http/Models/HttpWorkflowResource.cs b/src/modules/Elsa.Modules.Http/Models/HttpWorkflowResource.cs new file mode 100644 index 000000000..2c2fe8aed --- /dev/null +++ b/src/modules/Elsa.Modules.Http/Models/HttpWorkflowResource.cs @@ -0,0 +1,6 @@ +using Elsa.Models; + +namespace Elsa.Modules.Http.Models +{ + public record HttpWorkflowResource(ExpressionExecutionContext ExpressionExecutionContext, HttpEndpoint Activity, string WorkflowInstance); +} \ No newline at end of file diff --git a/src/modules/Elsa.Modules.Http/Options/HttpActivityOptions.cs b/src/modules/Elsa.Modules.Http/Options/HttpActivityOptions.cs new file mode 100644 index 000000000..444520bc2 --- /dev/null +++ b/src/modules/Elsa.Modules.Http/Options/HttpActivityOptions.cs @@ -0,0 +1,19 @@ +using System; +using Elsa.Modules.Http.Contracts; +using Elsa.Modules.Http.Handlers; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Modules.Http.Options +{ + public class HttpActivityOptions + { + /// + /// The root path at which HTTP activities can be invoked. + /// + public PathString? BasePath { get; set; } + + public Func HttpEndpointAuthorizationHandlerFactory { get; set; } = ActivatorUtilities.GetServiceOrCreateInstance; + public Func HttpEndpointWorkflowFaultHandlerFactory { get; set; } = ActivatorUtilities.GetServiceOrCreateInstance; + } +} \ No newline at end of file diff --git a/src/persistence/Elsa.Persistence.Abstractions/Entities/WorkflowInstance.cs b/src/persistence/Elsa.Persistence.Abstractions/Entities/WorkflowInstance.cs index 6e0ed9676..bc46b3696 100644 --- a/src/persistence/Elsa.Persistence.Abstractions/Entities/WorkflowInstance.cs +++ b/src/persistence/Elsa.Persistence.Abstractions/Entities/WorkflowInstance.cs @@ -1,3 +1,4 @@ +using Elsa.Persistence.Models; using Elsa.State; namespace Elsa.Persistence.Entities; @@ -11,6 +12,7 @@ public class WorkflowInstance : Entity public WorkflowStatus WorkflowStatus { get; set; } public string CorrelationId { get; init; } = default!; public string? Name { get; set; } + public WorkflowFault? Fault { get; set; } public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset? LastExecutedAt { get; set; } public DateTimeOffset? FinishedAt { get; set; } diff --git a/src/persistence/Elsa.Persistence.Abstractions/Models/SimpleException.cs b/src/persistence/Elsa.Persistence.Abstractions/Models/SimpleException.cs new file mode 100644 index 000000000..469e28a63 --- /dev/null +++ b/src/persistence/Elsa.Persistence.Abstractions/Models/SimpleException.cs @@ -0,0 +1,36 @@ +using System.Collections; + +namespace Elsa.Persistence.Models +{ + public class SimpleException + { + public SimpleException(Type type, string message, string? stackTrace, IDictionary data, SimpleException? innerException = default) + { + Type = type; + Message = message; + StackTrace = stackTrace; + InnerException = innerException; + Data = data; + } + + public Type Type { get; set; } + public string Message { get; set; } + public string? StackTrace { get; set; } + public SimpleException? InnerException { get; set; } + public IDictionary Data { get; set; } + + public static SimpleException? FromException(Exception? ex) + { + if (ex == null) + return null; + + var exceptionType = ex.GetType(); + var simpleException = new SimpleException(exceptionType, ex.Message, ex.StackTrace, ex.Data); + + if (ex.InnerException != null) + simpleException.InnerException = FromException(ex.InnerException); + + return simpleException; + } + } +} \ No newline at end of file diff --git a/src/persistence/Elsa.Persistence.Abstractions/Models/SimpleExceptionProperty.cs b/src/persistence/Elsa.Persistence.Abstractions/Models/SimpleExceptionProperty.cs new file mode 100644 index 000000000..b5ae9dbdc --- /dev/null +++ b/src/persistence/Elsa.Persistence.Abstractions/Models/SimpleExceptionProperty.cs @@ -0,0 +1,14 @@ +namespace Elsa.Persistence.Models +{ + public class SimpleExceptionProperty + { + public SimpleExceptionProperty(string name, object value) + { + Name = name; + Value = value; + } + + public string Name { get; set; } + public object Value { get; set; } + } +} \ No newline at end of file diff --git a/src/persistence/Elsa.Persistence.Abstractions/Models/WorkflowFault.cs b/src/persistence/Elsa.Persistence.Abstractions/Models/WorkflowFault.cs new file mode 100644 index 000000000..ea7b89c6d --- /dev/null +++ b/src/persistence/Elsa.Persistence.Abstractions/Models/WorkflowFault.cs @@ -0,0 +1,4 @@ +namespace Elsa.Persistence.Models +{ + public record WorkflowFault(SimpleException? Exception, string Message, string? FaultedActivityId, object? ActivityInput, bool Resuming); +} \ No newline at end of file diff --git a/src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220308133708_Initial.Designer.cs b/src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220309231832_Initial.Designer.cs similarity index 99% rename from src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220308133708_Initial.Designer.cs rename to src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220309231832_Initial.Designer.cs index 37132c170..57db119b1 100644 --- a/src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220308133708_Initial.Designer.cs +++ b/src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220309231832_Initial.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.Persistence.EntityFrameworkCore.Sqlite.Migrations { [DbContext(typeof(ElsaDbContext))] - [Migration("20220308133708_Initial")] + [Migration("20220309231832_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220308133708_Initial.cs b/src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220309231832_Initial.cs similarity index 100% rename from src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220308133708_Initial.cs rename to src/persistence/Elsa.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220309231832_Initial.cs diff --git a/src/persistence/Elsa.Persistence.EntityFrameworkCore/Configuration/WorkflowInstanceConfiguration.cs b/src/persistence/Elsa.Persistence.EntityFrameworkCore/Configuration/WorkflowInstanceConfiguration.cs index 9383db159..2927a3a20 100644 --- a/src/persistence/Elsa.Persistence.EntityFrameworkCore/Configuration/WorkflowInstanceConfiguration.cs +++ b/src/persistence/Elsa.Persistence.EntityFrameworkCore/Configuration/WorkflowInstanceConfiguration.cs @@ -9,6 +9,7 @@ namespace Elsa.Persistence.EntityFrameworkCore.Configuration public void Configure(EntityTypeBuilder builder) { builder.Ignore(x => x.WorkflowState); + builder.Ignore(x => x.Fault); builder.Property("Data"); builder.HasIndex(x => new { x.WorkflowStatus, x.DefinitionId, x.Version }).HasDatabaseName($"IX_{nameof(WorkflowInstance)}_{nameof(WorkflowInstance.WorkflowStatus)}_{nameof(WorkflowInstance.DefinitionId)}_{nameof(WorkflowInstance.Version)}"); builder.HasIndex(x => new { x.WorkflowStatus, x.DefinitionId }).HasDatabaseName($"IX_{nameof(WorkflowInstance)}_{nameof(WorkflowInstance.WorkflowStatus)}_{nameof(WorkflowInstance.DefinitionId)}"); diff --git a/src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Serialization/WorkflowInstanceSerializer.cs b/src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Serialization/WorkflowInstanceSerializer.cs index 2564facfb..a71b5fc6b 100644 --- a/src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Serialization/WorkflowInstanceSerializer.cs +++ b/src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Serialization/WorkflowInstanceSerializer.cs @@ -2,6 +2,7 @@ using System.Text.Json; using Elsa.Management.Serialization; using Elsa.Persistence.Entities; using Elsa.Persistence.EntityFrameworkCore.Contracts; +using Elsa.Persistence.Models; using Elsa.State; namespace Elsa.Persistence.EntityFrameworkCore.Handlers.Serialization; @@ -17,7 +18,7 @@ public class WorkflowInstanceSerializer : IEntitySerializer public void Serialize(ElsaDbContext dbContext, WorkflowInstance entity) { - var data = new WorkflowInstanceState(entity.WorkflowState); + var data = new WorkflowInstanceState(entity.WorkflowState, entity.Fault); var options = _workflowSerializerOptionsProvider.CreatePersistenceOptions(); var json = JsonSerializer.Serialize(data, options); @@ -26,7 +27,7 @@ public class WorkflowInstanceSerializer : IEntitySerializer public void Deserialize(ElsaDbContext dbContext, WorkflowInstance entity) { - var data = new WorkflowInstanceState(entity.WorkflowState); + var data = new WorkflowInstanceState(entity.WorkflowState, entity.Fault); var json = (string?)dbContext.Entry(entity).Property("Data").CurrentValue; if (!string.IsNullOrWhiteSpace(json)) @@ -36,6 +37,7 @@ public class WorkflowInstanceSerializer : IEntitySerializer } entity.WorkflowState = data.WorkflowState; + entity.Fault = data.Fault; } // Can't use records when using System.Text.Json serialization and reference handling. Hence, using a class with default constructor. @@ -45,11 +47,13 @@ public class WorkflowInstanceSerializer : IEntitySerializer { } - public WorkflowInstanceState(WorkflowState workflowState) + public WorkflowInstanceState(WorkflowState workflowState, WorkflowFault? fault) { WorkflowState = workflowState; + Fault = fault; } public WorkflowState WorkflowState { get; init; } = default!; + public WorkflowFault? Fault { get; set; } } } \ No newline at end of file