diff --git a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxStore.cs b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxStore.cs index 54226593c..5c5d8097f 100644 --- a/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxStore.cs +++ b/src/modules/Elsa.Dapper/Modules/Runtime/Stores/DapperWorkflowInboxStore.cs @@ -43,23 +43,42 @@ public class DapperWorkflowInboxStore : IWorkflowInboxStore var records = await _store.FindManyAsync(q => ApplyFilter(q, filter), cancellationToken); return Map(records); } - + + /// + public async ValueTask> FindManyAsync(IEnumerable filters, CancellationToken cancellationToken = default) + { + var records = await _store.FindManyAsync(q => ApplyFilter(q, filters.ToArray()), cancellationToken); + return Map(records); + } + /// public async ValueTask DeleteAsync(WorkflowInboxMessageFilter filter, CancellationToken cancellationToken = default) { return await _store.DeleteAsync(q => ApplyFilter(q, filter), cancellationToken); } - private void ApplyFilter(ParameterizedQuery query, WorkflowInboxMessageFilter filter) + private void ApplyFilter(ParameterizedQuery query, params WorkflowInboxMessageFilter[] filters) { - query - .Is(nameof(WorkflowInboxMessageRecord.Hash), filter.Hash) - .Is(nameof(WorkflowInboxMessageRecord.WorkflowInstanceId), filter.WorkflowInstanceId) - .Is(nameof(WorkflowInboxMessageRecord.CorrelationId), filter.CorrelationId) - .Is(nameof(WorkflowInboxMessageRecord.ActivityTypeName), filter.ActivityTypeName) - .Is(nameof(WorkflowInboxMessageRecord.ActivityInstanceId), filter.ActivityInstanceId) - .Is(nameof(WorkflowInboxMessageRecord.IsHandled), filter.IsHandled) - ; + var clauses = new List(); + + foreach (var filter in filters) + { + var clause = new ParameterizedQuery(query.Dialect); + + clause + .Is(nameof(WorkflowInboxMessageRecord.Hash), filter.Hash) + .Is(nameof(WorkflowInboxMessageRecord.WorkflowInstanceId), filter.WorkflowInstanceId) + .Is(nameof(WorkflowInboxMessageRecord.CorrelationId), filter.CorrelationId) + .Is(nameof(WorkflowInboxMessageRecord.ActivityTypeName), filter.ActivityTypeName) + .Is(nameof(WorkflowInboxMessageRecord.ActivityInstanceId), filter.ActivityInstanceId) + .Is(nameof(WorkflowInboxMessageRecord.IsHandled), filter.IsHandled) + ; + + clauses.Add(clause); + } + + var clausesSql = string.Join(" OR ", $"({clauses.Select(x => x.Sql)})"); + query.Sql.AppendLine(clausesSql); } private IEnumerable Map(IEnumerable source) => source.Select(Map); diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowInboxStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowInboxStore.cs index dea7fe9df..18f0c192d 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowInboxStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowInboxStore.cs @@ -30,6 +30,16 @@ public class EFCoreWorkflowInboxStore : IWorkflowInboxStore /// public async ValueTask> FindManyAsync(WorkflowInboxMessageFilter filter, CancellationToken cancellationToken = default) => await _store.QueryAsync(filter.Apply, LoadAsync, cancellationToken); + /// + public async ValueTask> FindManyAsync(IEnumerable filters, CancellationToken cancellationToken = default) + { + return await _store.QueryAsync(query => + { + foreach (var filter in filters) filter.Apply(query); + return query; + }, LoadAsync, cancellationToken); + } + /// public async ValueTask DeleteAsync(WorkflowInboxMessageFilter filter, CancellationToken cancellationToken = default) => await _store.DeleteWhereAsync(filter.Apply, cancellationToken); diff --git a/src/modules/Elsa.MongoDb/Modules/Runtime/WorkflowInboxStore.cs b/src/modules/Elsa.MongoDb/Modules/Runtime/WorkflowInboxStore.cs index 7945c506a..dda0b3b7c 100644 --- a/src/modules/Elsa.MongoDb/Modules/Runtime/WorkflowInboxStore.cs +++ b/src/modules/Elsa.MongoDb/Modules/Runtime/WorkflowInboxStore.cs @@ -22,17 +22,28 @@ public class MongoWorkflowInboxStore : IWorkflowInboxStore } /// - public async ValueTask SaveAsync(WorkflowInboxMessage record, CancellationToken cancellationToken = default) => + public async ValueTask SaveAsync(WorkflowInboxMessage record, CancellationToken cancellationToken = default) => await _mongoDbStore.SaveAsync(record, s => s.Id, cancellationToken); /// - public async ValueTask> FindManyAsync(WorkflowInboxMessageFilter filter, CancellationToken cancellationToken = default) => - (await _mongoDbStore.FindManyAsync(query => Filter(query, filter), cancellationToken)); + public async ValueTask> FindManyAsync(WorkflowInboxMessageFilter filter, CancellationToken cancellationToken = default) + { + return await _mongoDbStore.FindManyAsync(query => Filter(query, filter), cancellationToken); + } /// - public async ValueTask DeleteAsync(WorkflowInboxMessageFilter filter, CancellationToken cancellationToken = default) => + public async ValueTask> FindManyAsync(IEnumerable filters, CancellationToken cancellationToken = default) + { + return await _mongoDbStore.FindManyAsync(query => Filter(query, filters.ToArray()), cancellationToken); + } + + /// + public async ValueTask DeleteAsync(WorkflowInboxMessageFilter filter, CancellationToken cancellationToken = default) => await _mongoDbStore.DeleteWhereAsync(query => Filter(query, filter), x => x.Id, cancellationToken); - private IMongoQueryable Filter(IMongoQueryable queryable, WorkflowInboxMessageFilter filter) => - (filter.Apply(queryable) as IMongoQueryable)!; + private static IMongoQueryable Filter(IMongoQueryable queryable, params WorkflowInboxMessageFilter[] filters) + { + foreach (var filter in filters) filter.Apply(queryable); + return queryable; + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Activities/ForEach.cs b/src/modules/Elsa.Workflows.Core/Activities/ForEach.cs index ac31950dc..92895095c 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/ForEach.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/ForEach.cs @@ -1,10 +1,4 @@ -using System.Runtime.CompilerServices; -using Elsa.Expressions.Models; -using Elsa.Extensions; using Elsa.Workflows.Core.Attributes; -using Elsa.Workflows.Core.Behaviors; -using Elsa.Workflows.Core.Contracts; -using Elsa.Workflows.Core.Models; using JetBrains.Annotations; namespace Elsa.Workflows.Core.Activities; @@ -13,123 +7,6 @@ namespace Elsa.Workflows.Core.Activities; /// Iterate over a set of values. /// [Activity("Elsa", "Looping", "Iterate over a set of values.")] -[PublicAPI] -public class ForEach : Activity +public class ForEach : ForEach { - private const string CurrentIndexProperty = "CurrentIndex"; - - /// - public ForEach([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) - { - Behaviors.Add(this); - } - - /// - public ForEach(ICollection items, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) - { - Items = new Input>(items); - } - - /// - /// The set of values to iterate. - /// - [Input(Description = "The set of values to iterate.")] - public Input> Items { get; set; } = new(Array.Empty()); - - /// - /// The activity to execute for each iteration. - /// - [Port] - public IActivity? Body { get; set; } - - /// - /// The current value being iterated will be assigned to the specified . - /// - [Output(Description = "Assign the current value to the specified variable.")] - public Output? CurrentValue { get; set; } - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - // Execute first iteration. - await HandleIteration(context); - } - - private async Task HandleIteration(ActivityExecutionContext context) - { - var currentIndex = context.GetProperty(CurrentIndexProperty); - var items = context.Get(Items)!.ToList(); - - if (currentIndex >= items.Count) - { - await context.CompleteActivityAsync(); - return; - } - - var currentItem = items[currentIndex]; - context.Set(CurrentValue, currentItem); - - if (Body != null) - await context.ScheduleActivityAsync(Body, OnChildCompleted); - else - await context.CompleteActivityAsync(); - - // Increment index. - context.UpdateProperty(CurrentIndexProperty, x => x + 1); - } - - private async ValueTask OnChildCompleted(ActivityExecutionContext context, ActivityExecutionContext childContext) - { - await HandleIteration(context); - } -} - -/// -/// A strongly-typed for-each construct where is the item type. -/// -[PublicAPI] -public class ForEach : ForEach -{ - /// - public ForEach([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - public ForEach(Input> items) : this() - { - Items = items; - } - - /// - public ForEach(ICollection items) : this(new Input>(items)) - { - } - - /// - public ForEach(Func> items) : this(new Input>(items)) - { - } - - /// - /// The items to iterate on. - /// - [Input] - public new Input> Items - { - get => new(base.Items.Expression, base.Items.MemoryBlockReference()); - set => base.Items = new Input>(value.Expression, value.MemoryBlockReference()); - } - - /// - /// Provides access to the current value. - /// - public new Output CurrentValue - { - get => - base.CurrentValue != null - ? new(base.CurrentValue.MemoryBlockReference) - : new(); - set => base.CurrentValue = new Output(value.MemoryBlockReference); - } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Activities/ForEachT.cs b/src/modules/Elsa.Workflows.Core/Activities/ForEachT.cs new file mode 100644 index 000000000..5d283f180 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/Activities/ForEachT.cs @@ -0,0 +1,105 @@ +using System.Runtime.CompilerServices; +using Elsa.Expressions.Models; +using Elsa.Extensions; +using Elsa.Workflows.Core.Attributes; +using Elsa.Workflows.Core.Behaviors; +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Core.Memory; +using Elsa.Workflows.Core.Models; + +namespace Elsa.Workflows.Core.Activities; + +/// +/// A strongly-typed for-each construct where is the item type. +/// +public class ForEach : Activity +{ + private const string CurrentIndexProperty = "CurrentIndex"; + + /// + public ForEach(Func> @delegate, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(new Input>(@delegate), source, line) + { + } + + /// + public ForEach(Func> @delegate, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(new Input>(@delegate), source, line) + { + } + + /// + public ForEach(ICollection items, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(new Input>(items), source, line) + { + } + + /// + public ForEach(Input> items, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) + { + Items = items; + } + + /// + public ForEach([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) + { + Behaviors.Add(this); + } + + /// + /// The set of values to iterate. + /// + [Input(Description = "The set of values to iterate.")] + public Input> Items { get; set; } = new(Array.Empty()); + + /// + /// The activity to execute for each iteration. + /// + [Port] + public IActivity? Body { get; set; } + + /// + /// The current value being iterated will be assigned to the specified . + /// + [Output(Description = "Assign the current value to the specified variable.")] + public Output? CurrentValue { get; set; } + + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + // Execute first iteration. + await HandleIteration(context); + } + + private async Task HandleIteration(ActivityExecutionContext context) + { + var currentIndex = context.GetProperty(CurrentIndexProperty); + var items = context.Get(Items)!.ToList(); + + if (currentIndex >= items.Count) + { + await context.CompleteActivityAsync(); + return; + } + + var currentValue = items[currentIndex]; + context.Set(CurrentValue, currentValue); + + if (Body != null) + { + var variables = new[] + { + new Variable("CurrentIndex", currentIndex), + new Variable("CurrentValue", currentValue) + }; + await context.ScheduleActivityAsync(Body, OnChildCompleted, variables: variables); + } + else + await context.CompleteActivityAsync(); + + // Increment index. + context.UpdateProperty(CurrentIndexProperty, x => x + 1); + } + + private async ValueTask OnChildCompleted(ActivityExecutionContext context, ActivityExecutionContext childContext) + { + await HandleIteration(context); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs b/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs index ccd795fbe..0e08e5f3d 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs @@ -45,7 +45,7 @@ public class ParallelForEach : Activity foreach (var item in items) { // For each item, declare a new variable the work to be scheduled. - var variable = new Variable("CurrentItem", item) + var variable = new Variable("CurrentValue", item) { // TODO: This should be configurable, because this won't work for e.g. file streams and other non-serializable types. StorageDriverType = typeof(WorkflowStorageDriver) diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs index 3e8478a9f..c4c15d490 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs @@ -314,7 +314,8 @@ public class ActivityExecutionContext : IExecutionContext var bookmarkName = options?.BookmarkName ?? Activity.Type; var bookmarkHasher = GetRequiredService(); var identityGenerator = GetRequiredService(); - var hash = bookmarkHasher.Hash(bookmarkName, payload); + var includeActivityInstanceId = options?.IncludeActivityInstanceId ?? true; + var hash = bookmarkHasher.Hash(bookmarkName, payload, includeActivityInstanceId ? Id : null); var bookmark = new Bookmark( identityGenerator.GenerateId(), diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IBookmarkHasher.cs b/src/modules/Elsa.Workflows.Core/Contracts/IBookmarkHasher.cs index 6fc9e6613..981a8d538 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IBookmarkHasher.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IBookmarkHasher.cs @@ -6,7 +6,7 @@ namespace Elsa.Workflows.Core.Contracts; public interface IBookmarkHasher { /// - /// Produces a hash from the specified activity type name and bookmark payload. + /// Produces a hash from the specified activity type name, bookmark payload and activity instance ID. /// - string Hash(string activityTypeName, object? payload); + string Hash(string activityTypeName, object? payload, string? activityInstanceId = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs index 180154af8..d95038b81 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs @@ -71,7 +71,7 @@ public static class WorkflowExecutionContextExtensions /// public static void ScheduleBookmark(this WorkflowExecutionContext workflowExecutionContext, Bookmark bookmark) { - // Construct bookmark. + // Get the activity execution context that owns the bookmark. var bookmarkedActivityContext = workflowExecutionContext.ActiveActivityExecutionContexts.FirstOrDefault(x => x.Id == bookmark.ActivityInstanceId); if(bookmarkedActivityContext == null) diff --git a/src/modules/Elsa.Workflows.Core/Models/BookmarkOptions.cs b/src/modules/Elsa.Workflows.Core/Models/BookmarkOptions.cs index 18d16e7ad..40f73f437 100644 --- a/src/modules/Elsa.Workflows.Core/Models/BookmarkOptions.cs +++ b/src/modules/Elsa.Workflows.Core/Models/BookmarkOptions.cs @@ -2,4 +2,12 @@ using Elsa.Workflows.Core.Services; namespace Elsa.Workflows.Core.Models; -public record BookmarkOptions(object? Payload = default, ExecuteActivityDelegate? Callback = default, string? BookmarkName = default, bool AutoBurn = true); \ No newline at end of file +/// +/// Provides bookmark creation options. +/// +/// An optional payload to associate with the bookmark. +/// An optional callback to invoke when the bookmark is triggered. +/// An optional name to associate with the bookmark. +/// Whether or not the bookmark should be automatically burned when triggered. +/// Whether or not the activity instance ID should be included in the bookmark payload. +public record BookmarkOptions(object? Payload = default, ExecuteActivityDelegate? Callback = default, string? BookmarkName = default, bool AutoBurn = true, bool IncludeActivityInstanceId = true); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Services/BookmarkHasher.cs b/src/modules/Elsa.Workflows.Core/Services/BookmarkHasher.cs index f9abc4020..46ef5db34 100644 --- a/src/modules/Elsa.Workflows.Core/Services/BookmarkHasher.cs +++ b/src/modules/Elsa.Workflows.Core/Services/BookmarkHasher.cs @@ -1,3 +1,4 @@ +using System.Text; using System.Text.Json; using Elsa.Expressions.Contracts; using Elsa.Workflows.Core.Contracts; @@ -30,10 +31,18 @@ public class BookmarkHasher : IBookmarkHasher } /// - public string Hash(string activityTypeName, object? payload) + public string Hash(string activityTypeName, object? payload, string? activityInstanceId = default) { var json = payload != null ? Serialize(payload) : null; - var input = $"{activityTypeName}{(!string.IsNullOrWhiteSpace(json) ? ":" + json : "")}"; + var inputSource = new List { activityTypeName}; + + if (!string.IsNullOrWhiteSpace(json)) + inputSource.Add(json); + + if (!string.IsNullOrWhiteSpace(activityInstanceId)) + inputSource.Add(activityInstanceId); + + var input = string.Join("|", inputSource); var hash = _hasher.Hash(input); return hash; diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/Event.cs b/src/modules/Elsa.Workflows.Runtime/Activities/Event.cs index 082dc7bb0..56225a0c2 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/Event.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/Event.cs @@ -75,7 +75,12 @@ public class Event : Trigger if (!context.IsTriggerOfWorkflow()) { - context.CreateBookmark(new EventBookmarkPayload(eventName)); + var options = new BookmarkOptions + { + Payload = new EventBookmarkPayload(eventName), + IncludeActivityInstanceId = false + }; + context.CreateBookmark(options); return; } diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInbox.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInbox.cs index f12f5919b..ddb26be5a 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInbox.cs +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInbox.cs @@ -40,4 +40,12 @@ public interface IWorkflowInbox /// An optional cancellation token. /// A list of messages that match the filter. ValueTask> FindManyAsync(WorkflowInboxMessageFilter filter, CancellationToken cancellationToken = default); + + /// + /// Finds all messages matching the specified filter. + /// + /// The filters to apply. + /// An optional cancellation token. + /// A list of messages that match the filter. + ValueTask> FindManyAsync(IEnumerable filters, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInboxStore.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInboxStore.cs index 6aeaa1615..ae4fa5319 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInboxStore.cs +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowInboxStore.cs @@ -24,6 +24,14 @@ public interface IWorkflowInboxStore /// An optional cancellation token. /// A list of messages that match the filter. ValueTask> FindManyAsync(WorkflowInboxMessageFilter filter, CancellationToken cancellationToken = default); + + /// + /// Finds all messages matching the specified filters. + /// + /// The filters to apply. + /// An optional cancellation token. + /// A list of messages that match the filter. + ValueTask> FindManyAsync(IEnumerable filters, CancellationToken cancellationToken = default); /// /// Deletes all messages matching the specified filter. diff --git a/src/modules/Elsa.Workflows.Runtime/Handlers/DeliverWorkflowMessagesFromInbox.cs b/src/modules/Elsa.Workflows.Runtime/Handlers/DeliverWorkflowMessagesFromInbox.cs index 931169db5..024260604 100644 --- a/src/modules/Elsa.Workflows.Runtime/Handlers/DeliverWorkflowMessagesFromInbox.cs +++ b/src/modules/Elsa.Workflows.Runtime/Handlers/DeliverWorkflowMessagesFromInbox.cs @@ -28,12 +28,10 @@ public class DeliverWorkflowMessagesFromInbox : INotificationHandler + public async ValueTask> FindManyAsync(IEnumerable filters, CancellationToken cancellationToken = default) + { + return await _store.FindManyAsync(filters, cancellationToken); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Stores/MemoryWorkflowInboxStore.cs b/src/modules/Elsa.Workflows.Runtime/Stores/MemoryWorkflowInboxStore.cs index cde5839db..82f1d8f20 100644 --- a/src/modules/Elsa.Workflows.Runtime/Stores/MemoryWorkflowInboxStore.cs +++ b/src/modules/Elsa.Workflows.Runtime/Stores/MemoryWorkflowInboxStore.cs @@ -34,6 +34,13 @@ public class MemoryWorkflowInboxStore : IWorkflowInboxStore return new(entities); } + /// + public ValueTask> FindManyAsync(IEnumerable filters, CancellationToken cancellationToken = default) + { + var entities = _store.Query(query => Filter(query, filters.ToArray())).ToList(); + return new(entities); + } + /// public async ValueTask DeleteAsync(WorkflowInboxMessageFilter filter, CancellationToken cancellationToken = default) { @@ -41,5 +48,9 @@ public class MemoryWorkflowInboxStore : IWorkflowInboxStore return _store.DeleteMany(ids); } - private static IQueryable Filter(IQueryable query, WorkflowInboxMessageFilter filter) => filter.Apply(query); + private static IQueryable Filter(IQueryable query, params WorkflowInboxMessageFilter[] filters) + { + foreach (var filter in filters) filter.Apply(query); + return query; + } } \ No newline at end of file diff --git a/src/samples/console/Elsa.Samples.ConsoleApp.LoopingWorkflows/Workflows/ForEachWorkflow.cs b/src/samples/console/Elsa.Samples.ConsoleApp.LoopingWorkflows/Workflows/ForEachWorkflow.cs index a15e9747f..97d5ac22f 100644 --- a/src/samples/console/Elsa.Samples.ConsoleApp.LoopingWorkflows/Workflows/ForEachWorkflow.cs +++ b/src/samples/console/Elsa.Samples.ConsoleApp.LoopingWorkflows/Workflows/ForEachWorkflow.cs @@ -19,9 +19,9 @@ public static class ForEachWorkflow Activities = { new WriteLine("Going through the shopping list..."), - new ForEach(shoppingList) + new ForEach(shoppingList) { - CurrentValue = new Output(currentValueVariable), + CurrentValue = new Output(currentValueVariable), Body = new Sequence { Activities =