From 8b7b087e9ee02db397afc6563ca1a39663e94e53 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 6 Aug 2024 08:32:25 +0200 Subject: [PATCH 1/9] Refactor workflow context extension structure Removed unused WorkflowContextWorkflowDefinitionExtensions and renamed several extension and middleware classes for clarity and consistency. Simplified context provider type handling using JsonArray in custom properties. --- .../Extensions/ActivityExtensions.cs | 33 +++++----- ...ntPropertiesExecutionContextExtensions.cs} | 65 ++++++++++++++----- .../Extensions/WorkflowBuilderExtensions.cs | 7 +- ....cs => WorkflowContextModuleExtensions.cs} | 2 +- ...flowContextServiceCollectionExtensions.cs} | 2 +- ...flowContextWorkflowDefinitionExtensions.cs | 19 ------ ...s => WorkflowContextWorkflowExtensions.cs} | 26 +++----- ...kflowContextActivityExecutionMiddleware.cs | 42 ++++++------ ...kflowContextWorkflowExecutionMiddleware.cs | 30 ++++----- 9 files changed, 115 insertions(+), 111 deletions(-) rename src/modules/Elsa.WorkflowContexts/Extensions/{WorkflowExecutionContextExtensions.cs => TransientPropertiesExecutionContextExtensions.cs} (79%) rename src/modules/Elsa.WorkflowContexts/Extensions/{ModuleExtensions.cs => WorkflowContextModuleExtensions.cs} (93%) rename src/modules/Elsa.WorkflowContexts/Extensions/{DependencyInjectionExtensions.cs => WorkflowContextServiceCollectionExtensions.cs} (92%) delete mode 100644 src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowDefinitionExtensions.cs rename src/modules/Elsa.WorkflowContexts/Extensions/{WorkflowExtensions.cs => WorkflowContextWorkflowExtensions.cs} (55%) diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/ActivityExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/ActivityExtensions.cs index d5c22cc0ea..f6e3963179 100644 --- a/src/modules/Elsa.WorkflowContexts/Extensions/ActivityExtensions.cs +++ b/src/modules/Elsa.WorkflowContexts/Extensions/ActivityExtensions.cs @@ -1,3 +1,5 @@ +using System.Text.Json; +using System.Text.Json.Nodes; using Elsa.WorkflowContexts.Models; using Elsa.Workflows.Contracts; @@ -18,20 +20,21 @@ public static class ActivityExtensions /// The workflow context settings. public static IDictionary GetWorkflowContextSettings(this IActivity activity) { - var contextSetttings = activity.CustomProperties.GetOrAdd(ActivityWorkflowContextSettingsKey, () => new Dictionary())!; - - var result = new Dictionary(); - - foreach(var (key,value) in contextSetttings) - { - var targetType = Type.GetType(key); - - if(targetType != null) - { - result.Add(targetType, value); - } - } - + var contextSettings = activity.CustomProperties.GetOrAdd(ActivityWorkflowContextSettingsKey, () => new JsonObject()); + + var result = new Dictionary(); + + foreach(var (key, jsonNode) in contextSettings) + { + var targetType = Type.GetType(key); + + if(targetType != null) + { + var value = jsonNode.Deserialize()!; + result.Add(targetType, value); + } + } + return result; } @@ -84,7 +87,7 @@ public static ActivityWorkflowContextSettings GetActivityWorkflowContextSettings /// The workflow context settings. public static ActivityWorkflowContextSettings GetActivityWorkflowContextSettings(this IDictionary dictionary, Type providerType) { - var settings = dictionary.ContainsKey(providerType) ? dictionary[providerType] : default; + var settings = dictionary.TryGetValue(providerType, out ActivityWorkflowContextSettings? value) ? value : default; if(settings == null) { diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExecutionContextExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/TransientPropertiesExecutionContextExtensions.cs similarity index 79% rename from src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExecutionContextExtensions.cs rename to src/modules/Elsa.WorkflowContexts/Extensions/TransientPropertiesExecutionContextExtensions.cs index d6184a4469..adf0ce713e 100644 --- a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExecutionContextExtensions.cs +++ b/src/modules/Elsa.WorkflowContexts/Extensions/TransientPropertiesExecutionContextExtensions.cs @@ -6,11 +6,9 @@ // ReSharper disable once CheckNamespace namespace Elsa.Extensions; -/// /// Adds extension methods to . -/// [PublicAPI] -public static class WorkflowExecutionContextExtensions +public static class TransientPropertiesExecutionContextExtensions { private static readonly object WorkflowContextsKey = new(); @@ -20,7 +18,10 @@ public static class WorkflowExecutionContextExtensions /// The workflow execution context. /// The type of the workflow context provider. /// The value to set. - public static void SetWorkflowContextParameter(this WorkflowExecutionContext context, Type providerType, object? value) => SetWorkflowContextParameter(context, providerType, null, value); + public static void SetWorkflowContextParameter(this WorkflowExecutionContext context, Type providerType, object? value) + { + SetWorkflowContextParameter(context, providerType, null, value); + } /// /// Sets a workflow context provider parameter. @@ -28,7 +29,10 @@ public static class WorkflowExecutionContextExtensions /// The workflow execution context. /// The value to set. /// The type of the workflow context provider. - public static void SetWorkflowContextParameter(this WorkflowExecutionContext context, object? value) where T : IWorkflowContextProvider => SetWorkflowContextParameter(context, typeof(T), null, value); + public static void SetWorkflowContextParameter(this WorkflowExecutionContext context, object? value) where T : IWorkflowContextProvider + { + SetWorkflowContextParameter(context, typeof(T), null, value); + } /// /// Sets a workflow context provider parameter. @@ -50,7 +54,10 @@ public static void SetWorkflowContextParameter(this WorkflowExecutionContext con /// The name of the parameter. /// The value to set. /// The type of the workflow context provider. - public static void SetWorkflowContextParameter(this WorkflowExecutionContext context, string? parameterName, object? value) => SetWorkflowContextParameter(context, typeof(T), parameterName, value); + public static void SetWorkflowContextParameter(this WorkflowExecutionContext context, string? parameterName, object? value) + { + SetWorkflowContextParameter(context, typeof(T), parameterName, value); + } /// /// Gets a workflow context provider parameter. @@ -75,7 +82,9 @@ public static void SetWorkflowContextParameter(this WorkflowExecutionContext con /// The type of the parameter. /// The parameter value. public static TParameter? GetWorkflowContextParameter(this WorkflowExecutionContext context, string? parameterName = default) where TProvider : IWorkflowContextProvider - => GetWorkflowContextParameter(context, typeof(TProvider), parameterName); + { + return GetWorkflowContextParameter(context, typeof(TProvider), parameterName); + } /// /// Sets the specified workflow context value. @@ -83,7 +92,10 @@ public static void SetWorkflowContextParameter(this WorkflowExecutionContext con /// The workflow execution context. /// The type of the workflow context provider. /// The value to set. - public static void SetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext, Type providerType, object value) => workflowExecutionContext.TransientProperties.SetWorkflowContext(providerType, value); + public static void SetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext, Type providerType, object value) + { + workflowExecutionContext.TransientProperties.SetWorkflowContext(providerType, value); + } /// /// Sets the specified workflow context value. @@ -91,7 +103,10 @@ public static void SetWorkflowContextParameter(this WorkflowExecutionContext con /// The expression execution context. /// The type of the workflow context provider. /// The value to set. - public static void SetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext, Type providerType, object value) => expressionExecutionContext.GetWorkflowExecutionContext().TransientProperties.SetWorkflowContext(providerType, value); + public static void SetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext, Type providerType, object value) + { + expressionExecutionContext.GetWorkflowExecutionContext().TransientProperties.SetWorkflowContext(providerType, value); + } /// /// Gets the workflow context value. @@ -100,7 +115,10 @@ public static void SetWorkflowContextParameter(this WorkflowExecutionContext con /// The type of the workflow context value. /// The type of the workflow context provider. /// The workflow context value. - public static T? GetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext) => (T?)workflowExecutionContext.TransientProperties.GetWorkflowContextByProviderType(typeof(TProvider)); + public static T? GetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext) + { + return (T?)workflowExecutionContext.TransientProperties.GetWorkflowContextByProviderType(typeof(TProvider)); + } /// /// Gets the workflow context value. @@ -109,7 +127,10 @@ public static void SetWorkflowContextParameter(this WorkflowExecutionContext con /// The type of the workflow context value. /// The type of the workflow context provider. /// The workflow context value. - public static T GetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext) => (T)expressionExecutionContext.GetWorkflowExecutionContext().TransientProperties.GetWorkflowContextByProviderType(typeof(TProvider))!; + public static T GetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext) + { + return (T)expressionExecutionContext.GetWorkflowExecutionContext().TransientProperties.GetWorkflowContextByProviderType(typeof(TProvider))!; + } /// /// Gets the workflow context value. @@ -117,7 +138,10 @@ public static void SetWorkflowContextParameter(this WorkflowExecutionContext con /// The workflow execution context. /// The type of the workflow context provider. /// The workflow context value. - public static object GetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext, Type providerType) => workflowExecutionContext.TransientProperties.GetWorkflowContext(providerType); + public static object GetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext, Type providerType) + { + return workflowExecutionContext.TransientProperties.GetWorkflowContext(providerType); + } /// /// Gets the workflow context value. @@ -125,7 +149,10 @@ public static void SetWorkflowContextParameter(this WorkflowExecutionContext con /// The expression execution context. /// The type of the workflow context provider. /// The workflow context value. - public static object? GetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext, Type providerType) => expressionExecutionContext.GetWorkflowExecutionContext().TransientProperties.GetWorkflowContext(providerType); + public static object GetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext, Type providerType) + { + return expressionExecutionContext.GetWorkflowExecutionContext().TransientProperties.GetWorkflowContext(providerType); + } private static void SetWorkflowContext(this IDictionary transientProperties, Type providerType, object value) { @@ -139,8 +166,10 @@ private static object GetWorkflowContext(this IDictionary transi return contextDictionary[providerType]; } - private static object? GetWorkflowContextByProviderType(this IDictionary transientProperties, Type providerType) => - transientProperties.FindWorkflowContext(x => x == providerType); + private static object? GetWorkflowContextByProviderType(this IDictionary transientProperties, Type providerType) + { + return transientProperties.FindWorkflowContext(x => x == providerType); + } private static object? FindWorkflowContext(this IDictionary transientProperties, Func filter) { @@ -154,6 +183,8 @@ where filter(entry.Key) return query.FirstOrDefault(); } - private static IDictionary GetWorkflowContextDictionary(this IDictionary transientProperties) => - transientProperties.GetOrAdd(WorkflowContextsKey, () => new Dictionary()); + private static IDictionary GetWorkflowContextDictionary(this IDictionary transientProperties) + { + return transientProperties.GetOrAdd(WorkflowContextsKey, () => new Dictionary()); + } } \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowBuilderExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowBuilderExtensions.cs index b10faf2265..e001dc87e8 100644 --- a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowBuilderExtensions.cs +++ b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowBuilderExtensions.cs @@ -1,3 +1,4 @@ +using System.Text.Json.Nodes; using Elsa.WorkflowContexts; using Elsa.WorkflowContexts.Contracts; using Elsa.Workflows.Contracts; @@ -6,9 +7,7 @@ // ReSharper disable once CheckNamespace namespace Elsa.Extensions; -/// /// Adds extension methods to . -/// [PublicAPI] public static class WorkflowBuilderExtensions { @@ -29,8 +28,8 @@ public static IWorkflowBuilder AddWorkflowContextProvider(this IWorkflowBuild /// The type of the provider to add. public static IWorkflowBuilder AddWorkflowContextProvider(this IWorkflowBuilder workflow, Type providerType) { - var providerTypes = workflow.CustomProperties.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new List())!; - providerTypes.Add(providerType); + var providerTypes = workflow.CustomProperties.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new JsonArray()); + providerTypes.Add(providerType.GetSimpleAssemblyQualifiedName()); return workflow; } } \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/ModuleExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextModuleExtensions.cs similarity index 93% rename from src/modules/Elsa.WorkflowContexts/Extensions/ModuleExtensions.cs rename to src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextModuleExtensions.cs index b861c76a08..2ad3cf781d 100644 --- a/src/modules/Elsa.WorkflowContexts/Extensions/ModuleExtensions.cs +++ b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextModuleExtensions.cs @@ -9,7 +9,7 @@ namespace Elsa.Extensions; /// Extension methods for to add workflow context providers. /// [PublicAPI] -public static class ModuleExtensions +public static class WorkflowContextModuleExtensions { /// /// Adds support for workflow context providers. diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/DependencyInjectionExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextServiceCollectionExtensions.cs similarity index 92% rename from src/modules/Elsa.WorkflowContexts/Extensions/DependencyInjectionExtensions.cs rename to src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextServiceCollectionExtensions.cs index 968ba7c5c1..42a829690d 100644 --- a/src/modules/Elsa.WorkflowContexts/Extensions/DependencyInjectionExtensions.cs +++ b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextServiceCollectionExtensions.cs @@ -7,7 +7,7 @@ namespace Elsa.Extensions; /// /// Extension methods for to add workflow context providers. /// -public static class DependencyInjectionExtensions +public static class WorkflowContextServiceCollectionExtensions { /// /// Adds a workflow context provider. diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowDefinitionExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowDefinitionExtensions.cs deleted file mode 100644 index fc8279df02..0000000000 --- a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowDefinitionExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Elsa.WorkflowContexts; -using Elsa.Workflows.Management.Entities; - -// ReSharper disable once CheckNamespace -namespace Elsa.Extensions; - -/// Adds extension methods to . -public static class WorkflowContextWorkflowDefinitionExtensions -{ - /// - /// Gets the workflow context provider types that are installed on the workflow definition. - /// - /// The workflow definition to get the provider types from. - /// The workflow context provider types. - public static IEnumerable GetWorkflowContextProviderTypes(this WorkflowDefinition workflowDefinition) - { - return workflowDefinition.PropertyBag.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new List()); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowExtensions.cs similarity index 55% rename from src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExtensions.cs rename to src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowExtensions.cs index 1ebba29a04..b5d78a2004 100644 --- a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExtensions.cs +++ b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowExtensions.cs @@ -1,3 +1,4 @@ +using System.Text.Json.Nodes; using Elsa.WorkflowContexts; using Elsa.Workflows.Activities; @@ -7,29 +8,18 @@ namespace Elsa.Extensions; /// /// Adds extension methods to . /// -public static class WorkflowExtensions +public static class WorkflowContextWorkflowExtensions { /// /// Gets the workflow context provider types that are installed on the workflow. /// /// The workflow to get the provider types from. /// The workflow context provider types. - public static IEnumerable GetWorkflowContextProviderTypes(this Workflow workflow) - { - var contextProviderTypes = workflow.PropertyBag.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new List()); - - var result = new List(); - - foreach (var type in contextProviderTypes) - { - var targetType = Type.GetType(type); - - if (targetType != null) - { - result.Add(targetType); - } - } - - return result; + public static IEnumerable GetWorkflowContextProviderTypes(this Workflow workflow) + { + var contextProviderTypes = workflow.CustomProperties.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new JsonArray()); + var providerTypes = contextProviderTypes.Select(x => Type.GetType(x.GetValue())).Where(x => x != null).Select(x => x!).ToList(); + + return providerTypes; } } \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs index 7b1cda1bc3..2c9193f72d 100644 --- a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs +++ b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs @@ -1,40 +1,40 @@ -using System.Text.Json; -using Elsa.Expressions.Contracts; +using System.Text.Json.Nodes; using Elsa.Extensions; using Elsa.WorkflowContexts.Contracts; using Elsa.Workflows; using Elsa.Workflows.Contracts; using Elsa.Workflows.Pipelines.ActivityExecution; -using Elsa.Workflows.Serialization.Converters; -using JetBrains.Annotations; +using Elsa.Workflows.Runtime.Middleware.Activities; using Microsoft.Extensions.DependencyInjection; namespace Elsa.WorkflowContexts.Middleware; /// Middleware that loads and save workflow context into the currently executing workflow using installed workflow context providers. -[UsedImplicitly] -public class WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory, IWellKnownTypeRegistry wellKnownTypeRegistry) : IActivityExecutionMiddleware +public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMiddleware { - private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions + private readonly ActivityMiddlewareDelegate _next; + private readonly IServiceScopeFactory _serviceScopeFactory; + + /// + /// Constructor. + /// + public WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory) { - PropertyNameCaseInsensitive = true - }.WithConverters(new TypeJsonConverter(wellKnownTypeRegistry)); + _next = next; + _serviceScopeFactory = serviceScopeFactory; + } /// public async ValueTask InvokeAsync(ActivityExecutionContext context) { // Check if the workflow contains any workflow context providers. - if (!context.WorkflowExecutionContext.Workflow.PropertyBag.TryGetValue>(Constants.WorkflowContextProviderTypesKey, out var providerTypes, _jsonSerializerOptions)) - { - await next(context); - return; - } - - if (providerTypes.Count == 0) + if (!context.WorkflowExecutionContext.Workflow.CustomProperties.TryGetValue(Constants.WorkflowContextProviderTypesKey, out var providerTypeNodes)) { - await next(context); + await _next(context); return; } + + var providerTypes = providerTypeNodes.Select(x => Type.GetType(x.GetValue())).Where(x => x != null).ToList(); // Check if this is a background execution. var isBackgroundExecution = context.GetIsBackgroundExecution(); @@ -42,12 +42,12 @@ public async ValueTask InvokeAsync(ActivityExecutionContext context) // Is the activity configured to load the context? foreach (var providerType in providerTypes) { - // Is the activity configured to load the context, or is this a background execution? + // Is the activity configured to load the context or is this a background execution? var load = isBackgroundExecution || context.Activity.GetActivityWorkflowContextSettings(providerType).Load; if (!load) continue; // Load the context. - using var scope = serviceScopeFactory.CreateScope(); + using var scope = _serviceScopeFactory.CreateScope(); var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType); var value = await provider.LoadAsync(context.WorkflowExecutionContext); @@ -56,7 +56,7 @@ public async ValueTask InvokeAsync(ActivityExecutionContext context) } // Invoke the next middleware. - await next(context); + await _next(context); // Invoke each workflow context provider to persists the context. foreach (var providerType in providerTypes) @@ -66,7 +66,7 @@ public async ValueTask InvokeAsync(ActivityExecutionContext context) if (!save) continue; // Get the loaded value from the workflow execution context. - using var scope = serviceScopeFactory.CreateScope(); + using var scope = _serviceScopeFactory.CreateScope(); var value = context.WorkflowExecutionContext.GetWorkflowContext(providerType); // Save the context. diff --git a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs index 5bb6c8e492..19794b864f 100644 --- a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs +++ b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs @@ -1,37 +1,37 @@ -using System.Text.Json; -using Elsa.Expressions.Contracts; +using System.Text.Json.Nodes; using Elsa.Extensions; using Elsa.WorkflowContexts.Contracts; using Elsa.Workflows; using Elsa.Workflows.Pipelines.WorkflowExecution; -using Elsa.Workflows.Serialization.Converters; using Microsoft.Extensions.DependencyInjection; namespace Elsa.WorkflowContexts.Middleware; -/// /// Middleware that loads and save workflow context into the currently executing workflow using installed workflow context providers. -/// -/// -public class WorkflowContextWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory, IWellKnownTypeRegistry wellKnownTypeRegistry) : WorkflowExecutionMiddleware(next) -{ - private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions +public class WorkflowContextWorkflowExecutionMiddleware : WorkflowExecutionMiddleware +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + + /// + public WorkflowContextWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory) : base(next) { - PropertyNameCaseInsensitive = true - }.WithConverters(new TypeJsonConverter(wellKnownTypeRegistry)); - + _serviceScopeFactory = serviceScopeFactory; + } + /// public override async ValueTask InvokeAsync(WorkflowExecutionContext context) { // Check if the workflow contains any workflow context providers. - if (!context.Workflow.PropertyBag.TryGetValue>(Constants.WorkflowContextProviderTypesKey, out var providerTypes, _jsonSerializerOptions)) + if (!context.Workflow.CustomProperties.TryGetValue(Constants.WorkflowContextProviderTypesKey, out var providerTypeNodes)) { await Next(context); return; } + var providerTypes = providerTypeNodes.Select(x => Type.GetType(x.GetValue())).Where(x => x != null).ToList(); + // Invoke each workflow context provider. - using (var scope = serviceScopeFactory.CreateScope()) + using (var scope = _serviceScopeFactory.CreateScope()) { foreach (var providerType in providerTypes) { @@ -47,7 +47,7 @@ public override async ValueTask InvokeAsync(WorkflowExecutionContext context) await Next(context); // Invoke each workflow context provider to persists the context. - using (var scope = serviceScopeFactory.CreateScope()) + using (var scope = _serviceScopeFactory.CreateScope()) { foreach (var providerType in providerTypes) { From bbdbb350f05180d075b0c8b874effe1ef5b5b384 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 6 Aug 2024 08:43:38 +0200 Subject: [PATCH 2/9] Refactor context provider and update email workflow Refactored `CustomerWorkflowContextProvider` to use constructor parameter directly, removing redundant fields and constructors. Updated comments and list syntax in `CustomerCommunicationsWorkflow`. Improved README.md documentation for workflow contexts and context providers. --- .../Entities/Order.cs | 2 +- .../Providers/CustomerWorkflowContextProvider.cs | 13 +++---------- .../Elsa.Samples.AspNet.WorkflowContexts/README.md | 8 +++++++- .../Workflows/CustomerCommunicationsWorkflow.cs | 10 +++++----- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Entities/Order.cs b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Entities/Order.cs index 90309c9faf..a4d4a7f8c0 100644 --- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Entities/Order.cs +++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Entities/Order.cs @@ -1,3 +1,3 @@ namespace Elsa.Samples.AspNet.WorkflowContexts.Entities; -public class Order{} \ No newline at end of file +public class Order; \ No newline at end of file diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Providers/CustomerWorkflowContextProvider.cs b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Providers/CustomerWorkflowContextProvider.cs index 5813e8ace8..54133efd68 100644 --- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Providers/CustomerWorkflowContextProvider.cs +++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Providers/CustomerWorkflowContextProvider.cs @@ -6,26 +6,19 @@ namespace Elsa.Samples.AspNet.WorkflowContexts.Providers; -public class CustomerWorkflowContextProvider : WorkflowContextProvider +public class CustomerWorkflowContextProvider(ICustomerStore customerStore) : WorkflowContextProvider { - private readonly ICustomerStore _customerStore; - - public CustomerWorkflowContextProvider(ICustomerStore customerStore) - { - _customerStore = customerStore; - } - protected override async ValueTask LoadAsync(WorkflowExecutionContext workflowExecutionContext) { var customerId = workflowExecutionContext.GetCustomerId(); - return customerId != null ? await _customerStore.GetAsync(customerId) : null; + return customerId != null ? await customerStore.GetAsync(customerId) : null; } protected override async ValueTask SaveAsync(WorkflowExecutionContext workflowExecutionContext, Customer? context) { if (context != null) { - await _customerStore.SaveAsync(context); + await customerStore.SaveAsync(context); workflowExecutionContext.SetCustomerId(context.Id); } } diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/README.md b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/README.md index 1a514869eb..1d487fb2c8 100644 --- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/README.md +++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/README.md @@ -1,6 +1,12 @@ # Server -This project represents an Elsa application that hosts workflows and exposes API endpoints to manage & execute workflows. +This project demonstrates how to use Workflow Contexts. + +A Workflow Context represents a custom, application-specific object provided to the workflow at runtime. +For example, if your workflow handles a Customer, a custom workflow context provider could provide this customer automatically to the workflow without the need for custom activities that load & persist updates to this customer. +Instead, the custom context provider would load the customer into memory once before the workflow starts and persists changes made to the customer (if any) when the workflow execution ends. + +In this sample project, we handle two custom objects: Customer and Order. ## Secrets The following are the secrets stored in hashed form in appsettings.json: diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs index 91ffb48a53..97d3fa160b 100644 --- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs +++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs @@ -11,7 +11,7 @@ namespace Elsa.Samples.AspNet.WorkflowContexts.Workflows; /// -/// A workflow that sends annoying emails to customers. +/// A workflow that sends helpful emails to customers. /// public class CustomerCommunicationsWorkflow : WorkflowBase { @@ -30,28 +30,28 @@ protected override void Build(IWorkflowBuilder builder) { Subject = new(context => $"Welcome to our family, {context.GetCustomer().Name}!"), Body = new("Welcome aboard!"), - To = new(context => new[] { context.GetCustomer().Email }) + To = new(context => [context.GetCustomer().Email]) }, Delay.FromSeconds(5), new SendEmail { Subject = new(context => $"{context.GetCustomer().Name}, we got great deals for you!"), Body = new("Get your creditcard ready!"), - To = new(context => new[] { context.GetCustomer().Email }) + To = new(context => [context.GetCustomer().Email]) }, Delay.FromSeconds(5), new SendEmail { Subject = new(context => $"{context.GetCustomer().Name}, you're missing out!"), Body = new("Sale ends in 2 hours!"), - To = new(context => new[] { context.GetCustomer().Email }) + To = new(context => [context.GetCustomer().Email]) }, Delay.FromSeconds(5), new SendEmail { Subject = new(context => $"{context.GetCustomer().Name}, the clock is ticking!"), Body = new("Tick tik tick!"), - To = new(context => new[] { context.GetCustomer().Email }) + To = new(context => [context.GetCustomer().Email]) }, } }; From 37a229b45d62752881f5d76ceb47fe2f633eae47 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 6 Aug 2024 08:57:19 +0200 Subject: [PATCH 3/9] Adjust logging levels and improve workflow logic Modified `appsettings.json` to reduce the verbosity of logging by adjusting log levels. Updated `CustomerCommunicationsWorkflow` to include a timer activity and simplified the workflow context parameter setting. --- .../Workflows/CustomerCommunicationsWorkflow.cs | 12 ++++++++---- .../appsettings.json | 10 ++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs index 97d3fa160b..2ce726681c 100644 --- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs +++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs @@ -10,11 +10,12 @@ namespace Elsa.Samples.AspNet.WorkflowContexts.Workflows; -/// /// A workflow that sends helpful emails to customers. -/// public class CustomerCommunicationsWorkflow : WorkflowBase { + private static string CustomerId = "2"; + + /// protected override void Build(IWorkflowBuilder builder) { builder.AddWorkflowContextProvider(); @@ -23,8 +24,11 @@ protected override void Build(IWorkflowBuilder builder) { Activities = { - SetWorkflowContextParameter.For( - context => context.GetInput("CustomerId")!), + new Scheduling.Activities.Timer(TimeSpan.FromSeconds(5)) + { + CanStartWorkflow = true + }, + SetWorkflowContextParameter.For(CustomerId), Delay.FromSeconds(5), new SendEmail { diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/appsettings.json b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/appsettings.json index b8d0788786..43168e6aaf 100644 --- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/appsettings.json +++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/appsettings.json @@ -1,14 +1,8 @@ { "Logging": { "LogLevel": { - "Default": "Debug", - "Elsa.Mediator": "Warning", - "MassTransit": "Warning", - "Microsoft.Extensions.Http": "Warning", - "Microsoft.Hosting.Lifetime": "Information", - "Microsoft.EntityFrameworkCore": "Warning", - "Microsoft.AspNetCore": "Warning", - "System.Net.Http": "Warning" + "Default": "Warning", + "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", From 79c851f49d2219ce846df95166781650e57fe832 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 6 Aug 2024 20:29:05 +0200 Subject: [PATCH 4/9] Remove PropertyBag and related functionalities. Deleted the PropertyBag class and extension methods, and removed all instances where PropertyBag was utilized across various modules. This simplifies the workflow configuration and management by relying solely on CustomProperties for storing metadata. --- .../CustomerCommunicationsWorkflow.cs | 2 +- .../appsettings.json | 2 +- .../Models/WorkflowDefinition.cs | 5 -- .../Models/WorkflowDefinitionModel.cs | 5 -- .../Extensions/PropertyBagExtensions.cs | 67 ------------------- src/modules/Elsa.Common/Models/PropertyBag.cs | 20 ------ .../Modules/Management/Configurations.cs | 1 - .../Management/WorkflowDefinitionStore.cs | 13 +--- .../Activities/SetWorkflowContextParameter.cs | 2 +- .../JavaScript/ConfigureJavaScriptEngine.cs | 18 +---- .../WorkflowDefinitions/Post/Endpoint.cs | 3 +- .../StaticWorkflowDefinitionLinker.cs | 2 - .../Activities/Workflow.cs | 7 -- .../Builders/WorkflowBuilder.cs | 6 +- .../Contracts/IWorkflowBuilder.cs | 6 -- .../Entities/WorkflowDefinition.cs | 2 +- .../Mappers/WorkflowDefinitionMapper.cs | 4 -- .../Models/WorkflowDefinitionModel.cs | 3 - .../Services/WorkflowDefinitionImporter.cs | 3 +- 19 files changed, 13 insertions(+), 158 deletions(-) delete mode 100644 src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs delete mode 100644 src/modules/Elsa.Common/Models/PropertyBag.cs diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs index 2ce726681c..5b0533e8dd 100644 --- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs +++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs @@ -26,7 +26,7 @@ protected override void Build(IWorkflowBuilder builder) { new Scheduling.Activities.Timer(TimeSpan.FromSeconds(5)) { - CanStartWorkflow = true + CanStartWorkflow = false }, SetWorkflowContextParameter.For(CustomerId), Delay.FromSeconds(5), diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/appsettings.json b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/appsettings.json index 43168e6aaf..a5f549d8db 100644 --- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/appsettings.json +++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/appsettings.json @@ -11,7 +11,7 @@ }, "Identity": { "Tokens": { - "SigningKey": "secret-signing-key", + "SigningKey": "or*bHQ26K9yjT4wsqk8k7h*!jCgvctzM89Zt@ENFGeuT-xZWQ9i.36u6YBhXBZut", "AccessTokenLifetime": "1:00:00:00", "RefreshTokenLifetime": "1:00:10:00" }, diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinition.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinition.cs index 4d2ba3ef55..f3fe5d0ebe 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinition.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinition.cs @@ -58,11 +58,6 @@ public class WorkflowDefinition : LinkedEntity /// public IDictionary CustomProperties { get; set; } = new Dictionary(); - /// - /// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow. - /// - public PropertyBag PropertyBag { get; set; } = new(); - /// /// The name of the workflow provider that created this workflow, if any. /// diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinitionModel.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinitionModel.cs index 76dba636d3..a617b54f35 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinitionModel.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowDefinitionModel.cs @@ -55,11 +55,6 @@ public class WorkflowDefinitionModel : LinkedEntity /// public IDictionary? CustomProperties { get; set; } - /// - /// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow. - /// - public PropertyBag PropertyBag { get; set; } = new(); - /// /// Gets or sets whether the workflow definition is read-only. /// diff --git a/src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs b/src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs deleted file mode 100644 index ad0359c213..0000000000 --- a/src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Text.Json; -using Elsa.Common.Models; - -// ReSharper disable once CheckNamespace -namespace Elsa.Extensions; - -/// -/// Provides extension methods for the PropertyBag class. -/// -public static class PropertyBagExtensions -{ - /// - /// Tries to retrieve a value from the PropertyBag based on the provided key. - /// If the specified key does not exist in the PropertyBag, the method will return the default value obtained from the defaultValue function. - /// - /// The type of the value to retrieve. - /// The PropertyBag to retrieve the value from. - /// The key of the value to retrieve. - /// A function that returns the default value to be returned if the key does not exist in the PropertyBag. - /// Optional JSON serializer options. - /// The value associated with the key, or the default value if the key does not exist. - public static T TryGetValueOrDefault(this PropertyBag propertyBag, string key, Func defaultValue, JsonSerializerOptions? options = null) - { - if (!propertyBag.TryGetValue(key, out var value)) - return defaultValue(); - - var json = (string)value; - return JsonSerializer.Deserialize(json, options); - } - - /// - /// Tries to retrieve a value from the PropertyBag based on the provided key. - /// If the specified key does not exist in the PropertyBag, the method will return the default value obtained from the defaultValue function. - /// - /// The type of the value to retrieve. - /// The PropertyBag to retrieve the value from. - /// The key of the value to retrieve. - /// The deserialized value. - /// Optional JSON serializer options. - /// True if the value exists, false otherwise. - public static bool TryGetValue(this PropertyBag propertyBag, string key, out T value, JsonSerializerOptions? options = null) - { - if (!propertyBag.TryGetValue(key, out var v)) - { - value = default!; - return false; - } - - var json = (string)v; - value = JsonSerializer.Deserialize(json, options); - return true; - } - - /// - /// Sets a value in the PropertyBag based on the provided key. - /// The value is serialized using JSON. - /// - /// The PropertyBag to set the value in. - /// The key to associate with the value. - /// The value to store in the PropertyBag. - /// /// Optional JSON serializer options. - public static void SetValue(this PropertyBag propertyBag, string key, object value, JsonSerializerOptions? options = null) - { - var json = JsonSerializer.Serialize(value); - propertyBag[key] = json; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Models/PropertyBag.cs b/src/modules/Elsa.Common/Models/PropertyBag.cs deleted file mode 100644 index 0f6209e811..0000000000 --- a/src/modules/Elsa.Common/Models/PropertyBag.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Elsa.Common.Models; - -/// A dictionary of values that is skipped by polymorphic serialization. -public class PropertyBag : Dictionary -{ - /// - [JsonConstructor] - public PropertyBag() : base(StringComparer.OrdinalIgnoreCase) - { - } - - /// - public PropertyBag(IDictionary dictionary) : this() - { - foreach (var kvp in dictionary) - Add(kvp.Key, kvp.Value); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Configurations.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Configurations.cs index 7f25567ddd..02cdccc5c8 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Configurations.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/Configurations.cs @@ -17,7 +17,6 @@ public void Configure(EntityTypeBuilder builder) builder.Ignore(x => x.Outputs); builder.Ignore(x => x.Outcomes); builder.Ignore(x => x.CustomProperties); - builder.Ignore(x => x.PropertyBag); builder.Ignore(x => x.Options); builder.Property("Data"); builder.Property("UsableAsActivity"); diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs index 7421e59382..2c68860757 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs @@ -152,7 +152,7 @@ public async Task GetIsNameUnique(string name, string? definitionId = defa private ValueTask OnSaveAsync(ManagementElsaDbContext managementElsaDbContext, WorkflowDefinition entity, CancellationToken cancellationToken) { - var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties, entity.PropertyBag); + var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties); var json = _payloadSerializer.Serialize(data); managementElsaDbContext.Entry(entity).Property("Data").CurrentValue = json; @@ -165,7 +165,7 @@ private ValueTask OnLoadAsync(ManagementElsaDbContext managementElsaDbContext, W if (entity == null) return ValueTask.CompletedTask; - var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties, entity.PropertyBag); + var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties); var json = (string?)managementElsaDbContext.Entry(entity).Property("Data").CurrentValue; if (!string.IsNullOrWhiteSpace(json)) @@ -177,7 +177,6 @@ private ValueTask OnLoadAsync(ManagementElsaDbContext managementElsaDbContext, W entity.Outputs = data.Outputs; entity.Outcomes = data.Outcomes; entity.CustomProperties = data.CustomProperties; - entity.PropertyBag = data.PropertyBag; return ValueTask.CompletedTask; } @@ -226,8 +225,7 @@ public WorkflowDefinitionState( ICollection inputs, ICollection outputs, ICollection outcomes, - IDictionary customProperties, - PropertyBag propertyBag + IDictionary customProperties ) { Options = options; @@ -236,7 +234,6 @@ PropertyBag propertyBag Outputs = outputs; Outcomes = outcomes; CustomProperties = customProperties; - PropertyBag = propertyBag; } public WorkflowOptions Options { get; set; } = new(); @@ -244,10 +241,6 @@ PropertyBag propertyBag public ICollection Inputs { get; set; } = new List(); public ICollection Outputs { get; set; } = new List(); public ICollection Outcomes { get; set; } = new List(); - - [Obsolete("Use PropertyBag instead")] public IDictionary CustomProperties { get; set; } = new Dictionary(); - - public PropertyBag PropertyBag { get; set; } = new(); } } \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs b/src/modules/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs index e6406b55ef..4962364abb 100644 --- a/src/modules/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs +++ b/src/modules/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs @@ -12,7 +12,7 @@ namespace Elsa.WorkflowContexts.Activities; /// /// Sets a workflow context parameter for a given workflow context provider. /// -[Activity("Elsa", "Primitives", "Sets a workflow context parameter for a given workflow context provider.")] +[Activity("Elsa", "Workflow Context", "Sets a workflow context parameter for a given workflow context provider.")] [PublicAPI] public class SetWorkflowContextParameter : CodeActivity { diff --git a/src/modules/Elsa.WorkflowContexts/Scripting/JavaScript/ConfigureJavaScriptEngine.cs b/src/modules/Elsa.WorkflowContexts/Scripting/JavaScript/ConfigureJavaScriptEngine.cs index 250de1463a..4ad44a9ee3 100644 --- a/src/modules/Elsa.WorkflowContexts/Scripting/JavaScript/ConfigureJavaScriptEngine.cs +++ b/src/modules/Elsa.WorkflowContexts/Scripting/JavaScript/ConfigureJavaScriptEngine.cs @@ -14,20 +14,8 @@ namespace Elsa.WorkflowContexts.Scripting.JavaScript; /// /// Configures the JavaScript engine with functions that allow access to workflow contexts. /// -public class ConfigureJavaScriptEngine : INotificationHandler, IFunctionDefinitionProvider, ITypeDefinitionProvider +public class ConfigureJavaScriptEngine(ITypeAliasRegistry typeAliasRegistry, ITypeDescriber typeDescriber) : INotificationHandler, IFunctionDefinitionProvider, ITypeDefinitionProvider { - private readonly ITypeAliasRegistry _typeAliasRegistry; - private readonly ITypeDescriber _typeDescriber; - - /// - /// Initializes a new instance of the class. - /// - public ConfigureJavaScriptEngine(ITypeAliasRegistry typeAliasRegistry, ITypeDescriber typeDescriber) - { - _typeAliasRegistry = typeAliasRegistry; - _typeDescriber = typeDescriber; - } - /// public Task HandleAsync(EvaluatingJavaScript notification, CancellationToken cancellationToken) { @@ -52,7 +40,7 @@ public ValueTask> GetTypeDefinitionsAsync(TypeDefini { var providerTypes = GetProviderTypes(context.Workflow); var contextTypes = providerTypes.Select(x => x.GetWorkflowContextType()); - var typeDefinitions = contextTypes.Select(x => _typeDescriber.DescribeType(x)); + var typeDefinitions = contextTypes.Select(x => typeDescriber.DescribeType(x)); return new(typeDefinitions); } @@ -72,7 +60,7 @@ private IEnumerable BuildFunctionDefinitions(IEnumerable(); - draft.PropertyBag = model.PropertyBag ?? new PropertyBag(); + draft.CustomProperties = model.CustomProperties ?? new Dictionary(); draft.Variables = variables; draft.Inputs = inputs; draft.Outputs = outputs; diff --git a/src/modules/Elsa.Workflows.Api/Services/StaticWorkflowDefinitionLinker.cs b/src/modules/Elsa.Workflows.Api/Services/StaticWorkflowDefinitionLinker.cs index d065b58184..da2a3351ef 100644 --- a/src/modules/Elsa.Workflows.Api/Services/StaticWorkflowDefinitionLinker.cs +++ b/src/modules/Elsa.Workflows.Api/Services/StaticWorkflowDefinitionLinker.cs @@ -32,7 +32,6 @@ public async Task MapAsync(WorkflowDefinition def Outputs = workflowDefinitionModel.Outputs, Outcomes = workflowDefinitionModel.Outcomes, CustomProperties = workflowDefinitionModel.CustomProperties, - PropertyBag = workflowDefinitionModel.PropertyBag, IsReadonly = workflowDefinitionModel.IsReadonly, IsSystem = workflowDefinitionModel.IsSystem, IsLatest = workflowDefinitionModel.IsLatest, @@ -100,7 +99,6 @@ public async Task> MapAsync(List outputs, ICollection outcomes, IDictionary customProperties, - PropertyBag propertyBag, bool isReadonly, bool isSystem) { @@ -40,7 +39,6 @@ public Workflow( Inputs = inputs; Outputs = outputs; Outcomes = outcomes; - PropertyBag = propertyBag; WorkflowMetadata = workflowMetadata; Options = options; Variables = variables; @@ -99,11 +97,6 @@ public Workflow() /// Gets or sets options for the workflow. /// public WorkflowOptions Options { get; set; } = new(); - - /// - /// A bag of properties that can be used by applications and modules to store information that can be shared with tooling. - /// - public PropertyBag PropertyBag { get; set; } = new(); /// /// Make workflow definition readonly. diff --git a/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs b/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs index 246ee7bad2..878de48b85 100644 --- a/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Builders/WorkflowBuilder.cs @@ -1,4 +1,3 @@ -using Elsa.Common.Models; using Elsa.Extensions; using Elsa.Workflows.Activities; using Elsa.Workflows.Contracts; @@ -53,9 +52,6 @@ public class WorkflowBuilder(IActivityVisitor activityVisitor, IIdentityGraphSer /// public IDictionary CustomProperties { get; set; } = new Dictionary(); - /// - public PropertyBag PropertyBag { get; set; } = new(); - /// public WorkflowOptions WorkflowOptions { get; } = new(); @@ -211,7 +207,7 @@ public async Task BuildWorkflowAsync(CancellationToken cancellationTok var publication = WorkflowPublication.LatestAndPublished; var name = string.IsNullOrEmpty(Name) ? definitionId : Name; var workflowMetadata = new WorkflowMetadata(name, Description); - var workflow = new Workflow(identity, publication, workflowMetadata, WorkflowOptions, root, Variables, Inputs, Outputs, Outcomes, CustomProperties, PropertyBag, IsReadonly, IsSystem); + var workflow = new Workflow(identity, publication, workflowMetadata, WorkflowOptions, root, Variables, Inputs, Outputs, Outcomes, CustomProperties, IsReadonly, IsSystem); // If a Result variable is defined, install it into the workflow, so we can capture the output into it. if (Result != null) diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowBuilder.cs b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowBuilder.cs index 57bf044a38..73dc2fd927 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowBuilder.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IWorkflowBuilder.cs @@ -1,4 +1,3 @@ -using Elsa.Common.Models; using Elsa.Workflows.Activities; using Elsa.Workflows.Memory; using Elsa.Workflows.Models; @@ -86,11 +85,6 @@ public interface IWorkflowBuilder [Obsolete("Use PropertyBag instead")] IDictionary CustomProperties { get; } - /// - /// A set of properties that can be used for storing application-specific information about the workflow being built. - /// - PropertyBag PropertyBag { get; set; } - /// /// A fluent method for setting the property. /// diff --git a/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs b/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs index 9405a4dc81..6a92005e95 100644 --- a/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs +++ b/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs @@ -59,7 +59,7 @@ public class WorkflowDefinition : VersionedEntity public IDictionary CustomProperties { get; set; } = new Dictionary(); /// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow. - public PropertyBag PropertyBag { get; set; } = new(); + //public PropertyBag PropertyBag { get; set; } = new(); /// /// The name of the workflow provider that created this workflow, if any. diff --git a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs index 9f6ac512a0..a090db733e 100644 --- a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs +++ b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs @@ -46,7 +46,6 @@ public Workflow Map(WorkflowDefinition source) source.Outputs, source.Outcomes, source.CustomProperties, - source.PropertyBag, source.IsReadonly, source.IsSystem); } @@ -79,7 +78,6 @@ public Workflow Map(WorkflowDefinitionModel source) source.Outputs ?? new List(), source.Outcomes ?? new List(), source.CustomProperties ?? new Dictionary(), - source.PropertyBag ?? new(), source.IsReadonly, source.IsSystem); } @@ -118,7 +116,6 @@ public async Task MapAsync(WorkflowDefinition workflowD workflowDefinition.Outputs, workflowDefinition.Outcomes, workflowDefinition.CustomProperties, - workflowDefinition.PropertyBag, workflowDefinition.IsReadonly, workflowDefinition.IsSystem, workflowDefinition.IsLatest, @@ -150,7 +147,6 @@ public WorkflowDefinitionModel Map(Workflow workflow) workflow.Outputs, workflow.Outcomes, workflow.CustomProperties, - workflow.PropertyBag, workflow.IsReadonly, workflow.IsSystem, workflow.Publication.IsLatest, diff --git a/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs b/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs index 5f03324868..02e276980a 100644 --- a/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs +++ b/src/modules/Elsa.Workflows.Management/Models/WorkflowDefinitionModel.cs @@ -19,9 +19,7 @@ public record WorkflowDefinitionModel( ICollection? Inputs, ICollection? Outputs, ICollection? Outcomes, - [property: Obsolete("Use PropertyBag instead")] IDictionary? CustomProperties, - PropertyBag? PropertyBag, bool IsReadonly, bool IsSystem, bool IsLatest, @@ -46,7 +44,6 @@ public WorkflowDefinitionModel() : this( default!, default!, default, - default, default!, default!, default!, diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs index 47df456372..f09a4ef7b4 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs @@ -63,8 +63,7 @@ public async Task ImportAsync(SaveWorkflowDefinitionReques draft.MaterializerName = JsonWorkflowMaterializer.MaterializerName; draft.Name = model.Name?.Trim(); draft.Description = model.Description?.Trim(); - draft.CustomProperties = model.CustomProperties ?? new Dictionary(); - draft.PropertyBag = model.PropertyBag ?? new PropertyBag(); + draft.CustomProperties = model.CustomProperties ?? new Dictionary(); draft.Variables = variables; draft.Inputs = model.Inputs ?? new List(); draft.Outputs = model.Outputs ?? new List(); From b762ef6ffebccac6dd711d4bd054a5663b933a9f Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 6 Aug 2024 21:02:27 +0200 Subject: [PATCH 5/9] Add workflow context management extensions and optimizations Introduces `WorkflowExecutionContextExtensions` for loading and saving workflow contexts more efficiently. Replaces `JsonArray` usage with `List` for context provider types, simplifying the type management in the workflow execution context. Streamlines `WorkflowContextActivityExecutionMiddleware` by removing redundant service scope creation and enhancing readability. --- .../Activities/SetWorkflowContextParameter.cs | 7 ++- .../WorkflowContextWorkflowExtensions.cs | 5 +- .../WorkflowExecutionContextExtensions.cs | 28 +++++++++++ ...kflowContextActivityExecutionMiddleware.cs | 46 +++++-------------- ...kflowContextWorkflowExecutionMiddleware.cs | 6 +-- 5 files changed, 49 insertions(+), 43 deletions(-) create mode 100644 src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExecutionContextExtensions.cs diff --git a/src/modules/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs b/src/modules/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs index 4962364abb..00fbf01c93 100644 --- a/src/modules/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs +++ b/src/modules/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs @@ -6,6 +6,7 @@ using Elsa.Workflows.Attributes; using Elsa.Workflows.Models; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; namespace Elsa.WorkflowContexts.Activities; @@ -100,13 +101,17 @@ public static SetWorkflowContextParameter For(Func ParameterValue { get; set; } = default!; /// - protected override void Execute(ActivityExecutionContext context) + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { var providerType = ProviderType.Get(context); var parameterName = ParameterName.GetOrDefault(context); var scopedParameterName = providerType.GetScopedParameterName(parameterName); var parameterValue = ParameterValue.Get(context); + // Update the parameter. context.WorkflowExecutionContext.SetProperty(scopedParameterName, parameterValue); + + // Load the context. + await context.WorkflowExecutionContext.LoadWorkflowContextAsync(providerType); } } \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowExtensions.cs index b5d78a2004..b3281e64cd 100644 --- a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowExtensions.cs +++ b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowContextWorkflowExtensions.cs @@ -1,4 +1,3 @@ -using System.Text.Json.Nodes; using Elsa.WorkflowContexts; using Elsa.Workflows.Activities; @@ -17,8 +16,8 @@ public static class WorkflowContextWorkflowExtensions /// The workflow context provider types. public static IEnumerable GetWorkflowContextProviderTypes(this Workflow workflow) { - var contextProviderTypes = workflow.CustomProperties.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new JsonArray()); - var providerTypes = contextProviderTypes.Select(x => Type.GetType(x.GetValue())).Where(x => x != null).Select(x => x!).ToList(); + var contextProviderTypes = workflow.CustomProperties.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new List()); + var providerTypes = contextProviderTypes.Select(x => Type.GetType(x.ToString()!)).Where(x => x != null).Select(x => x!).ToList(); return providerTypes; } diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExecutionContextExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExecutionContextExtensions.cs new file mode 100644 index 0000000000..57a2bebe9b --- /dev/null +++ b/src/modules/Elsa.WorkflowContexts/Extensions/WorkflowExecutionContextExtensions.cs @@ -0,0 +1,28 @@ + + +using Elsa.WorkflowContexts.Contracts; +using Microsoft.Extensions.DependencyInjection; +// ReSharper disable once CheckNamespace +using Elsa.Workflows; + +namespace Elsa.Extensions; + +public static class WorkflowExecutionContextExtensions +{ + public static async Task LoadWorkflowContextAsync(this WorkflowExecutionContext workflowExecutionContext, Type providerType) + { + // Load the context. + var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(workflowExecutionContext.ServiceProvider, providerType); + var value = await provider.LoadAsync(workflowExecutionContext); + + // Store the loaded value into the workflow execution context. + workflowExecutionContext.SetWorkflowContext(providerType, value!); + } + + public static async Task SaveWorkflowContextAsync(this WorkflowExecutionContext workflowExecutionContext, Type providerType) + { + var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(workflowExecutionContext.ServiceProvider, providerType); + var value = workflowExecutionContext.GetWorkflowContext(providerType); + await provider.SaveAsync(workflowExecutionContext, value); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs index 2c9193f72d..6d32cf2295 100644 --- a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs +++ b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs @@ -1,40 +1,26 @@ -using System.Text.Json.Nodes; using Elsa.Extensions; -using Elsa.WorkflowContexts.Contracts; using Elsa.Workflows; using Elsa.Workflows.Contracts; using Elsa.Workflows.Pipelines.ActivityExecution; -using Elsa.Workflows.Runtime.Middleware.Activities; -using Microsoft.Extensions.DependencyInjection; +using JetBrains.Annotations; namespace Elsa.WorkflowContexts.Middleware; /// Middleware that loads and save workflow context into the currently executing workflow using installed workflow context providers. -public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMiddleware +[UsedImplicitly] +public class WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next) : IActivityExecutionMiddleware { - private readonly ActivityMiddlewareDelegate _next; - private readonly IServiceScopeFactory _serviceScopeFactory; - - /// - /// Constructor. - /// - public WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory) - { - _next = next; - _serviceScopeFactory = serviceScopeFactory; - } - /// public async ValueTask InvokeAsync(ActivityExecutionContext context) { // Check if the workflow contains any workflow context providers. - if (!context.WorkflowExecutionContext.Workflow.CustomProperties.TryGetValue(Constants.WorkflowContextProviderTypesKey, out var providerTypeNodes)) + if (!context.WorkflowExecutionContext.Workflow.CustomProperties.TryGetValue>(Constants.WorkflowContextProviderTypesKey, out var providerTypeNodes)) { - await _next(context); + await next(context); return; } - var providerTypes = providerTypeNodes.Select(x => Type.GetType(x.GetValue())).Where(x => x != null).ToList(); + var providerTypes = providerTypeNodes.Select(x => Type.GetType(x.ToString()!)).Where(x => x != null).ToList(); // Check if this is a background execution. var isBackgroundExecution = context.GetIsBackgroundExecution(); @@ -47,31 +33,21 @@ public async ValueTask InvokeAsync(ActivityExecutionContext context) if (!load) continue; // Load the context. - using var scope = _serviceScopeFactory.CreateScope(); - var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType); - var value = await provider.LoadAsync(context.WorkflowExecutionContext); - - // Store the loaded value into the workflow execution context. - context.WorkflowExecutionContext.SetWorkflowContext(providerType, value!); + await context.WorkflowExecutionContext.LoadWorkflowContextAsync(providerType); } // Invoke the next middleware. - await _next(context); + await next(context); - // Invoke each workflow context provider to persists the context. + // Invoke each workflow context provider to persist the context. foreach (var providerType in providerTypes) { - // Is the activity configured to save the context or is this a background execution? + // Is the activity configured to save the context, or is this a background execution? var save = isBackgroundExecution || context.Activity.GetActivityWorkflowContextSettings(providerType).Load; if (!save) continue; - // Get the loaded value from the workflow execution context. - using var scope = _serviceScopeFactory.CreateScope(); - var value = context.WorkflowExecutionContext.GetWorkflowContext(providerType); - // Save the context. - var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType); - await provider.SaveAsync(context.WorkflowExecutionContext, value); + await context.WorkflowExecutionContext.SaveWorkflowContextAsync(providerType); } } } \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs index 19794b864f..cf930b6b08 100644 --- a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs +++ b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs @@ -1,4 +1,3 @@ -using System.Text.Json.Nodes; using Elsa.Extensions; using Elsa.WorkflowContexts.Contracts; using Elsa.Workflows; @@ -17,18 +16,17 @@ public WorkflowContextWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate nex { _serviceScopeFactory = serviceScopeFactory; } - /// public override async ValueTask InvokeAsync(WorkflowExecutionContext context) { // Check if the workflow contains any workflow context providers. - if (!context.Workflow.CustomProperties.TryGetValue(Constants.WorkflowContextProviderTypesKey, out var providerTypeNodes)) + if (!context.Workflow.CustomProperties.TryGetValue>(Constants.WorkflowContextProviderTypesKey, out var providerTypeObjects)) { await Next(context); return; } - var providerTypes = providerTypeNodes.Select(x => Type.GetType(x.GetValue())).Where(x => x != null).ToList(); + var providerTypes = providerTypeObjects.Select(x => Type.GetType(x.ToString())).Where(x => x != null).ToList(); // Invoke each workflow context provider. using (var scope = _serviceScopeFactory.CreateScope()) From 6e2db187a202f51ac10ff0f487825ef2226ec372 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 6 Aug 2024 22:05:49 +0200 Subject: [PATCH 6/9] Remove redundant timer in CustomerCommunicationsWorkflow The timer activity that was set to not start the workflow has been removed to streamline the workflow process. Updated the README to include instructions on how to start the CustomerCommunicationsWorkflow using the REST API or Elsa Studio. --- samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/README.md | 2 ++ .../Workflows/CustomerCommunicationsWorkflow.cs | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/README.md b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/README.md index 1d487fb2c8..bf37ae2c6c 100644 --- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/README.md +++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/README.md @@ -8,6 +8,8 @@ Instead, the custom context provider would load the customer into memory once be In this sample project, we handle two custom objects: Customer and Order. +To start the `CustomerCommunicationsWorkflow`, start it using the REST API or from Elsa Studio. + ## Secrets The following are the secrets stored in hashed form in appsettings.json: diff --git a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs index 5b0533e8dd..4b4597c2ef 100644 --- a/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs +++ b/samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/Workflows/CustomerCommunicationsWorkflow.cs @@ -24,10 +24,6 @@ protected override void Build(IWorkflowBuilder builder) { Activities = { - new Scheduling.Activities.Timer(TimeSpan.FromSeconds(5)) - { - CanStartWorkflow = false - }, SetWorkflowContextParameter.For(CustomerId), Delay.FromSeconds(5), new SendEmail From 651bca5a095463932e6564f8f55dd79fb685d7c0 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 6 Aug 2024 22:16:40 +0200 Subject: [PATCH 7/9] Refactor middleware to streamline context management Removed dependency on `IServiceScopeFactory` and refactored middleware to directly load and save workflow contexts. Simplified workflow context provider invocation, enhancing code readability and maintainability. --- ...kflowContextActivityExecutionMiddleware.cs | 2 +- ...kflowContextWorkflowExecutionMiddleware.cs | 39 ++++--------------- 2 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs index 6d32cf2295..24e8a5e717 100644 --- a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs +++ b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs @@ -9,7 +9,7 @@ namespace Elsa.WorkflowContexts.Middleware; /// Middleware that loads and save workflow context into the currently executing workflow using installed workflow context providers. [UsedImplicitly] public class WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next) : IActivityExecutionMiddleware -{ +{ /// public async ValueTask InvokeAsync(ActivityExecutionContext context) { diff --git a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs index cf930b6b08..8dd423aa2b 100644 --- a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs +++ b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextWorkflowExecutionMiddleware.cs @@ -1,20 +1,15 @@ using Elsa.Extensions; -using Elsa.WorkflowContexts.Contracts; using Elsa.Workflows; using Elsa.Workflows.Pipelines.WorkflowExecution; -using Microsoft.Extensions.DependencyInjection; namespace Elsa.WorkflowContexts.Middleware; /// Middleware that loads and save workflow context into the currently executing workflow using installed workflow context providers. public class WorkflowContextWorkflowExecutionMiddleware : WorkflowExecutionMiddleware -{ - private readonly IServiceScopeFactory _serviceScopeFactory; - +{ /// - public WorkflowContextWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory) : base(next) + public WorkflowContextWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next) : base(next) { - _serviceScopeFactory = serviceScopeFactory; } /// public override async ValueTask InvokeAsync(WorkflowExecutionContext context) @@ -26,35 +21,17 @@ public override async ValueTask InvokeAsync(WorkflowExecutionContext context) return; } - var providerTypes = providerTypeObjects.Select(x => Type.GetType(x.ToString())).Where(x => x != null).ToList(); + var providerTypes = providerTypeObjects.Select(x => Type.GetType(x.ToString()!)).Where(x => x != null).Select(x => x!).ToList(); // Invoke each workflow context provider. - using (var scope = _serviceScopeFactory.CreateScope()) - { - foreach (var providerType in providerTypes) - { - var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType); - var value = await provider.LoadAsync(context); - - // Store the loaded value into the workflow execution context. - context.SetWorkflowContext(providerType, value!); - } - } + foreach (var providerType in providerTypes) + await context.LoadWorkflowContextAsync(providerType); // Invoke the next middleware. await Next(context); - // Invoke each workflow context provider to persists the context. - using (var scope = _serviceScopeFactory.CreateScope()) - { - foreach (var providerType in providerTypes) - { - // Get the loaded value from the workflow execution context. - var value = context.GetWorkflowContext(providerType); - - var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType); - await provider.SaveAsync(context, value); - } - } + // Invoke each workflow context provider to persist the context. + foreach (var providerType in providerTypes) + await context.SaveWorkflowContextAsync(providerType); } } \ No newline at end of file From 76905d4a13fc31da3e1cc936c1e4768c53346678 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 6 Aug 2024 22:19:47 +0200 Subject: [PATCH 8/9] Remove redundant PropertyBag property The PropertyBag property was commented out and is no longer needed in the WorkflowDefinition class. This change removes the commented-out code to clean up the class definition. --- .../Elsa.Workflows.Management/Entities/WorkflowDefinition.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs b/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs index 6a92005e95..9bffa04991 100644 --- a/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs +++ b/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs @@ -58,9 +58,6 @@ public class WorkflowDefinition : VersionedEntity /// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow. public IDictionary CustomProperties { get; set; } = new Dictionary(); - /// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow. - //public PropertyBag PropertyBag { get; set; } = new(); - /// /// The name of the workflow provider that created this workflow, if any. /// From ccca99600467d58190f6213ad790740baaad7d44 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 6 Aug 2024 22:35:41 +0200 Subject: [PATCH 9/9] Add JsonSerializerOptions for deserialization Introduce JsonSerializerOptions with camelCase policy and case insensitivity for property names. This ensures consistent deserialization of custom properties in activity workflow context settings. --- .../Extensions/ActivityExtensions.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/modules/Elsa.WorkflowContexts/Extensions/ActivityExtensions.cs b/src/modules/Elsa.WorkflowContexts/Extensions/ActivityExtensions.cs index f6e3963179..8e9038f94c 100644 --- a/src/modules/Elsa.WorkflowContexts/Extensions/ActivityExtensions.cs +++ b/src/modules/Elsa.WorkflowContexts/Extensions/ActivityExtensions.cs @@ -12,6 +12,12 @@ namespace Elsa.Extensions; public static class ActivityExtensions { private const string ActivityWorkflowContextSettingsKey = "ActivityWorkflowContextSettingsKey"; + + private static readonly JsonSerializerOptions JsonSerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true + }; /// /// Gets the workflow context settings for the specified activity. @@ -21,7 +27,6 @@ public static class ActivityExtensions public static IDictionary GetWorkflowContextSettings(this IActivity activity) { var contextSettings = activity.CustomProperties.GetOrAdd(ActivityWorkflowContextSettingsKey, () => new JsonObject()); - var result = new Dictionary(); foreach(var (key, jsonNode) in contextSettings) @@ -30,7 +35,7 @@ public static IDictionary GetWorkflowCont if(targetType != null) { - var value = jsonNode.Deserialize()!; + var value = jsonNode.Deserialize(JsonSerializerOptions)!; result.Add(targetType, value); } } @@ -75,7 +80,7 @@ public static TActivity SaveContext(this TActivity activity, Type pro /// The workflow context settings. public static ActivityWorkflowContextSettings GetActivityWorkflowContextSettings(this TActivity activity, Type providerType) where TActivity: IActivity { - var dictionary = activity.GetWorkflowContextSettings()!; + var dictionary = activity.GetWorkflowContextSettings(); return dictionary.GetActivityWorkflowContextSettings(providerType); }