diff --git a/Elsa.sln b/Elsa.sln index 5af513753..f873a4d89 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -308,6 +308,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Studio.Web", "src\bund EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.AspNet.CustomUIHandler", "src\samples\aspnet\Elsa.Samples.AspNet.CustomUIHandler\Elsa.Samples.AspNet.CustomUIHandler.csproj", "{832D6FF5-16D3-41D1-8FDD-D9BEC26BDA6A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.BatchProcessing", "src\samples\aspnet\Elsa.Samples.AspNet.BatchProcessing\Elsa.Samples.AspNet.BatchProcessing.csproj", "{0AAF5EF6-02E5-44F9-B2CB-B1401FC5EF66}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -750,6 +752,10 @@ Global {832D6FF5-16D3-41D1-8FDD-D9BEC26BDA6A}.Debug|Any CPU.Build.0 = Debug|Any CPU {832D6FF5-16D3-41D1-8FDD-D9BEC26BDA6A}.Release|Any CPU.ActiveCfg = Release|Any CPU {832D6FF5-16D3-41D1-8FDD-D9BEC26BDA6A}.Release|Any CPU.Build.0 = Release|Any CPU + {0AAF5EF6-02E5-44F9-B2CB-B1401FC5EF66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0AAF5EF6-02E5-44F9-B2CB-B1401FC5EF66}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0AAF5EF6-02E5-44F9-B2CB-B1401FC5EF66}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0AAF5EF6-02E5-44F9-B2CB-B1401FC5EF66}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -884,6 +890,7 @@ Global {26888832-DBEA-4B23-8DC2-84478A702CC4} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} {D5C149EE-276C-4C59-98F9-37F0EA2A7866} = {F06B9573-DF68-4606-866C-A7546A10A05A} {832D6FF5-16D3-41D1-8FDD-D9BEC26BDA6A} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} + {0AAF5EF6-02E5-44F9-B2CB-B1401FC5EF66} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E} diff --git a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs index 53adfb070..4c56e5fe5 100644 --- a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs +++ b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs @@ -116,10 +116,14 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator if (context.IsInsideCompositeActivity()) return; - var inputs = context.GetWorkflowInputs(); + var inputs = context.GetWorkflowInputs().ToDictionary(x => x.Name); + var inputDefinitions = context.GetWorkflowExecutionContext().Workflow.Inputs; - foreach (var input in inputs) - engine.SetValue($"get{input.Name}", (Func)(() => input.Value)); + foreach (var inputDefinition in inputDefinitions) + { + var input = inputs.GetValueOrDefault(inputDefinition.Name); + engine.SetValue($"get{inputDefinition.Name}", (Func)(() => input?.Value)); + } } private static void CreateVariableAccessors(Engine engine, ExpressionExecutionContext context) diff --git a/src/modules/Elsa.Workflows.Core/Activities/ForEachT.cs b/src/modules/Elsa.Workflows.Core/Activities/ForEachT.cs index c954f50b7..1ef5e49ea 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/ForEachT.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/ForEachT.cs @@ -47,7 +47,13 @@ public class ForEach : Activity /// The set of values to iterate. /// [Input(Description = "The set of values to iterate.")] - public Input> Items { get; set; } = new(Array.Empty()); + public Input>? Items { get; set; } + + /// + /// The source of values to iterate. + /// + [Input(Description = "The set of values to iterate.")] + public Input>? ItemSource { get; set; } /// /// The activity to execute for each iteration. @@ -79,15 +85,16 @@ public class ForEach : Activity } var currentIndex = context.GetProperty(CurrentIndexProperty); - var items = context.Get(Items)!.ToList(); + var currentValueTuple = await GetCurrentValueAsync(context, currentIndex); - if (currentIndex >= items.Count) + if (!currentValueTuple.Exists) { await context.CompleteActivityAsync(); return; } - - var currentValue = items[currentIndex]; + + var currentValue = currentValueTuple.Value; + context.Set(CurrentValue, currentValue); if (Body != null) @@ -106,6 +113,36 @@ public class ForEach : Activity context.UpdateProperty(CurrentIndexProperty, x => x + 1); } + private async Task<(T Value, bool Exists)> GetCurrentValueAsync(ActivityExecutionContext context, int currentIndex) + { + var items = context.Get(Items)?.ToList(); + + if (items != null) + { + return (currentIndex >= items.Count ? (default, false) : (items[currentIndex], true))!; + } + + var itemSource = context.Get(ItemSource); + + if(itemSource != null) + { + await using var enumerator = itemSource.GetAsyncEnumerator(); + + // Move the cursor to the current index. + for (var i = 0; i < currentIndex; i++) + await enumerator.MoveNextAsync(); + + var hasNext = await enumerator.MoveNextAsync(); + + if(!hasNext) + return (default, false)!; + + return (enumerator.Current, true); + } + + return (default, false)!; + } + private async ValueTask OnChildCompleted(ActivityCompletedContext context) { await HandleIteration(context.TargetContext); diff --git a/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs b/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs index ac8c437bc..07cee0097 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/ParallelForEachT.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Runtime.CompilerServices; using Elsa.Expressions.Helpers; using Elsa.Extensions; @@ -28,7 +29,7 @@ public class ParallelForEach : Activity /// The items to iterate. /// [Input(Description = "The items to iterate through.")] - public Input> Items { get; set; } = new(Array.Empty()); + public Input Items { get; set; } = new(Array.Empty()); /// /// The to execute each iteration. @@ -39,17 +40,12 @@ public class ParallelForEach : Activity /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { - var items = context.Get(Items)!.ToList(); + var items = context.GetItemSource(Items); var tags = new List(); var currentIndex = 0; - if (items.Count == 0) - { - await context.CompleteActivityAsync(); - return; - } - - foreach (var item in items) + // Iterate over the items. + await foreach (var item in items) { // For each item, declare a new variable for the work to be scheduled. var currentValueVariable = new Variable("CurrentValue", item) @@ -57,17 +53,9 @@ public class ParallelForEach : Activity // TODO: This should be configurable, because this won't work for e.g. file streams and other non-serializable types. StorageDriverType = typeof(WorkflowStorageDriver) }; - - var currentIndexVariable = new Variable("CurrentIndex", currentIndex++) - { - StorageDriverType = typeof(WorkflowStorageDriver) - }; - var variables = new List - { - currentValueVariable, - currentIndexVariable - }; + var currentIndexVariable = new Variable("CurrentIndex", currentIndex++) { StorageDriverType = typeof(WorkflowStorageDriver) }; + var variables = new List { currentValueVariable, currentIndexVariable }; // Schedule a body of work for each item. var tag = Guid.NewGuid(); @@ -77,6 +65,10 @@ public class ParallelForEach : Activity context.SetProperty(ScheduledTagsProperty, tags); context.SetProperty(CompletedTagsProperty, new List()); + + // If there were no items, we're done. + if (tags.Count == 0) + await context.CompleteActivityAsync(); } private async ValueTask OnChildCompleted(ActivityCompletedContext context) diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IActivityDataSource.cs b/src/modules/Elsa.Workflows.Core/Contracts/IActivityDataSource.cs deleted file mode 100644 index 9059a9644..000000000 --- a/src/modules/Elsa.Workflows.Core/Contracts/IActivityDataSource.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Elsa.Workflows.Contracts; - -/// -/// Represents a data source for an activity. -/// -public interface IActivityDataSource -{ - /// - /// Gets the data for the specified workflow execution context. - /// - /// The activity execution context. - /// An enumerable of objects. - IAsyncEnumerable GetDataAsync(ActivityExecutionContext context); -} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj b/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj index 6dbf5d73e..1b47908ce 100644 --- a/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj +++ b/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj @@ -34,6 +34,7 @@ + diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs index cb9dd853c..882883686 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs @@ -476,6 +476,10 @@ public static class ExpressionExecutionContextExtensions if (obj is not IEnumerable enumerable || obj is string || obj is IDictionary) return obj; + // If this is an async enumerable, return as-is. + if (obj.GetType().Name == "AsyncIListEnumerableAdapter`1") + return obj; + // Use LINQ to convert the IEnumerable to an array. var elementType = obj.GetType().GetGenericArguments().FirstOrDefault(); diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ItemSourceActivityExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ItemSourceActivityExecutionContextExtensions.cs new file mode 100644 index 000000000..66fd31869 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/Extensions/ItemSourceActivityExecutionContextExtensions.cs @@ -0,0 +1,46 @@ +using System.Collections; +using Elsa.Workflows.Models; + +namespace Elsa.Workflows; + +/// +/// Provides extension methods for the ActivityExecutionContext class. +/// +public static class ItemSourceActivityExecutionContextExtensions +{ + /// + /// Retrieves the item source and returns it as an asynchronous enumerable. + /// Supported types are , and IAsyncEnumerable{IEnumerable{T}}. + /// + /// The type of the items in the source collection. + /// The activity execution context. + /// The input object. + /// An asynchronous enumerable of items from the source collection. + public static async IAsyncEnumerable GetItemSource(this ActivityExecutionContext context, Input input) + { + var items = context.Get(input); + + if (items == null) + yield break; + + var itemsType = items.GetType(); + if (itemsType.Name == "AsyncEnumerableAdapter`1") + { + var isBatch = itemsType.GenericTypeArguments.Length == 1 && typeof(IEnumerable).IsAssignableFrom(itemsType.GenericTypeArguments[0]); + + if (isBatch) + { + if(items is IAsyncEnumerable> typedItems) + await foreach (var typedItem in typedItems) + foreach (T item in typedItem) + yield return item; + } + } + + if (items is IEnumerable enumerable) + { + foreach (T item in enumerable) + yield return item; + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultWorkSchedulerMiddleware.cs b/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultWorkSchedulerMiddleware.cs index 5d16919b4..d8b6313b1 100644 --- a/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultWorkSchedulerMiddleware.cs +++ b/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultWorkSchedulerMiddleware.cs @@ -56,7 +56,8 @@ public class DefaultActivitySchedulerMiddleware : WorkflowExecutionMiddleware Owner = workItem.Owner, ExistingActivityExecutionContext = workItem.ExistingActivityExecutionContext, Tag = workItem.Tag, - Variables = workItem.Variables + Variables = workItem.Variables, + Input = workItem.Input }; await _activityInvoker.InvokeAsync(context, workItem.Activity, options); diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs index 25578afd2..8bab6daec 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs @@ -48,13 +48,7 @@ public class BulkDispatchWorkflows : Activity /// The data source to use for dispatching the workflows. /// [Input(Description = "The data source to use for dispatching the workflows.")] - public Input>? Items { get; set; } - - /// - /// The data source to use for dispatching the workflows. - /// - [Input(Description = "The data source to use for dispatching the workflows.")] - public IActivityDataSource? DataSource { get; set; } + public Input Items { get; set; } = default!; /// /// The correlation ID to associate the workflow with. @@ -95,30 +89,15 @@ public class BulkDispatchWorkflows : Activity protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { var waitForCompletion = WaitForCompletion.GetOrDefault(context); - var items = GetItemsAsync(context).WithCancellation(context.CancellationToken); + var items = context.GetItemSource(Items); var dispatchedInstancesCount = 0L; - var batchSize = 1000; - var batch = new List(); await foreach (var item in items) { - batch.Add(item); - - if (batch.Count < batchSize) - continue; - - await ProcessBatch(context, batch); - dispatchedInstancesCount += batch.Count; - batch.Clear(); + await ProcessItem(context, item); + dispatchedInstancesCount++; } - - // Process the last batch if it has any items. - if (batch.Count > 0) - { - await ProcessBatch(context, batch); - dispatchedInstancesCount += batch.Count; - } - + context.SetProperty(DispatchedInstancesCountKey, dispatchedInstancesCount); // If we need to wait for the child workflow to complete, create a bookmark. @@ -142,29 +121,22 @@ public class BulkDispatchWorkflows : Activity // Otherwise, we can complete immediately. await context.CompleteActivityAsync(); } - - // Cancelling children } - private async Task ProcessBatch(ActivityExecutionContext context, List items) + private async Task ProcessItem(ActivityExecutionContext context, object item) { - var tasks = items.Select(async item => + try { - try - { - await DispatchChildWorkflowAsync(context, item); - } - catch (TaskCanceledException) - { - await context.CompleteActivityWithOutcomesAsync("Canceled"); - } - catch (Exception ex) - { - context.JournalData.Add("Error", ex.Message); - } - }); - - await Task.WhenAll(tasks); + await DispatchChildWorkflowAsync(context, item); + } + catch (TaskCanceledException) + { + await context.CompleteActivityWithOutcomesAsync("Canceled"); + } + catch (Exception ex) + { + context.JournalData.Add("Error", ex.Message); + } } private async ValueTask DispatchChildWorkflowAsync(ActivityExecutionContext context, object item) @@ -206,14 +178,7 @@ public class BulkDispatchWorkflows : Activity return instanceId; } - - private IAsyncEnumerable GetItemsAsync(ActivityExecutionContext context) - { - var items = Items.Get(context); - var dataSource = DataSource != null ? DataSource.GetDataAsync(context) : items.ToAsyncEnumerable(); - return dataSource; - } - + private async ValueTask OnChildWorkflowCompletedAsync(ActivityExecutionContext context) { var input = context.WorkflowInput; diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Activities/FetchOrders.cs b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Activities/FetchOrders.cs new file mode 100644 index 000000000..9736e1b93 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Activities/FetchOrders.cs @@ -0,0 +1,46 @@ +using Bogus; +using Elsa.Extensions; +using Elsa.Samples.AspNet.BatchProcessing.Models; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; + +namespace Elsa.Samples.AspNet.BatchProcessing.Activities; + +[Activity("Demo", "Warehousing", "Fetch orders from the data source.")] +[Output(IsSerializable = false)] +public class FetchOrders : CodeActivity>> +{ + /// + /// The total number of orders to fetch. + /// + [Input(Description = "The total number of orders to fetch.")] + public Input Count { get; set; } = new(100); + + /// + /// The number of orders to fetch per batch. + /// + [Input(Description = "The number of orders to fetch per batch.")] + public Input BatchSize { get; set; } = new(100); + + protected override void Execute(ActivityExecutionContext context) + { + var count = Count.Get(context); + var batchSize = BatchSize.Get(context); + var orders = GenerateOrders(count).Chunk(batchSize).ToAsyncEnumerable(); + + Result.Set(context, orders); + } + + private IEnumerable GenerateOrders(int count) + { + var orderFaker = new Faker() + .RuleFor(o => o.Id, f => Guid.NewGuid().ToString()) + .RuleFor(o => o.CustomerId, f => Guid.NewGuid().ToString()) + .RuleFor(o => o.ProductId, f => Guid.NewGuid().ToString()) + .RuleFor(o => o.Quantity, f => f.Random.Int(1, 100)) + .RuleFor(o => o.Price, f => f.Random.Decimal(0.01m, 1000.00m)); + + return orderFaker.Generate(count); + } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Activities/FetchProducts.cs b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Activities/FetchProducts.cs new file mode 100644 index 000000000..f54dcb9fb --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Activities/FetchProducts.cs @@ -0,0 +1,65 @@ +using Bogus; +using Elsa.Extensions; +using Elsa.Samples.AspNet.BatchProcessing.Models; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using Elsa.Workflows.Options; + +namespace Elsa.Samples.AspNet.BatchProcessing.Activities; + +[Activity("Demo", "Warehousing", "Fetch products from the data source.")] +[Output(IsSerializable = false)] +public class FetchProducts : CodeActivity> +{ + private const string CurrentBathKey = nameof(CurrentBathKey); + + /// + /// The total number of products to fetch. + /// + [Input(Description = "The total number of products to fetch.")] + public Input Count { get; set; } = new(100); + + /// + /// The number of products to fetch per batch. + /// + [Input(Description = "The number of products to fetch per batch.")] + public Input BatchSize { get; set; } = new(100); + + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var count = Count.Get(context); + var batchSize = BatchSize.Get(context); + var currentBatch = context.ActivityInput.TryGetValue(CurrentBathKey, out var currentBatchValue) ? (int)currentBatchValue : 0; + var orders = GenerateProducts(count).Skip(currentBatch * batchSize).Take(batchSize).ToList(); + + if (orders.Any()) + { + currentBatch++; + context.SetProperty(CurrentBathKey, currentBatch); + Result.Set(context, orders); + + // Schedule the next batch. + await context.ScheduleActivityAsync(this, new ScheduleWorkOptions + { + Input = new Dictionary + { + [CurrentBathKey] = currentBatch + } + }); + } + + // Complete the activity. + await context.CompleteActivityAsync(); + } + + private IEnumerable GenerateProducts(int count) + { + var productFaker = new Faker() + .RuleFor(o => o.Id, f => Guid.NewGuid().ToString()) + .RuleFor(o => o.Name, f => Guid.NewGuid().ToString()) + .RuleFor(o => o.Price, f => f.Random.Decimal(0.01m, 1000.00m)); + + return productFaker.Generate(count); + } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Elsa.Samples.AspNet.BatchProcessing.csproj b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Elsa.Samples.AspNet.BatchProcessing.csproj new file mode 100644 index 000000000..8f87493f0 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Elsa.Samples.AspNet.BatchProcessing.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Models/Order.cs b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Models/Order.cs new file mode 100644 index 000000000..c5c4bd438 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Models/Order.cs @@ -0,0 +1,10 @@ +namespace Elsa.Samples.AspNet.BatchProcessing.Models; + +public class Order +{ + public string Id { get; set; } = default!; + public string CustomerId { get; set; } = default!; + public string ProductId { get; set; } = default!; + public int Quantity { get; set; } + public decimal Price { get; set; } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Models/Product.cs b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Models/Product.cs new file mode 100644 index 000000000..af72291c9 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Models/Product.cs @@ -0,0 +1,8 @@ +namespace Elsa.Samples.AspNet.BatchProcessing.Models; + +public class Product +{ + public string Id { get; set; } = default!; + public string Name { get; set; } = default!; + public decimal Price { get; set; } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Program.cs b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Program.cs new file mode 100644 index 000000000..53681d835 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Program.cs @@ -0,0 +1,42 @@ +using Elsa.EntityFrameworkCore.Modules.Management; +using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddElsa(elsa => +{ + elsa.UseWorkflowManagement(management => management.UseEntityFrameworkCore()); + elsa.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore()); + elsa.UseWorkflowsApi(); + elsa.UseHttp(); + elsa.UseScheduling(); + elsa.UseJavaScript(javaScript => javaScript.AllowClrAccess = true); + elsa.UseCSharp(); + elsa.UseLiquid(); + + elsa.UseIdentity(identity => + { + identity.UseAdminUserProvider(); + identity.TokenOptions = options => + { + options.SigningKey = "super-secret-tamper-free-token-signing-key"; + options.AccessTokenLifetime = TimeSpan.FromDays(1); + }; + }); + + elsa.UseDefaultAuthentication(auth => auth.UseAdminApiKey()); + elsa.AddActivitiesFrom(); + elsa.AddWorkflowsFrom(); +}); + +builder.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().WithExposedHeaders("*"))); + +var app = builder.Build(); +app.UseHttpsRedirection(); +app.UseCors(); +app.UseAuthentication(); +app.UseAuthorization(); +app.UseWorkflowsApi(); +app.UseWorkflows(); +app.Run(); \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Properties/launchSettings.json b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Properties/launchSettings.json new file mode 100644 index 000000000..a82352f12 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:14013", + "sslPort": 44367 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5210", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7024;http://localhost:5210", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Workflows/OrderBatchProcessor.cs b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Workflows/OrderBatchProcessor.cs new file mode 100644 index 000000000..8d0df87ca --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/Workflows/OrderBatchProcessor.cs @@ -0,0 +1,28 @@ +using Elsa.Samples.AspNet.BatchProcessing.Activities; +using Elsa.Samples.AspNet.BatchProcessing.Models; +using Elsa.Workflows; +using Elsa.Workflows.Activities; +using Elsa.Workflows.Contracts; + +namespace Elsa.Samples.AspNet.BatchProcessing.Workflows; + +public class OrderBatchProcessor : WorkflowBase +{ + protected override void Build(IWorkflowBuilder builder) + { + var orders = builder.WithVariable>(); + builder.Root = new Sequence + { + Activities = + { + new WriteLine("Fetching orders..."), + new FetchOrders(), + new ForEach + { + ItemSource = new(orders) + }, + new WriteLine("Done!") + } + }; + } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/appsettings.Development.json b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/appsettings.Development.json new file mode 100644 index 000000000..4b90d5b82 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.EntityFrameworkCore": "Warning", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/appsettings.json b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/appsettings.json new file mode 100644 index 000000000..006192806 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.BatchProcessing/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.EntityFrameworkCore": "Warning", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Elsa.Samples.AspNet.WorkflowServer.csproj b/src/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Elsa.Samples.AspNet.WorkflowServer.csproj index d85ccfdb3..4ffba88d0 100644 --- a/src/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Elsa.Samples.AspNet.WorkflowServer.csproj +++ b/src/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Elsa.Samples.AspNet.WorkflowServer.csproj @@ -8,6 +8,7 @@ + diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Program.cs b/src/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Program.cs index 3dc0e8e53..a093abdf5 100644 --- a/src/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Program.cs +++ b/src/samples/aspnet/Elsa.Samples.AspNet.WorkflowServer/Program.cs @@ -30,7 +30,7 @@ builder.Services.AddElsa(elsa => identity.UseAdminUserProvider(); identity.TokenOptions = options => { - options.SigningKey = "secret-token-signing-key"; + options.SigningKey = "super-secret-tamper-free-token-signing-key"; options.AccessTokenLifetime = TimeSpan.FromDays(1); }; });