From 594853d45c087987f770ac3202ef5caec94d0feb Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sun, 1 Nov 2020 20:03:21 +0100 Subject: [PATCH] Add Composite Activity support --- Samples.sln | 7 ++ .../Builders/IWorkflowBuilder.cs | 8 +- .../Models/WorkflowDefinition.cs | 3 +- .../Services/ActivityPropertyProviders.cs | 13 ++- .../Services/CompositeActivity.cs | 19 ++++ .../Services/IActivityFactory.cs | 10 +++ .../Services/IActivityPropertyProviders.cs | 5 +- .../Services/Models/ActivityBlueprint.cs | 2 +- .../Services/Models/IActivityBlueprint.cs | 1 + .../Elsa.Core/Builders/WorkflowBuilder.cs | 90 +++++++++++++------ .../ElsaServiceCollectionExtensions.cs | 1 + .../Elsa.Core/Services/ActivityFactory.cs | 15 ++++ .../Elsa.Core/Services/WorkflowFactory.cs | 11 +-- src/core/Elsa.Core/Services/WorkflowRunner.cs | 14 +-- .../Elsa.Core/Triggers/WorkflowSelector.cs | 3 +- .../AzureBlobLockProvider.cs | 12 +-- .../ElsaOptionsExtensions.cs | 4 +- .../LockedBlob.cs | 2 +- .../ElsaOptionsExtensions.cs | 6 +- .../RedisLockProvider.cs | 8 +- .../ElsaOptionsExtensions.cs | 2 +- .../SqlLockProvider.cs | 2 +- .../Activities/CountDownActivity.cs | 17 ++++ ....Samples.CompositeActivitiesConsole.csproj | 16 ++++ .../Program.cs | 34 +++++++ .../Workflows/CompositionWorkflow.cs | 17 ++++ .../Elsa.Samples.DistributedLock/Program.cs | 1 - .../WorkflowDefinitions/PostTests.cs | 2 +- .../Helpers/TemporaryFolder.cs | 2 +- .../Helpers/WorkflowsTestBase.cs | 1 - .../Helpers/XunitConsoleForwarder.cs | 2 +- 31 files changed, 246 insertions(+), 84 deletions(-) create mode 100644 src/core/Elsa.Abstractions/Services/CompositeActivity.cs create mode 100644 src/core/Elsa.Abstractions/Services/IActivityFactory.cs create mode 100644 src/core/Elsa.Core/Services/ActivityFactory.cs create mode 100644 src/samples/Elsa.Samples.CompositeActivitiesConsole/Activities/CountDownActivity.cs create mode 100644 src/samples/Elsa.Samples.CompositeActivitiesConsole/Elsa.Samples.CompositeActivitiesConsole.csproj create mode 100644 src/samples/Elsa.Samples.CompositeActivitiesConsole/Program.cs create mode 100644 src/samples/Elsa.Samples.CompositeActivitiesConsole/Workflows/CompositionWorkflow.cs diff --git a/Samples.sln b/Samples.sln index 6c1abb33c..ccd9d4c01 100644 --- a/Samples.sln +++ b/Samples.sln @@ -112,6 +112,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.ContextualWork EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.RunChildWorkflowWorker", "src\samples\Elsa.Samples.RunChildWorkflowWorker\Elsa.Samples.RunChildWorkflowWorker.csproj", "{1E5E1669-AEC9-4DA9-9A3D-3D405101A246}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.CompositeActivitiesConsole", "src\samples\Elsa.Samples.CompositeActivitiesConsole\Elsa.Samples.CompositeActivitiesConsole.csproj", "{CC39E9F8-72D2-4A5B-846B-E2764DFE1C19}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -267,6 +269,10 @@ Global {1E5E1669-AEC9-4DA9-9A3D-3D405101A246}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E5E1669-AEC9-4DA9-9A3D-3D405101A246}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E5E1669-AEC9-4DA9-9A3D-3D405101A246}.Release|Any CPU.Build.0 = Release|Any CPU + {CC39E9F8-72D2-4A5B-846B-E2764DFE1C19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC39E9F8-72D2-4A5B-846B-E2764DFE1C19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC39E9F8-72D2-4A5B-846B-E2764DFE1C19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC39E9F8-72D2-4A5B-846B-E2764DFE1C19}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -319,6 +325,7 @@ Global {565E2F7D-1F5F-45EF-B130-BC09CB3465B1} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A} {37753BF9-9D9B-4264-AB5B-D057E467D3C5} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A} {1E5E1669-AEC9-4DA9-9A3D-3D405101A246} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A} + {CC39E9F8-72D2-4A5B-846B-E2764DFE1C19} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158} diff --git a/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs b/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs index d2dc2ccb1..e01ff5f9c 100644 --- a/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs +++ b/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs @@ -64,9 +64,9 @@ namespace Elsa.Builders Func target, string outcome = OutcomeNames.Done); - IWorkflowBlueprint Build(); - IWorkflowBlueprint Build(IWorkflow workflow); - IWorkflowBlueprint Build(Type workflowType); - IWorkflowBlueprint Build() where T : IWorkflow; + IWorkflowBlueprint Build(string activityIdPrefix = "activity"); + IWorkflowBlueprint Build(IWorkflow workflow, string activityIdPrefix = "activity"); + IWorkflowBlueprint Build(Type workflowType, string activityIdPrefix = "activity"); + IWorkflowBlueprint Build(string activityIdPrefix = "activity") where T : IWorkflow; } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Models/WorkflowDefinition.cs b/src/core/Elsa.Abstractions/Models/WorkflowDefinition.cs index a18dbd6bf..9cefb13b0 100644 --- a/src/core/Elsa.Abstractions/Models/WorkflowDefinition.cs +++ b/src/core/Elsa.Abstractions/Models/WorkflowDefinition.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Elsa.Services.Models; namespace Elsa.Models diff --git a/src/core/Elsa.Abstractions/Services/ActivityPropertyProviders.cs b/src/core/Elsa.Abstractions/Services/ActivityPropertyProviders.cs index a91d95c13..5292c382f 100644 --- a/src/core/Elsa.Abstractions/Services/ActivityPropertyProviders.cs +++ b/src/core/Elsa.Abstractions/Services/ActivityPropertyProviders.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; @@ -43,10 +44,7 @@ namespace Elsa.Services ? provider : null; - public async ValueTask SetActivityPropertiesAsync( - IActivity activity, - ActivityExecutionContext activityExecutionContext, - CancellationToken cancellationToken = default) + public async ValueTask SetActivityPropertiesAsync(IActivity activity, ActivityExecutionContext activityExecutionContext, CancellationToken cancellationToken = default) { var properties = activity.GetType().GetProperties().Where(IsActivityProperty).ToList(); var providers = GetProviders(activity.Id); @@ -64,7 +62,8 @@ namespace Elsa.Services } } - private bool IsActivityProperty(PropertyInfo property) => - property.GetCustomAttribute() != null; + private bool IsActivityProperty(PropertyInfo property) => property.GetCustomAttribute() != null; + public IEnumerator>> GetEnumerator() => _providers.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs new file mode 100644 index 000000000..ea80b6b45 --- /dev/null +++ b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using Elsa.ActivityResults; +using Elsa.Builders; +using Elsa.Services.Models; + +namespace Elsa.Services +{ + public abstract class CompositeActivity : Activity + { + public abstract void Build(IWorkflowBuilder workflowBuilder); + + protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) + { + var startActivities = context.ActivityBlueprint.ChildWorkflow!.GetStartActivities().Select(x => x.Id).ToList(); + return Combine(Done(), Schedule(startActivities, null!)); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/IActivityFactory.cs b/src/core/Elsa.Abstractions/Services/IActivityFactory.cs new file mode 100644 index 000000000..a76f78f89 --- /dev/null +++ b/src/core/Elsa.Abstractions/Services/IActivityFactory.cs @@ -0,0 +1,10 @@ +using Elsa.Models; +using Elsa.Services.Models; + +namespace Elsa.Services +{ + public interface IActivityFactory + { + ActivityInstance Instantiate(IActivityBlueprint activityBlueprint); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/IActivityPropertyProviders.cs b/src/core/Elsa.Abstractions/Services/IActivityPropertyProviders.cs index 14d884817..4c0d1e3f8 100644 --- a/src/core/Elsa.Abstractions/Services/IActivityPropertyProviders.cs +++ b/src/core/Elsa.Abstractions/Services/IActivityPropertyProviders.cs @@ -1,10 +1,11 @@ -using System.Threading; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Elsa.Services.Models; namespace Elsa.Services { - public interface IActivityPropertyProviders + public interface IActivityPropertyProviders : IEnumerable>> { void AddProvider(string activityId, string propertyName, IActivityPropertyValueProvider provider); IActivityPropertyValueProvider? GetProvider(string activityId, string propertyName); diff --git a/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs index ae55d457d..eeb63d3f1 100644 --- a/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs +++ b/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs @@ -27,8 +27,8 @@ namespace Elsa.Services.Models public string Id { get; set; } = default!; public string? Name { get; set; } public string Type { get; set; } = default!; - public bool PersistWorkflow { get; set; } + public IWorkflowBlueprint? ChildWorkflow { get; set; } public Func> CreateActivityAsync { get; set; } = default!; } diff --git a/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs index 6336ff7d5..305d0b8f6 100644 --- a/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs +++ b/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs @@ -11,5 +11,6 @@ namespace Elsa.Services.Models public string Type { get; } public bool PersistWorkflow { get; } Func> CreateActivityAsync { get; } + IWorkflowBlueprint? ChildWorkflow { get; set; } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Builders/WorkflowBuilder.cs b/src/core/Elsa.Core/Builders/WorkflowBuilder.cs index fda16ff2b..6a2c9ba67 100644 --- a/src/core/Elsa.Core/Builders/WorkflowBuilder.cs +++ b/src/core/Elsa.Core/Builders/WorkflowBuilder.cs @@ -5,21 +5,23 @@ using Elsa.Models; using Elsa.Services; using Elsa.Services.Models; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; +using NetBox.Extensions; namespace Elsa.Builders { public class WorkflowBuilder : IWorkflowBuilder { private readonly IIdGenerator _idGenerator; - private readonly JsonSerializer _serializer; + private readonly IActivityActivator _activityActivator; + private readonly Func _workflowBuilderFactory; private readonly IList _activityBuilders; private readonly IList _connectionBuilders; - public WorkflowBuilder(IIdGenerator idGenerator, JsonSerializer serializer, IServiceProvider serviceProvider) + public WorkflowBuilder(IIdGenerator idGenerator, IActivityActivator activityActivator, Func workflowBuilderFactory, IServiceProvider serviceProvider) { _idGenerator = idGenerator; - _serializer = serializer; + _activityActivator = activityActivator; + _workflowBuilderFactory = workflowBuilderFactory; ServiceProvider = serviceProvider; Id = idGenerator.Generate(); Version = 1; @@ -62,13 +64,15 @@ namespace Elsa.Builders } public IWorkflowBuilder WithContextType(WorkflowContextFidelity fidelity) => WithContextType(typeof(T), fidelity); - - public IWorkflowBuilder WithContextType(Type type, WorkflowContextFidelity fidelity = WorkflowContextFidelity.Burst) => WithContextOptions(new WorkflowContextOptions - { - ContextType = type, - ContextFidelity = fidelity - }); - + + public IWorkflowBuilder WithContextType(Type type, WorkflowContextFidelity fidelity = WorkflowContextFidelity.Burst) => + WithContextOptions( + new WorkflowContextOptions + { + ContextType = type, + ContextFidelity = fidelity + }); + public IWorkflowBuilder WithContextOptions(WorkflowContextOptions value) { ContextOptions = value; @@ -213,34 +217,38 @@ namespace Elsa.Builders where T : class, IActivity => StartWith(branch); - public IWorkflowBlueprint Build(IWorkflow workflow) + public IWorkflowBlueprint Build(IWorkflow workflow, string activityIdPrefix = "activity") { WithId(workflow.GetType().Name); workflow.Build(this); - return Build(); + return Build(activityIdPrefix); } - public IWorkflowBlueprint Build() + public IWorkflowBlueprint Build(string activityIdPrefix = "activity") { var definitionId = !string.IsNullOrWhiteSpace(Id) ? Id : _idGenerator.Generate(); + var activityBlueprints = new List(); + var connections = new List(); + var activityPropertyProviders = new Dictionary>(); // Assign automatic ids to activity builders var index = 0; - foreach (var activityBuilder in _activityBuilders.Where(x => string.IsNullOrWhiteSpace(x.ActivityId))) activityBuilder.ActivityId = $"activity-{++index}"; - - var activityBlueprints = _activityBuilders - .Select(BuildActivityBlueprint) - .ToList(); + foreach (var activityBuilder in _activityBuilders.Where(x => string.IsNullOrWhiteSpace(x.ActivityId))) + activityBuilder.ActivityId = $"{activityIdPrefix}-{++index}"; + + activityBlueprints.AddRange(_activityBuilders.Select(BuildActivityBlueprint)); + + // Build composite activities. + BuildCompositeActivities(_activityBuilders, activityBlueprints, connections, activityPropertyProviders); + var activityBlueprintDictionary = activityBlueprints.ToDictionary(x => x.Id); - var connections = _connectionBuilders - .Select(x => new Connection(activityBlueprintDictionary[x.Source().ActivityId], activityBlueprintDictionary[x.Target().ActivityId], x.Outcome)) - .ToList(); + connections.AddRange(_connectionBuilders.Select(x => new Connection(activityBlueprintDictionary[x.Source().ActivityId], activityBlueprintDictionary[x.Target().ActivityId], x.Outcome))); - var activityPropertyValueProviders = _activityBuilders + activityPropertyProviders.AddRange(_activityBuilders .Select(x => (x.ActivityId, x.PropertyValueProviders)) - .ToDictionary(x => x.ActivityId!, x => x.PropertyValueProviders!); + .ToDictionary(x => x.ActivityId!, x => x.PropertyValueProviders!)); var workflow = new WorkflowBlueprint( definitionId, @@ -257,18 +265,42 @@ namespace Elsa.Builders DeleteCompletedInstances, activityBlueprints, connections, - new ActivityPropertyProviders(activityPropertyValueProviders)); + new ActivityPropertyProviders(activityPropertyProviders)); return workflow; } - public IWorkflowBlueprint Build(Type workflowType) + private void BuildCompositeActivities(IEnumerable activityBuilders, ICollection activityBlueprints, ICollection connections, IDictionary> activityPropertyProviders) { - var workflow = (IWorkflow)ActivatorUtilities.GetServiceOrCreateInstance(ServiceProvider, workflowType); - return Build(workflow); + var compositeActivityType = typeof(CompositeActivity); + var compositeActivityBuilders = activityBuilders.Where(x => compositeActivityType.IsAssignableFrom(x.ActivityType)).ToList(); + + foreach (var activityBuilder in compositeActivityBuilders) + { + var compositeActivity = (CompositeActivity)_activityActivator.ActivateActivity(activityBuilder.ActivityType.Name); + var workflowBuilder = _workflowBuilderFactory(); + + compositeActivity.Build(workflowBuilder); + + var workflow = workflowBuilder.Build($"composite-{activityBuilder.ActivityId}-activity"); + var activityDictionary = workflow.Activities.ToDictionary(x => x.Id); + + activityBlueprints.AddRange(workflow.Activities); + connections.AddRange(workflow.Connections.Select(x => new Connection(activityDictionary[x.Source.Activity.Id], activityDictionary[x.Target.Activity.Id], x.Source.Outcome))); + activityPropertyProviders.AddRange(workflow.ActivityPropertyProviders); + + var compositeActivityBlueprint = activityBlueprints.Single(x => x.Id == activityBuilder.ActivityId); + compositeActivityBlueprint.ChildWorkflow = workflow; + } } - public IWorkflowBlueprint Build() where T : IWorkflow => Build(typeof(T)); + public IWorkflowBlueprint Build(Type workflowType, string activityIdPrefix = "activity") + { + var workflow = (IWorkflow)ActivatorUtilities.GetServiceOrCreateInstance(ServiceProvider, workflowType); + return Build(workflow, activityIdPrefix); + } + + public IWorkflowBlueprint Build(string activityIdPrefix = "activity") where T : IWorkflow => Build(typeof(T), activityIdPrefix); private IActivityBlueprint BuildActivityBlueprint(IActivityBuilder builder, int index) => new ActivityBlueprint(builder.ActivityId, builder.Name, builder.ActivityType.Name, builder.PersistWorkflow, builder.BuildActivityAsync()); } diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index 6b839a188..9ef0ae9aa 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -102,6 +102,7 @@ namespace Microsoft.Extensions.DependencyInjection .AddSingleton() .AddScoped() .AddSingleton() + .AddSingleton() .AddSingleton() .AddScoped() .AddScoped() diff --git a/src/core/Elsa.Core/Services/ActivityFactory.cs b/src/core/Elsa.Core/Services/ActivityFactory.cs new file mode 100644 index 000000000..e494f81a4 --- /dev/null +++ b/src/core/Elsa.Core/Services/ActivityFactory.cs @@ -0,0 +1,15 @@ +using Elsa.Models; +using Elsa.Services.Models; +using Newtonsoft.Json.Linq; + +namespace Elsa.Services +{ + public class ActivityFactory : IActivityFactory + { + public ActivityInstance Instantiate(IActivityBlueprint activityBlueprint) => new ActivityInstance( + activityBlueprint.Id, + activityBlueprint.Type, + null, + new JObject()); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/WorkflowFactory.cs b/src/core/Elsa.Core/Services/WorkflowFactory.cs index 276319972..4aedc37ae 100644 --- a/src/core/Elsa.Core/Services/WorkflowFactory.cs +++ b/src/core/Elsa.Core/Services/WorkflowFactory.cs @@ -3,18 +3,19 @@ using System.Threading; using System.Threading.Tasks; using Elsa.Models; using Elsa.Services.Models; -using Newtonsoft.Json.Linq; using NodaTime; namespace Elsa.Services { public class WorkflowFactory : IWorkflowFactory { + private readonly IActivityFactory _activityFactory; private readonly IClock _clock; private readonly IIdGenerator _idGenerator; - public WorkflowFactory(IClock clock, IIdGenerator idGenerator) + public WorkflowFactory(IActivityFactory activityFactory, IClock clock, IIdGenerator idGenerator) { + _activityFactory = activityFactory; _clock = clock; _idGenerator = idGenerator; } @@ -41,10 +42,6 @@ namespace Elsa.Services return Task.FromResult(workflowInstance); } - private ActivityInstance CreateInstance(IActivityBlueprint activityBlueprint) => new ActivityInstance( - activityBlueprint.Id, - activityBlueprint.Type, - null, - new JObject()); + private ActivityInstance CreateInstance(IActivityBlueprint activityBlueprint) => _activityFactory.Instantiate(activityBlueprint); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/WorkflowRunner.cs b/src/core/Elsa.Core/Services/WorkflowRunner.cs index e19f97fdb..bd738158e 100644 --- a/src/core/Elsa.Core/Services/WorkflowRunner.cs +++ b/src/core/Elsa.Core/Services/WorkflowRunner.cs @@ -23,7 +23,7 @@ namespace Elsa.Services private readonly IWorkflowRegistry _workflowRegistry; private readonly IWorkflowFactory _workflowFactory; - private readonly IWorkflowBuilder _workflowBuilder; + private readonly Func _workflowBuilderFactory; private readonly IWorkflowContextManager _workflowContextManager; private readonly IMediator _mediator; private readonly IServiceProvider _serviceProvider; @@ -32,7 +32,7 @@ namespace Elsa.Services public WorkflowRunner( IWorkflowRegistry workflowRegistry, IWorkflowFactory workflowFactory, - IWorkflowBuilder workflowBuilder, + Func workflowBuilderFactory, IWorkflowContextManager workflowContextManager, IMediator mediator, IServiceProvider serviceProvider, @@ -40,7 +40,7 @@ namespace Elsa.Services { _workflowRegistry = workflowRegistry; _workflowFactory = workflowFactory; - _workflowBuilder = workflowBuilder; + _workflowBuilderFactory = workflowBuilderFactory; _workflowContextManager = workflowContextManager; _mediator = mediator; _serviceProvider = serviceProvider; @@ -71,7 +71,7 @@ namespace Elsa.Services string? contextId = default, CancellationToken cancellationToken = default) where T : IWorkflow => - await RunWorkflowAsync(_workflowBuilder.Build(), activityId, input, correlationId, contextId, cancellationToken); + await RunWorkflowAsync(_workflowBuilderFactory().Build(), activityId, input, correlationId, contextId, cancellationToken); public async ValueTask RunWorkflowAsync( WorkflowInstance workflowInstance, @@ -79,7 +79,7 @@ namespace Elsa.Services object? input = default, CancellationToken cancellationToken = default) where T : IWorkflow => - await RunWorkflowAsync(_workflowBuilder.Build(), workflowInstance, activityId, input, cancellationToken); + await RunWorkflowAsync(_workflowBuilderFactory().Build(), workflowInstance, activityId, input, cancellationToken); public async ValueTask RunWorkflowAsync( IWorkflow workflow, @@ -89,7 +89,7 @@ namespace Elsa.Services string? contextId = default, CancellationToken cancellationToken = default) { - var workflowBlueprint = _workflowBuilder.Build(workflow); + var workflowBlueprint = _workflowBuilderFactory().Build(workflow); return await RunWorkflowAsync(workflowBlueprint, activityId, input, correlationId, contextId, cancellationToken); } @@ -100,7 +100,7 @@ namespace Elsa.Services object? input = default, CancellationToken cancellationToken = default) { - var workflowBlueprint = _workflowBuilder.Build(workflow); + var workflowBlueprint = _workflowBuilderFactory().Build(workflow); return await RunWorkflowAsync(workflowBlueprint, workflowInstance, activityId, input, cancellationToken); } diff --git a/src/core/Elsa.Core/Triggers/WorkflowSelector.cs b/src/core/Elsa.Core/Triggers/WorkflowSelector.cs index 954f74a82..20d545d44 100644 --- a/src/core/Elsa.Core/Triggers/WorkflowSelector.cs +++ b/src/core/Elsa.Core/Triggers/WorkflowSelector.cs @@ -155,8 +155,7 @@ namespace Elsa.Triggers { var providers = _triggerProviders.ToList(); var descriptors = new List(); - var loadWorkflowContext = new LoadWorkflowContext(workflowBlueprint, workflowInstance); - var workflowContext = await _workflowContextManager.LoadContext(loadWorkflowContext, cancellationToken); + var workflowContext = workflowBlueprint.ContextOptions != null ? await _workflowContextManager.LoadContext(new LoadWorkflowContext(workflowBlueprint, workflowInstance), cancellationToken) : default; var workflowExecutionContext = new WorkflowExecutionContext(_serviceProvider, workflowBlueprint, workflowInstance, default, workflowContext); foreach (var blockingActivity in blockingActivities) diff --git a/src/providers/Elsa.DistributedLocking.AzureBlob/AzureBlobLockProvider.cs b/src/providers/Elsa.DistributedLocking.AzureBlob/AzureBlobLockProvider.cs index b199d501c..099a5dd9c 100644 --- a/src/providers/Elsa.DistributedLocking.AzureBlob/AzureBlobLockProvider.cs +++ b/src/providers/Elsa.DistributedLocking.AzureBlob/AzureBlobLockProvider.cs @@ -1,16 +1,16 @@ -using Elsa.DistributedLock; -using Microsoft.Azure.Storage; -using Microsoft.Azure.Storage.Blob; -using Microsoft.Azure.Storage.RetryPolicies; -using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Elsa.DistributedLock; +using Microsoft.Azure.Storage; +using Microsoft.Azure.Storage.Blob; +using Microsoft.Azure.Storage.RetryPolicies; +using Microsoft.Extensions.Logging; -namespace Elsa.DistributedLocking.AzureBlob +namespace Elsa { // See also: // The lock duration can be 15 to 60 seconds, or can be infinite, Reference: https://docs.microsoft.com/en-us/rest/api/storageservices/lease-blob diff --git a/src/providers/Elsa.DistributedLocking.AzureBlob/ElsaOptionsExtensions.cs b/src/providers/Elsa.DistributedLocking.AzureBlob/ElsaOptionsExtensions.cs index f6f2eeea0..848fca1e0 100644 --- a/src/providers/Elsa.DistributedLocking.AzureBlob/ElsaOptionsExtensions.cs +++ b/src/providers/Elsa.DistributedLocking.AzureBlob/ElsaOptionsExtensions.cs @@ -1,8 +1,8 @@ +using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using System; -namespace Elsa.DistributedLocking.AzureBlob +namespace Elsa { public static class ElsaOptionsExtensions { diff --git a/src/providers/Elsa.DistributedLocking.AzureBlob/LockedBlob.cs b/src/providers/Elsa.DistributedLocking.AzureBlob/LockedBlob.cs index 5a2dad9ea..1d7a435d2 100644 --- a/src/providers/Elsa.DistributedLocking.AzureBlob/LockedBlob.cs +++ b/src/providers/Elsa.DistributedLocking.AzureBlob/LockedBlob.cs @@ -1,6 +1,6 @@ using Microsoft.Azure.Storage.Blob; -namespace Elsa.DistributedLocking.AzureBlob +namespace Elsa { internal class LockedBlob { diff --git a/src/providers/Elsa.DistributedLocking.Redis/ElsaOptionsExtensions.cs b/src/providers/Elsa.DistributedLocking.Redis/ElsaOptionsExtensions.cs index f1477b14c..54ac54ad7 100644 --- a/src/providers/Elsa.DistributedLocking.Redis/ElsaOptionsExtensions.cs +++ b/src/providers/Elsa.DistributedLocking.Redis/ElsaOptionsExtensions.cs @@ -1,13 +1,13 @@ +using System; +using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using RedLockNet; using RedLockNet.SERedis; using RedLockNet.SERedis.Configuration; using StackExchange.Redis; -using System; -using System.Collections.Generic; -namespace Elsa.DistributedLocking.Redis +namespace Elsa { public static class ElsaOptionsExtensions { diff --git a/src/providers/Elsa.DistributedLocking.Redis/RedisLockProvider.cs b/src/providers/Elsa.DistributedLocking.Redis/RedisLockProvider.cs index 10a878702..b2385af61 100644 --- a/src/providers/Elsa.DistributedLocking.Redis/RedisLockProvider.cs +++ b/src/providers/Elsa.DistributedLocking.Redis/RedisLockProvider.cs @@ -1,12 +1,12 @@ -using Elsa.DistributedLock; -using Microsoft.Extensions.Logging; -using RedLockNet; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Elsa.DistributedLock; +using Microsoft.Extensions.Logging; +using RedLockNet; -namespace Elsa.DistributedLocking.Redis +namespace Elsa { public class RedisLockProvider : IDistributedLockProvider { diff --git a/src/providers/Elsa.DistributedLocking.SqlServer/ElsaOptionsExtensions.cs b/src/providers/Elsa.DistributedLocking.SqlServer/ElsaOptionsExtensions.cs index bbff96207..b9a4715d7 100644 --- a/src/providers/Elsa.DistributedLocking.SqlServer/ElsaOptionsExtensions.cs +++ b/src/providers/Elsa.DistributedLocking.SqlServer/ElsaOptionsExtensions.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Elsa.DistributedLocking.SqlServer +namespace Elsa { public static class ElsaOptionsExtensions { diff --git a/src/providers/Elsa.DistributedLocking.SqlServer/SqlLockProvider.cs b/src/providers/Elsa.DistributedLocking.SqlServer/SqlLockProvider.cs index d199fe209..8fe91aacd 100644 --- a/src/providers/Elsa.DistributedLocking.SqlServer/SqlLockProvider.cs +++ b/src/providers/Elsa.DistributedLocking.SqlServer/SqlLockProvider.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Elsa.DistributedLock; using Microsoft.Extensions.Logging; -namespace Elsa.DistributedLocking.SqlServer +namespace Elsa { // CREDITS: // Implementation taken & adapted from Workflow Core: https://github.com/danielgerlag/workflow-core/blob/master/src/providers/WorkflowCore.LockProviders.SqlServer/SqlLockProvider.cs diff --git a/src/samples/Elsa.Samples.CompositeActivitiesConsole/Activities/CountDownActivity.cs b/src/samples/Elsa.Samples.CompositeActivitiesConsole/Activities/CountDownActivity.cs new file mode 100644 index 000000000..4fe3be5a6 --- /dev/null +++ b/src/samples/Elsa.Samples.CompositeActivitiesConsole/Activities/CountDownActivity.cs @@ -0,0 +1,17 @@ +using Elsa.Activities.Console; +using Elsa.Builders; +using Elsa.Services; + +namespace Elsa.Samples.CompositeActivitiesConsole.Activities +{ + public class CountDownActivity : CompositeActivity + { + public override void Build(IWorkflowBuilder workflowBuilder) + { + workflowBuilder + .WriteLine("3!") + .WriteLine("2!") + .WriteLine("1!"); + } + } +} \ No newline at end of file diff --git a/src/samples/Elsa.Samples.CompositeActivitiesConsole/Elsa.Samples.CompositeActivitiesConsole.csproj b/src/samples/Elsa.Samples.CompositeActivitiesConsole/Elsa.Samples.CompositeActivitiesConsole.csproj new file mode 100644 index 000000000..7fbd68615 --- /dev/null +++ b/src/samples/Elsa.Samples.CompositeActivitiesConsole/Elsa.Samples.CompositeActivitiesConsole.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + diff --git a/src/samples/Elsa.Samples.CompositeActivitiesConsole/Program.cs b/src/samples/Elsa.Samples.CompositeActivitiesConsole/Program.cs new file mode 100644 index 000000000..e5e86f07b --- /dev/null +++ b/src/samples/Elsa.Samples.CompositeActivitiesConsole/Program.cs @@ -0,0 +1,34 @@ +using System.Data; +using System.Threading.Tasks; +using Elsa.Samples.CompositeActivitiesConsole.Activities; +using Elsa.Samples.CompositeActivitiesConsole.Workflows; +using Elsa.Services; +using Microsoft.Extensions.DependencyInjection; +using YesSql.Provider.Sqlite; + +namespace Elsa.Samples.CompositeActivitiesConsole +{ + class Program + { + static async Task Main(string[] args) + { + // Create a service container with Elsa services. + var services = new ServiceCollection() + .AddElsa(option => option.UsePersistence(db => db.UseSqLite("Data Source=elsa.db;Cache=Shared", IsolationLevel.ReadUncommitted))) + .AddConsoleActivities() + .AddActivity() + .AddWorkflow() + .BuildServiceProvider(); + + // Run startup actions (not needed when registering Elsa with a Host). + var startupRunner = services.GetRequiredService(); + await startupRunner.StartupAsync(); + + // Get a workflow host. + var workflowRunner = services.GetService(); + + // Execute the workflow. + await workflowRunner.RunWorkflowAsync(); + } + } +} \ No newline at end of file diff --git a/src/samples/Elsa.Samples.CompositeActivitiesConsole/Workflows/CompositionWorkflow.cs b/src/samples/Elsa.Samples.CompositeActivitiesConsole/Workflows/CompositionWorkflow.cs new file mode 100644 index 000000000..2d0332c97 --- /dev/null +++ b/src/samples/Elsa.Samples.CompositeActivitiesConsole/Workflows/CompositionWorkflow.cs @@ -0,0 +1,17 @@ +using Elsa.Activities.Console; +using Elsa.Builders; +using Elsa.Samples.CompositeActivitiesConsole.Activities; + +namespace Elsa.Samples.CompositeActivitiesConsole.Workflows +{ + /// + /// A basic workflow with just one WriteLine activity. + /// + public class CompositionWorkflow : IWorkflow + { + public void Build(IWorkflowBuilder workflow) => workflow + .WriteLine("Welcome to the Composite Activities demo workflow!") + .Then() + .WriteLine("Done!"); + } +} \ No newline at end of file diff --git a/src/samples/Elsa.Samples.DistributedLock/Program.cs b/src/samples/Elsa.Samples.DistributedLock/Program.cs index 887551668..4aecd7db9 100644 --- a/src/samples/Elsa.Samples.DistributedLock/Program.cs +++ b/src/samples/Elsa.Samples.DistributedLock/Program.cs @@ -2,7 +2,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using NodaTime; using System.Threading.Tasks; -using Elsa.DistributedLocking.Redis; using YesSql.Provider.Sqlite; namespace Elsa.Samples.DistributedLock diff --git a/test/component/Elsa.ComponentTests/Endpoints/WorkflowDefinitions/PostTests.cs b/test/component/Elsa.ComponentTests/Endpoints/WorkflowDefinitions/PostTests.cs index a78f17c82..fca91a478 100644 --- a/test/component/Elsa.ComponentTests/Endpoints/WorkflowDefinitions/PostTests.cs +++ b/test/component/Elsa.ComponentTests/Endpoints/WorkflowDefinitions/PostTests.cs @@ -5,10 +5,10 @@ using System.Threading.Tasks; using AutoFixture; using Elsa.Activities.Console; using Elsa.ComponentTests.Helpers; -using Elsa.Core.IntegrationTests.Helpers; using Elsa.Models; using Elsa.Server.Api.Endpoints.WorkflowDefinitions; using Elsa.Testing.Shared.AutoFixture; +using Elsa.Testing.Shared.Helpers; using Xunit; namespace Elsa.ComponentTests.Endpoints.WorkflowDefinitions diff --git a/test/shared/Elsa.Testing.Shared/Helpers/TemporaryFolder.cs b/test/shared/Elsa.Testing.Shared/Helpers/TemporaryFolder.cs index 513e60145..092527faf 100644 --- a/test/shared/Elsa.Testing.Shared/Helpers/TemporaryFolder.cs +++ b/test/shared/Elsa.Testing.Shared/Helpers/TemporaryFolder.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace Elsa.Core.IntegrationTests.Helpers +namespace Elsa.Testing.Shared.Helpers { public class TemporaryFolder : IDisposable { diff --git a/test/shared/Elsa.Testing.Shared/Helpers/WorkflowsTestBase.cs b/test/shared/Elsa.Testing.Shared/Helpers/WorkflowsTestBase.cs index 38b60da00..538362f9c 100644 --- a/test/shared/Elsa.Testing.Shared/Helpers/WorkflowsTestBase.cs +++ b/test/shared/Elsa.Testing.Shared/Helpers/WorkflowsTestBase.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Elsa.Builders; -using Elsa.Core.IntegrationTests.Helpers; using Elsa.Services; using Elsa.Triggers; using Microsoft.Extensions.DependencyInjection; diff --git a/test/shared/Elsa.Testing.Shared/Helpers/XunitConsoleForwarder.cs b/test/shared/Elsa.Testing.Shared/Helpers/XunitConsoleForwarder.cs index 6bf20f966..4443a1852 100644 --- a/test/shared/Elsa.Testing.Shared/Helpers/XunitConsoleForwarder.cs +++ b/test/shared/Elsa.Testing.Shared/Helpers/XunitConsoleForwarder.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using Xunit.Abstractions; -namespace Elsa.Core.IntegrationTests.Helpers +namespace Elsa.Testing.Shared.Helpers { public class XunitConsoleForwarder : TextWriter {