From a7360ed9223187115174124ed17d0c56df78c674 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 21 Jan 2025 19:11:31 +0100 Subject: [PATCH 1/6] Add option to disable variable copying in Jint engine Introduce a `DisableVariableCopying` option to improve performance by preventing workflow variables from being copied into the Jint engine or back into the workflow context. Updated related logic to honor this setting and ensure compatibility with existing behavior. --- src/apps/Elsa.Server.Web/Program.cs | 2 ++ .../Extensions/EngineExtensions.cs | 14 +++++----- .../Handlers/ConfigureEngineWithVariables.cs | 27 +++++++++++++++---- .../Elsa.JavaScript/Options/JintOptions.cs | 6 +++++ .../ExpressionExecutionContextExtensions.cs | 8 +++--- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs index 3f7f5f7333..1e4778c0af 100644 --- a/src/apps/Elsa.Server.Web/Program.cs +++ b/src/apps/Elsa.Server.Web/Program.cs @@ -89,6 +89,7 @@ const bool useAgents = false; const bool useSecrets = false; const bool disableVariableWrappers = false; +const bool disableVariableCopying = true; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; @@ -355,6 +356,7 @@ { options.AllowClrAccess = true; options.DisableWrappers = disableVariableWrappers; + options.DisableVariableCopying = disableVariableCopying; options.RegisterType(); options.ConfigureEngine(engine => { diff --git a/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs b/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs index b12b9f4493..7219e79d55 100644 --- a/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs +++ b/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs @@ -24,12 +24,12 @@ public static class EngineExtensions internal static void SyncVariablesContainer(this Engine engine, IOptions options, string name, object? value) { - if (!options.Value.DisableWrappers) - { - // To ensure both variable accessor syntaxes work, we need to update the variables container in the engine as well as the context to keep them in sync. - var variablesContainer = (IDictionary)engine.GetValue("variables").ToObject()!; - variablesContainer[name] = ObjectConverterHelper.ProcessVariableValue(engine, value); - engine.SetValue("variables", variablesContainer); - } + if (options.Value.DisableWrappers || options.Value.DisableVariableCopying) + return; + + // To ensure both variable accessor syntaxes work, we need to update the variables container in the engine as well as the context to keep them in sync. + var variablesContainer = (IDictionary)engine.GetValue("variables").ToObject()!; + variablesContainer[name] = ObjectConverterHelper.ProcessVariableValue(engine, value); + engine.SetValue("variables", variablesContainer); } } \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariables.cs b/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariables.cs index 417c4f5eb6..3e1d4f6c97 100644 --- a/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariables.cs +++ b/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariables.cs @@ -1,4 +1,5 @@ using System.Dynamic; +using System.Text.RegularExpressions; using Elsa.Expressions.Models; using Elsa.Extensions; using Elsa.JavaScript.Extensions; @@ -8,7 +9,6 @@ using Elsa.Mediator.Contracts; using Elsa.Workflows.Activities; using JetBrains.Annotations; -using Jint; using Jint.Native; using Microsoft.Extensions.Options; @@ -18,12 +18,14 @@ namespace Elsa.JavaScript.Handlers; /// A handler that configures the Jint engine with workflow variables. /// [UsedImplicitly] -public class ConfigureEngineWithVariables(IOptions options) : INotificationHandler, INotificationHandler +public partial class ConfigureEngineWithVariables(IOptions options) : INotificationHandler, INotificationHandler { + private bool IsEnabled => options.Value is { DisableWrappers: false, DisableVariableCopying: false }; + /// public Task HandleAsync(EvaluatingJavaScript notification, CancellationToken cancellationToken) { - if (options.Value.DisableWrappers) + if (!IsEnabled) return Task.CompletedTask; CopyVariablesIntoEngine(notification); @@ -32,7 +34,7 @@ public Task HandleAsync(EvaluatingJavaScript notification, CancellationToken can public Task HandleAsync(EvaluatedJavaScript notification, CancellationToken cancellationToken) { - if (options.Value.DisableWrappers) + if (!IsEnabled) return Task.CompletedTask; CopyVariablesIntoWorkflowExecutionContext(notification); @@ -60,7 +62,8 @@ private void CopyVariablesIntoEngine(EvaluatingJavaScript notification) { var engine = notification.Engine; var context = notification.Context; - var variableNames = context.GetVariableNamesInScope().FilterInvalidVariableNames().ToList(); + var expression = notification.Expression; + var variableNames = GetUsedVariableNames(context, expression).ToList(); var variablesContainer = (IDictionary)new ExpandoObject(); foreach (var variableName in variableNames) @@ -73,6 +76,17 @@ private void CopyVariablesIntoEngine(EvaluatingJavaScript notification) engine.SetValue("variables", variablesContainer); } + private IEnumerable GetUsedVariableNames(ExpressionExecutionContext context, string expression) + { + var variableNames = context.GetVariableNamesInScope().FilterInvalidVariableNames(); + + var variableNamesInScript = ExtractVariableNamesRegex().Matches(expression) + .Select(m => m.Groups[1].Value) + .ToList(); + + return variableNames.Where(x => variableNamesInScript.Contains(x)); + } + private IEnumerable GetInputNames(ExpressionExecutionContext context) { var activityExecutionContext = context.TryGetActivityExecutionContext(out var aec) ? aec : null; @@ -95,4 +109,7 @@ private IEnumerable GetInputNames(ExpressionExecutionContext context) activityExecutionContext = activityExecutionContext.ParentActivityExecutionContext; } } + + [GeneratedRegex(@"variables\.(\w+)(?:\.\w+)*")] + private static partial Regex ExtractVariableNamesRegex(); } \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Options/JintOptions.cs b/src/modules/Elsa.JavaScript/Options/JintOptions.cs index 2cff888b74..ad611cc72c 100644 --- a/src/modules/Elsa.JavaScript/Options/JintOptions.cs +++ b/src/modules/Elsa.JavaScript/Options/JintOptions.cs @@ -47,6 +47,12 @@ public class JintOptions /// public bool DisableWrappers { get; set; } + /// + /// Disables copying workflow variables into the Jint engine and copying them back into the workflow execution context. + /// Disabling this option will increase performance but will also prevent you from accessing workflow variables from within JavaScript expressions using the variables.MyVariable syntax. + /// + public bool DisableVariableCopying { get; set; } + /// /// Configures the Jint engine options. /// diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs index a88e84498d..7382b84b99 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs @@ -143,7 +143,7 @@ public static Variable CreateVariable(this ExpressionExecutionContext context var existingVariable = context.GetVariable(name, localScopeOnly: true); if (existingVariable != null) - throw new Exception($"Variable {name} already exists in the context."); + throw new($"Variable {name} already exists in the context."); var variable = new Variable(name, value) { @@ -464,7 +464,7 @@ public static async IAsyncEnumerable GetActivityOutputs(this Ex foreach (var output in activityDescriptor.Outputs) { var outputPascalName = output.Name.Pascalize(); - yield return new ActivityOutputs(activity.Id, activityIdPascalName, [ + yield return new(activity.Id, activityIdPascalName, [ outputPascalName ]); } @@ -508,7 +508,7 @@ public static IEnumerable GetWorkflowInputs(this ExpressionExecut { var inputPascalName = inputEntry.Key.Pascalize(); var inputValue = inputEntry.Value; - yield return new WorkflowInput(inputPascalName, inputValue); + yield return new(inputPascalName, inputValue); } } else @@ -522,7 +522,7 @@ public static IEnumerable GetWorkflowInputs(this ExpressionExecut var variable = variableBlockMetadata.Variable; var variablePascalName = variable.Name.Pascalize(); - yield return new WorkflowInput(variablePascalName, block.Value); + yield return new(variablePascalName, block.Value); } } } From 3262e7493fe1d7e8b751291415d7ed0ad9eceece Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 21 Jan 2025 19:29:56 +0100 Subject: [PATCH 2/6] Enable variable copying in Elsa.Server.Web configuration Updated the `disableVariableCopying` flag to `false` in `Program.cs`. This change allows variables to be copied, potentially addressing scenarios where variable duplication is needed. Ensure to test for any side effects this might introduce. --- src/apps/Elsa.Server.Web/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs index 1e4778c0af..1f1c393d4b 100644 --- a/src/apps/Elsa.Server.Web/Program.cs +++ b/src/apps/Elsa.Server.Web/Program.cs @@ -89,7 +89,7 @@ const bool useAgents = false; const bool useSecrets = false; const bool disableVariableWrappers = false; -const bool disableVariableCopying = true; +const bool disableVariableCopying = false; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; From 33a2fb7afe9e2654c57a9d2a5780e870a8236c2a Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 21 Jan 2025 19:52:30 +0100 Subject: [PATCH 3/6] Optimize ActivityOutputRegister. Enhanced `ActivityOutputRegister` with dictionary-based lookups for improved performance and added unique key generation methods to efficiently retrieve outputs. --- .../Models/ActivityOutputRegister.cs | 29 +++++++++++++------ .../Models/PropertyDescriptor.cs | 8 ++--- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs b/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs index 8b120af83f..b6cd8b8fda 100644 --- a/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs +++ b/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs @@ -5,7 +5,9 @@ namespace Elsa.Workflows.Models; /// public class ActivityOutputRegister { - private readonly ICollection _records = new List(); + private readonly List _records = new(); + private readonly Dictionary _recordsByActivityIdAndOutputName = new(); + private readonly Dictionary _recordsByActivityInstanceIdAndOutputName = new(); /// /// The default output name. @@ -19,7 +21,7 @@ public class ActivityOutputRegister /// The output value. public void Record(ActivityExecutionContext activityExecutionContext, object? outputValue) { - Record(activityExecutionContext, default, outputValue); + Record(activityExecutionContext, null, outputValue); } /// @@ -39,13 +41,15 @@ public void Record(ActivityExecutionContext activityExecutionContext, string? ou // Inspect the output descriptor to see if the specified output name matches any PropertyInfo's name. // If so, use that descriptor's name instead. var outputDescriptor = activityExecutionContext.ActivityDescriptor.Outputs.FirstOrDefault(x => x.PropertyInfo?.Name == outputName); - + if (outputDescriptor != null) outputName = outputDescriptor.Name; var record = new ActivityOutputRecord(containerId, activityId, activityInstanceId, outputName, outputValue); _records.Add(record); + _recordsByActivityIdAndOutputName[CreateActivityIdLookupKey(activityId, outputName)] = record; + _recordsByActivityInstanceIdAndOutputName[CreateActivityInstanceIdLookupKey(activityInstanceId, outputName)] = record; } /// @@ -59,10 +63,12 @@ public void Record(ActivityExecutionContext activityExecutionContext, string? ou /// The activity ID. /// Name of the output. /// The output value. - public object? FindOutputByActivityId(string activityId, string? outputName = default) + public object? FindOutputByActivityId(string activityId, string? outputName = null) { - var record = _records.LastOrDefault(x => x.ActivityId == activityId && x.OutputName == (outputName ?? DefaultOutputName)); - return record?.Value; + var key = CreateActivityIdLookupKey(activityId, outputName ?? DefaultOutputName); + return !_recordsByActivityIdAndOutputName.TryGetValue(key, out var record) + ? null + : record.Value; } /// @@ -71,9 +77,14 @@ public void Record(ActivityExecutionContext activityExecutionContext, string? ou /// The activity instance ID. /// /// The output value. - public object? FindOutputByActivityInstanceId(string activityInstanceId, string? outputName = default) + public object? FindOutputByActivityInstanceId(string activityInstanceId, string? outputName = null) { - var record = _records.LastOrDefault(x => x.ActivityInstanceId == activityInstanceId && x.OutputName == (outputName ?? DefaultOutputName)); - return record?.Value; + var key = $"{activityInstanceId}:{outputName ?? DefaultOutputName}"; + return !_recordsByActivityInstanceIdAndOutputName.TryGetValue(key, out var record) + ? null + : record.Value; } + + private string CreateActivityIdLookupKey(string activityId, string outputName) => $"{activityId}:{outputName}"; + private string CreateActivityInstanceIdLookupKey(string activityInstanceId, string? outputName) => $"{activityInstanceId}:{outputName ?? DefaultOutputName}"; } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Models/PropertyDescriptor.cs b/src/modules/Elsa.Workflows.Core/Models/PropertyDescriptor.cs index dc6e8ec546..24ed114b93 100644 --- a/src/modules/Elsa.Workflows.Core/Models/PropertyDescriptor.cs +++ b/src/modules/Elsa.Workflows.Core/Models/PropertyDescriptor.cs @@ -11,13 +11,13 @@ public abstract class PropertyDescriptor /// /// The name. /// - public string Name { get; set; } = default!; + public string Name { get; set; } = null!; /// /// The .NET type. /// [JsonPropertyName("typeName")] - public Type Type { get; set; } = default!; + public Type Type { get; set; } = null!; /// /// The user friendly name of the input. Used by UI tools. @@ -53,13 +53,13 @@ public abstract class PropertyDescriptor /// Returns the value of the input property for the specified activity. /// [JsonIgnore] - public Func ValueGetter { get; set; } = default!; + public Func ValueGetter { get; set; } = null!; /// /// Sets the value of the input property for the specified activity. /// [JsonIgnore] - public Action ValueSetter { get; set; } = default!; + public Action ValueSetter { get; set; } = null!; /// /// The source of the property, if any. From dd39ca8d847912ad789b01e9c1de8bf4569f7e17 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 21 Jan 2025 20:10:54 +0100 Subject: [PATCH 4/6] Refactor variable handling and activity output registration. Replaced default value assignments with null for clarity and simplicity. Refactored ActivityOutputRegister to optimize record storage and retrieval using grouped dictionary entries instead of flat lists. Adjusted related methods to improve performance and maintain consistency. --- .../ExpressionExecutionContextExtensions.cs | 16 ++++----- .../Models/ActivityOutputRegister.cs | 35 ++++++++++++------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs index 7382b84b99..70f5381d2a 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs @@ -119,7 +119,7 @@ public static IDictionary CreateTriggerIndexingPropertiesFrom(Wo public static Variable? GetVariable(this ExpressionExecutionContext context, string name, bool localScopeOnly = false) { var block = context.GetVariableBlock(name, localScopeOnly); - return block?.Metadata is VariableBlockMetadata metadata ? metadata.Variable : default; + return block?.Metadata is VariableBlockMetadata metadata ? metadata.Variable : null; } private static MemoryBlock? GetVariableBlock(this ExpressionExecutionContext context, string name, bool localScopeOnly = false) @@ -138,7 +138,7 @@ public static IDictionary CreateTriggerIndexingPropertiesFrom(Wo /// Creates a named variable in the context. /// public static Variable CreateVariable(this ExpressionExecutionContext context, string name, T? value, Type? storageDriverType = null, - Action? configure = default) + Action? configure = null) { var existingVariable = context.GetVariable(name, localScopeOnly: true); @@ -173,7 +173,7 @@ public static ExpressionExecutionContext GetVariableContainerContext(this Expres /// /// Sets the value of a named variable in the context. /// - public static Variable SetVariable(this ExpressionExecutionContext context, string name, T? value, Action? configure = default) + public static Variable SetVariable(this ExpressionExecutionContext context, string name, T? value, Action? configure = null) { var variable = context.GetVariable(name); @@ -193,7 +193,7 @@ public static Variable SetVariable(this ExpressionExecutionContext context, s /// /// Sets the output to the specified value. /// - public static void Set(this ExpressionExecutionContext context, Output? output, object? value, Action? configure = default) + public static void Set(this ExpressionExecutionContext context, Output? output, object? value, Action? configure = null) { if (output != null) { @@ -412,11 +412,11 @@ private static JsonSerializerOptions GetSerializerOptions(ExpressionExecutionCon // Otherwise, return the input. var workflowExecutionContext = context.GetWorkflowExecutionContext(); var input = workflowExecutionContext.Input; - return input.TryGetValue(name, out var value) ? value : default; + return input.TryGetValue(name, out var value) ? value : null; } /// - /// Returns the value of the specified input. + /// Returns the value of the specified output. /// /// /// The ID or name of the activity. @@ -433,9 +433,9 @@ private static JsonSerializerOptions GetSerializerOptions(ExpressionExecutionCon throw new InvalidOperationException("Activity not found."); var outputRegister = workflowExecutionContext.GetActivityOutputRegister(); - var outputRecordCandidates = outputRegister.FindMany(x => x.ActivityId == activity.Id && x.OutputName == outputName).ToList(); + var outputRecordCandidates = outputRegister.FindMany(activity.Id, outputName); var containerIds = activityExecutionContext.GetAncestors().Select(x => x.Id).ToList(); - var filteredOutputRecordCandidates = outputRecordCandidates.Where(x => containerIds.Contains(x.ContainerId)).ToList(); + var filteredOutputRecordCandidates = outputRecordCandidates.Where(x => containerIds.Contains(x.ContainerId)); var outputRecord = filteredOutputRecordCandidates.FirstOrDefault(); return outputRecord?.Value; } diff --git a/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs b/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs index b6cd8b8fda..5a3be3937e 100644 --- a/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs +++ b/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs @@ -5,8 +5,7 @@ namespace Elsa.Workflows.Models; /// public class ActivityOutputRegister { - private readonly List _records = new(); - private readonly Dictionary _recordsByActivityIdAndOutputName = new(); + private readonly Dictionary> _recordsByActivityIdAndOutputName = new(); private readonly Dictionary _recordsByActivityInstanceIdAndOutputName = new(); /// @@ -46,16 +45,28 @@ public void Record(ActivityExecutionContext activityExecutionContext, string? ou outputName = outputDescriptor.Name; var record = new ActivityOutputRecord(containerId, activityId, activityInstanceId, outputName, outputValue); - - _records.Add(record); - _recordsByActivityIdAndOutputName[CreateActivityIdLookupKey(activityId, outputName)] = record; + _recordsByActivityInstanceIdAndOutputName[CreateActivityInstanceIdLookupKey(activityInstanceId, outputName)] = record; + + var scopedRecordsKey = CreateActivityIdLookupKey(activityId, outputName); + + if(!_recordsByActivityIdAndOutputName.TryGetValue(scopedRecordsKey, out var scopedRecords)) + { + scopedRecords = new(); + _recordsByActivityIdAndOutputName[scopedRecordsKey] = scopedRecords; + } + + scopedRecords.Add(record); } /// - /// Finds all output records matching the specified predicate. + /// Finds all output records for the specified activity ID and output name. /// - public IEnumerable FindMany(Func predicate) => _records.Where(predicate); + public IEnumerable FindMany(string activityId, string? outputName = null) + { + var key = CreateActivityIdLookupKey(activityId, outputName); + return _recordsByActivityIdAndOutputName.TryGetValue(key, out var records) ? records : Enumerable.Empty(); + } /// /// Gets the output value for the specified activity ID. @@ -65,10 +76,10 @@ public void Record(ActivityExecutionContext activityExecutionContext, string? ou /// The output value. public object? FindOutputByActivityId(string activityId, string? outputName = null) { - var key = CreateActivityIdLookupKey(activityId, outputName ?? DefaultOutputName); - return !_recordsByActivityIdAndOutputName.TryGetValue(key, out var record) + var key = CreateActivityIdLookupKey(activityId, outputName); + return !_recordsByActivityIdAndOutputName.TryGetValue(key, out var records) ? null - : record.Value; + : records.FirstOrDefault(); } /// @@ -79,12 +90,12 @@ public void Record(ActivityExecutionContext activityExecutionContext, string? ou /// The output value. public object? FindOutputByActivityInstanceId(string activityInstanceId, string? outputName = null) { - var key = $"{activityInstanceId}:{outputName ?? DefaultOutputName}"; + var key = CreateActivityInstanceIdLookupKey(activityInstanceId, outputName); return !_recordsByActivityInstanceIdAndOutputName.TryGetValue(key, out var record) ? null : record.Value; } - private string CreateActivityIdLookupKey(string activityId, string outputName) => $"{activityId}:{outputName}"; + private string CreateActivityIdLookupKey(string activityId, string? outputName) => $"{activityId}:{outputName ?? DefaultOutputName}"; private string CreateActivityInstanceIdLookupKey(string activityInstanceId, string? outputName) => $"{activityInstanceId}:{outputName ?? DefaultOutputName}"; } \ No newline at end of file From 1ca8f2a4237d8ae6ab038204170a6dda3c36315e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 21 Jan 2025 22:53:08 +0100 Subject: [PATCH 5/6] Fix null reference issue in ActivityOutputRegister. Updated the method to safely access the `Value` property when retrieving the first record, preventing potential null reference exceptions. This ensures more robust and error-free behavior when querying output records. --- .../Elsa.Workflows.Core/Models/ActivityOutputRegister.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs b/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs index 5a3be3937e..d4845cfead 100644 --- a/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs +++ b/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs @@ -79,7 +79,7 @@ public IEnumerable FindMany(string activityId, string? out var key = CreateActivityIdLookupKey(activityId, outputName); return !_recordsByActivityIdAndOutputName.TryGetValue(key, out var records) ? null - : records.FirstOrDefault(); + : records.FirstOrDefault()?.Value; } /// From 0bb183f1aa1e227ece3cef5f8ab98347c106a8b3 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 21 Jan 2025 22:57:19 +0100 Subject: [PATCH 6/6] Fix spacing inconsistencies in ActivityOutputRegister.cs Resolved unnecessary whitespace issues and adjusted spacing around conditional statements to improve code readability and maintain consistency. These changes do not alter functionality but enhance the code's clarity and professional formatting. --- .../Models/ActivityOutputRegister.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs b/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs index d4845cfead..ac72cf660b 100644 --- a/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs +++ b/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs @@ -45,17 +45,17 @@ public void Record(ActivityExecutionContext activityExecutionContext, string? ou outputName = outputDescriptor.Name; var record = new ActivityOutputRecord(containerId, activityId, activityInstanceId, outputName, outputValue); - + _recordsByActivityInstanceIdAndOutputName[CreateActivityInstanceIdLookupKey(activityInstanceId, outputName)] = record; - + var scopedRecordsKey = CreateActivityIdLookupKey(activityId, outputName); - if(!_recordsByActivityIdAndOutputName.TryGetValue(scopedRecordsKey, out var scopedRecords)) + if (!_recordsByActivityIdAndOutputName.TryGetValue(scopedRecordsKey, out var scopedRecords)) { scopedRecords = new(); _recordsByActivityIdAndOutputName[scopedRecordsKey] = scopedRecords; } - + scopedRecords.Add(record); } @@ -77,8 +77,8 @@ public IEnumerable FindMany(string activityId, string? out public object? FindOutputByActivityId(string activityId, string? outputName = null) { var key = CreateActivityIdLookupKey(activityId, outputName); - return !_recordsByActivityIdAndOutputName.TryGetValue(key, out var records) - ? null + return !_recordsByActivityIdAndOutputName.TryGetValue(key, out var records) + ? null : records.FirstOrDefault()?.Value; } @@ -91,11 +91,11 @@ public IEnumerable FindMany(string activityId, string? out public object? FindOutputByActivityInstanceId(string activityInstanceId, string? outputName = null) { var key = CreateActivityInstanceIdLookupKey(activityInstanceId, outputName); - return !_recordsByActivityInstanceIdAndOutputName.TryGetValue(key, out var record) - ? null + return !_recordsByActivityInstanceIdAndOutputName.TryGetValue(key, out var record) + ? null : record.Value; } - + private string CreateActivityIdLookupKey(string activityId, string? outputName) => $"{activityId}:{outputName ?? DefaultOutputName}"; private string CreateActivityInstanceIdLookupKey(string activityInstanceId, string? outputName) => $"{activityInstanceId}:{outputName ?? DefaultOutputName}"; } \ No newline at end of file