diff --git a/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs b/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs index ddfca040f..8b3f99a88 100644 --- a/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs +++ b/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs @@ -9,7 +9,7 @@ namespace Elsa.Builders { public interface IActivityBuilder : IBuilder { - IWorkflowBuilder WorkflowBuilder { get; } + ICompositeActivityBuilder WorkflowBuilder { get; } public Type ActivityType { get; } string ActivityId { get; set; } public string? Name { get; set; } @@ -23,6 +23,6 @@ namespace Elsa.Builders IActivityBuilder WithId(string? id); IActivityBuilder WithName(string? name); Func> BuildActivityAsync(); - IWorkflowBlueprint Build(); + //IWorkflowBlueprint Build(); } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Builders/ICompositeActivity.cs b/src/core/Elsa.Abstractions/Builders/ICompositeActivity.cs new file mode 100644 index 000000000..a8c510186 --- /dev/null +++ b/src/core/Elsa.Abstractions/Builders/ICompositeActivity.cs @@ -0,0 +1,11 @@ +namespace Elsa.Builders +{ + public interface ICompositeActivity where TBuilder: ICompositeActivityBuilder + { + void Build(TBuilder compositeActivity); + } + + public interface ICompositeActivity : ICompositeActivity + { + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Builders/ICompositeActivityBuilder.cs b/src/core/Elsa.Abstractions/Builders/ICompositeActivityBuilder.cs new file mode 100644 index 000000000..2f689af30 --- /dev/null +++ b/src/core/Elsa.Abstractions/Builders/ICompositeActivityBuilder.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using Elsa.Services; +using Elsa.Services.Models; + +namespace Elsa.Builders +{ + public interface ICompositeActivityBuilder : IActivityBuilder + { + IServiceProvider ServiceProvider { get; } + IReadOnlyCollection Activities { get; } + + IActivityBuilder New( + Action? branch = default, + IDictionary? propertyValueProviders = default) + where T : class, IActivity; + + IActivityBuilder New( + Action>? setup, + Action? branch = default) where T : class, IActivity; + + IActivityBuilder StartWith( + Action>? setup, + Action? branch = default) where T : class, IActivity; + + IActivityBuilder StartWith(Action? branch = default) + where T : class, IActivity; + + IActivityBuilder Add( + Action>? setup = default, + Action? branch = default) where T : class, IActivity; + + IActivityBuilder Add( + Action? branch = default, + IDictionary? propertyValueProviders = default) + where T : class, IActivity; + + IConnectionBuilder Connect( + IActivityBuilder source, + IActivityBuilder target, + string outcome = OutcomeNames.Done); + + IConnectionBuilder Connect( + Func source, + Func target, + string outcome = OutcomeNames.Done); + + ICompositeActivityBlueprint Build(string activityIdPrefix = "activity"); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Builders/IConnectionBuilder.cs b/src/core/Elsa.Abstractions/Builders/IConnectionBuilder.cs index 21d990350..9b02d3f0b 100644 --- a/src/core/Elsa.Abstractions/Builders/IConnectionBuilder.cs +++ b/src/core/Elsa.Abstractions/Builders/IConnectionBuilder.cs @@ -4,7 +4,7 @@ namespace Elsa.Builders { public interface IConnectionBuilder { - IWorkflowBuilder WorkflowBuilder { get; } + ICompositeActivityBuilder WorkflowBuilder { get; } Func Source { get; } Func Target{ get; } string Outcome { get; } diff --git a/src/core/Elsa.Abstractions/Builders/IOutcomeBuilder.cs b/src/core/Elsa.Abstractions/Builders/IOutcomeBuilder.cs index 5da605d56..82440ee99 100644 --- a/src/core/Elsa.Abstractions/Builders/IOutcomeBuilder.cs +++ b/src/core/Elsa.Abstractions/Builders/IOutcomeBuilder.cs @@ -4,7 +4,7 @@ namespace Elsa.Builders { public interface IOutcomeBuilder : IBuilder { - IWorkflowBuilder WorkflowBuilder { get; } + ICompositeActivityBuilder WorkflowBuilder { get; } IActivityBuilder Source { get; } string? Outcome { get; } IConnectionBuilder Then(string activityName); diff --git a/src/core/Elsa.Abstractions/Builders/IWorkflow.cs b/src/core/Elsa.Abstractions/Builders/IWorkflow.cs index f7912b150..cca5065dc 100644 --- a/src/core/Elsa.Abstractions/Builders/IWorkflow.cs +++ b/src/core/Elsa.Abstractions/Builders/IWorkflow.cs @@ -1,7 +1,6 @@ namespace Elsa.Builders { - public interface IWorkflow + public interface IWorkflow : ICompositeActivity { - void Build(IWorkflowBuilder workflow); } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs b/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs index e01ff5f9c..2b8612b1a 100644 --- a/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs +++ b/src/core/Elsa.Abstractions/Builders/IWorkflowBuilder.cs @@ -1,24 +1,15 @@ using System; -using System.Collections.Generic; using Elsa.Models; -using Elsa.Services; using Elsa.Services.Models; namespace Elsa.Builders { - public interface IWorkflowBuilder : IBuilder + public interface IWorkflowBuilder : ICompositeActivityBuilder { - string Id { get; } - string Name { get; } - string Description { get; } int Version { get; } WorkflowPersistenceBehavior PersistenceBehavior { get; } bool DeleteCompletedInstances { get; } bool IsEnabled { get; } - IServiceProvider ServiceProvider { get; } - IReadOnlyCollection Activities { get; } - IWorkflowBuilder WithId(string value); - IWorkflowBuilder WithName(string value); IWorkflowBuilder WithDescription(string value); IWorkflowBuilder WithVersion(int value); IWorkflowBuilder AsSingleton(); @@ -29,42 +20,8 @@ namespace Elsa.Builders IWorkflowBuilder WithPersistenceBehavior(WorkflowPersistenceBehavior value); IWorkflowBuilder Enable(bool value); - IActivityBuilder New( - Action? branch = default, - IDictionary? propertyValueProviders = default) - where T : class, IActivity; - - IActivityBuilder New( - Action>? setup, - Action? branch = default) where T : class, IActivity; - - IActivityBuilder StartWith( - Action>? setup, - Action? branch = default) where T : class, IActivity; - - IActivityBuilder StartWith(Action? branch = default) - where T : class, IActivity; - - IActivityBuilder Add( - Action>? setup = default, - Action? branch = default) where T : class, IActivity; - - IActivityBuilder Add( - Action? branch = default, - IDictionary? propertyValueProviders = default) - where T : class, IActivity; - - IConnectionBuilder Connect( - IActivityBuilder source, - IActivityBuilder target, - string outcome = OutcomeNames.Done); - - IConnectionBuilder Connect( - Func source, - Func target, - string outcome = OutcomeNames.Done); - - IWorkflowBlueprint Build(string activityIdPrefix = "activity"); + IWorkflowBlueprint BuildBlueprint(string activityIdPrefix = "activity"); + // 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; diff --git a/src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs b/src/core/Elsa.Abstractions/Extensions/CompositeActivityBlueprintExtensions.cs similarity index 56% rename from src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs rename to src/core/Elsa.Abstractions/Extensions/CompositeActivityBlueprintExtensions.cs index 3fb3ddea4..cee7a457b 100644 --- a/src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs +++ b/src/core/Elsa.Abstractions/Extensions/CompositeActivityBlueprintExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Elsa.Models; @@ -7,36 +7,9 @@ using Elsa.Services.Models; namespace Elsa { - public static class WorkflowExtensions + public static class CompositeActivityBlueprintExtensions { - public static IEnumerable WithVersion( - this IEnumerable query, - VersionOptions version) => - query.AsQueryable().WithVersion(version); - - public static IQueryable WithVersion( - this IQueryable query, - VersionOptions version) - { - if (version.IsDraft) - query = query.Where(x => !x.IsPublished); - else if (version.IsLatest) - query = query.Where(x => x.IsLatest); - else if (version.IsPublished) - query = query.Where(x => x.IsPublished); - else if (version.IsLatestOrPublished) - query = query.Where(x => x.IsPublished || x.IsLatest); - else if (version.AllVersions) - { - // Nothing to filter. - } - else if (version.Version > 0) - query = query.Where(x => x.Version == version.Version); - - return query.OrderByDescending(x => x.Version); - } - - public static IEnumerable GetStartActivities(this IWorkflowBlueprint workflowBlueprint) + public static IEnumerable GetStartActivities(this ICompositeActivityBlueprint workflowBlueprint) { var targetActivityIds = workflowBlueprint.Connections.Select(x => x.Target.Activity.Id).Distinct() .ToLookup(x => x); @@ -49,22 +22,22 @@ namespace Elsa return query; } - public static IEnumerable GetStartActivities(this IWorkflowBlueprint workflowBlueprint, string activityType) => workflowBlueprint.GetStartActivities().Where(x => x.Type == activityType); - public static IEnumerable GetStartActivities(this IWorkflowBlueprint workflowBlueprint, Type activityType) => workflowBlueprint.GetStartActivities(activityType.Name); - public static IEnumerable GetStartActivities(this IWorkflowBlueprint workflowBlueprint) where T : IActivity => workflowBlueprint.GetStartActivities(typeof(T)); - public static IActivityBlueprint? GetActivity(this IWorkflowBlueprint workflowBlueprint, string id) => workflowBlueprint.Activities.FirstOrDefault(x => x.Id == id); - public static IEnumerable GetActivities(this IWorkflowBlueprint workflowBlueprint, IEnumerable ids) => workflowBlueprint.Activities.Where(x => ids.Contains(x.Id)); + public static IEnumerable GetStartActivities(this ICompositeActivityBlueprint workflowBlueprint, string activityType) => workflowBlueprint.GetStartActivities().Where(x => x.Type == activityType); + public static IEnumerable GetStartActivities(this ICompositeActivityBlueprint workflowBlueprint, Type activityType) => workflowBlueprint.GetStartActivities(activityType.Name); + public static IEnumerable GetStartActivities(this ICompositeActivityBlueprint workflowBlueprint) where T : IActivity => workflowBlueprint.GetStartActivities(typeof(T)); + public static IActivityBlueprint? GetActivity(this ICompositeActivityBlueprint workflowBlueprint, string id) => workflowBlueprint.Activities.FirstOrDefault(x => x.Id == id); + public static IEnumerable GetActivities(this ICompositeActivityBlueprint workflowBlueprint, IEnumerable ids) => workflowBlueprint.Activities.Where(x => ids.Contains(x.Id)); - public static IEnumerable GetBlockingActivities(this IWorkflowBlueprint workflowBlueprint, WorkflowInstance workflowInstance) => + public static IEnumerable GetBlockingActivities(this ICompositeActivityBlueprint workflowBlueprint, WorkflowInstance workflowInstance) => workflowBlueprint.Activities.Where(activity => workflowInstance.BlockingActivities.Select(x => x.ActivityId).Contains(activity.Id)); public static IEnumerable GetInboundConnections( - this IWorkflowBlueprint workflowBlueprint, + this ICompositeActivityBlueprint workflowBlueprint, string activityId) => workflowBlueprint.Connections.Where(x => x.Target.Activity.Id == activityId).ToList(); public static IEnumerable GetOutboundConnections( - this IWorkflowBlueprint workflowBlueprint, + this ICompositeActivityBlueprint workflowBlueprint, string activityId) => workflowBlueprint.Connections.Where(x => x.Source.Activity.Id == activityId).ToList(); @@ -72,7 +45,7 @@ namespace Elsa /// Returns the full path of incoming activities. /// public static IEnumerable GetInboundActivityPath( - this IWorkflowBlueprint workflowBlueprint, + this ICompositeActivityBlueprint workflowBlueprint, string activityId) { var inspectedActivityIds = new HashSet(); @@ -82,7 +55,7 @@ namespace Elsa } private static IEnumerable GetInboundActivityPathInternal( - this IWorkflowBlueprint workflowBlueprintBlueprintInstance, + this ICompositeActivityBlueprint workflowBlueprintBlueprintInstance, string activityId, HashSet inspectedActivityIds) { diff --git a/src/core/Elsa.Abstractions/Extensions/WorkflowBlueprintExtensions.cs b/src/core/Elsa.Abstractions/Extensions/WorkflowBlueprintExtensions.cs index be2a3ee0a..7e1aa0e22 100644 --- a/src/core/Elsa.Abstractions/Extensions/WorkflowBlueprintExtensions.cs +++ b/src/core/Elsa.Abstractions/Extensions/WorkflowBlueprintExtensions.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; +using Elsa.Models; using Elsa.Services; using Elsa.Services.Models; using Newtonsoft.Json; @@ -59,5 +62,32 @@ namespace Elsa var serializer = activityExecutionContext.GetService(); return activity.Data[propertyName]!.ToObject(serializer)!; } + + public static IEnumerable WithVersion( + this IEnumerable query, + VersionOptions version) => + query.AsQueryable().WithVersion(version); + + public static IQueryable WithVersion( + this IQueryable query, + VersionOptions version) + { + if (version.IsDraft) + query = query.Where(x => !x.IsPublished); + else if (version.IsLatest) + query = query.Where(x => x.IsLatest); + else if (version.IsPublished) + query = query.Where(x => x.IsPublished); + else if (version.IsLatestOrPublished) + query = query.Where(x => x.IsPublished || x.IsLatest); + else if (version.AllVersions) + { + // Nothing to filter. + } + else if (version.Version > 0) + query = query.Where(x => x.Version == version.Version); + + return query.OrderByDescending(x => x.Version); + } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs index ea80b6b45..5c1be9711 100644 --- a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs +++ b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using Elsa.ActivityResults; using Elsa.Builders; using Elsa.Services.Models; @@ -8,11 +7,12 @@ namespace Elsa.Services { public abstract class CompositeActivity : Activity { - public abstract void Build(IWorkflowBuilder workflowBuilder); + public abstract void Build(ICompositeActivityBuilder composite); protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) { - var startActivities = context.ActivityBlueprint.ChildWorkflow!.GetStartActivities().Select(x => x.Id).ToList(); + var compositeActivityBlueprint = (ICompositeActivityBlueprint)context.ActivityBlueprint; + var startActivities = compositeActivityBlueprint.GetStartActivities().Select(x => x.Id).ToList(); return Combine(Done(), Schedule(startActivities, null!)); } } diff --git a/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs index eeb63d3f1..b704d1d51 100644 --- a/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs +++ b/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs @@ -28,8 +28,7 @@ namespace Elsa.Services.Models 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!; } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/CompositeActivityBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/CompositeActivityBlueprint.cs new file mode 100644 index 000000000..a7d11998a --- /dev/null +++ b/src/core/Elsa.Abstractions/Services/Models/CompositeActivityBlueprint.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Elsa.Services.Models +{ + public class CompositeActivityBlueprint : ActivityBlueprint, ICompositeActivityBlueprint + { + public CompositeActivityBlueprint() + { + Activities = new List(); + Connections = new List(); + ActivityPropertyProviders = new ActivityPropertyProviders(); + } + + public CompositeActivityBlueprint( + string id, + string? name, + string type, + bool persistWorkflow, + Func> createActivity) : base(id, name, type, persistWorkflow, createActivity) + { + } + + public ICollection Activities { get; set; } = default!; + public ICollection Connections { get; set; }= default!; + public IActivityPropertyProviders ActivityPropertyProviders { get; set; } = default!; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs index 305d0b8f6..6336ff7d5 100644 --- a/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs +++ b/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs @@ -11,6 +11,5 @@ 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.Abstractions/Services/Models/ICompositeActivityBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/ICompositeActivityBlueprint.cs new file mode 100644 index 000000000..6f29e7ab8 --- /dev/null +++ b/src/core/Elsa.Abstractions/Services/Models/ICompositeActivityBlueprint.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Elsa.Services.Models +{ + public interface ICompositeActivityBlueprint : IActivityBlueprint + { + public ICollection Activities { get; set; } + + public ICollection Connections { get; } + IActivityPropertyProviders ActivityPropertyProviders { get; } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/IWorkflowBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/IWorkflowBlueprint.cs index a7059e25a..394c40ded 100644 --- a/src/core/Elsa.Abstractions/Services/Models/IWorkflowBlueprint.cs +++ b/src/core/Elsa.Abstractions/Services/Models/IWorkflowBlueprint.cs @@ -1,12 +1,9 @@ -using System.Collections.Generic; -using Elsa.Models; +using Elsa.Models; namespace Elsa.Services.Models { - public interface IWorkflowBlueprint + public interface IWorkflowBlueprint : ICompositeActivityBlueprint { - public string? Name { get; } - public string Id { get; } public int Version { get; set; } public bool IsSingleton { get; } public bool IsEnabled { get; } @@ -21,8 +18,6 @@ namespace Elsa.Services.Models public WorkflowContextOptions? ContextOptions { get; set; } public WorkflowPersistenceBehavior PersistenceBehavior { get; } public bool DeleteCompletedInstances { get; } - public ICollection Activities { get; } - public ICollection Connections { get; } IActivityPropertyProviders ActivityPropertyProviders { get; } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/WorkflowBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/WorkflowBlueprint.cs index c756f1035..9123d33f6 100644 --- a/src/core/Elsa.Abstractions/Services/Models/WorkflowBlueprint.cs +++ b/src/core/Elsa.Abstractions/Services/Models/WorkflowBlueprint.cs @@ -4,12 +4,10 @@ using Elsa.Models; namespace Elsa.Services.Models { - public class WorkflowBlueprint : IWorkflowBlueprint + public class WorkflowBlueprint : CompositeActivityBlueprint, IWorkflowBlueprint { public WorkflowBlueprint() { - Activities = new List(); - Connections = new List(); ActivityPropertyProviders = new ActivityPropertyProviders(); Variables = new Variables(); } @@ -29,7 +27,7 @@ namespace Elsa.Services.Models bool deleteCompletedInstances, IEnumerable activities, IEnumerable connections, - IActivityPropertyProviders activityPropertyValueProviders) + IActivityPropertyProviders activityPropertyValueProviders) : base(id, name, id, true, null!) { Id = id; Version = version; @@ -47,12 +45,10 @@ namespace Elsa.Services.Models Connections = connections.ToList(); ActivityPropertyProviders = activityPropertyValueProviders; } - - public string Id { get; set; } = default!; + public int Version { get; set; } public bool IsSingleton { get; set; } public bool IsEnabled { get; set; } - public string? Name { get; set; } public string? Description { get; set; } public bool IsPublished { get; set; } public bool IsLatest { get; set; } @@ -60,10 +56,5 @@ namespace Elsa.Services.Models public WorkflowContextOptions? ContextOptions { get; set; } public WorkflowPersistenceBehavior PersistenceBehavior { get; set; } public bool DeleteCompletedInstances { get; set; } - public ICollection Activities { get; set; } - - public ICollection Connections { get; set; } - - public IActivityPropertyProviders ActivityPropertyProviders { get; set; } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Builders/ActivityBuilder.cs b/src/core/Elsa.Core/Builders/ActivityBuilder.cs index ba65a3426..c4057b268 100644 --- a/src/core/Elsa.Core/Builders/ActivityBuilder.cs +++ b/src/core/Elsa.Core/Builders/ActivityBuilder.cs @@ -12,7 +12,7 @@ namespace Elsa.Builders { public ActivityBuilder( Type activityType, - IWorkflowBuilder workflowBuilder, + ICompositeActivityBuilder workflowBuilder, IDictionary? propertyValueProviders) { ActivityType = activityType; @@ -20,8 +20,8 @@ namespace Elsa.Builders PropertyValueProviders = propertyValueProviders; } - public Type ActivityType { get; } - public IWorkflowBuilder WorkflowBuilder { get; } + public Type ActivityType { get; } = null!; + public ICompositeActivityBuilder WorkflowBuilder { get; } = default!; public string ActivityId { get; set; } = default!; public string? Name { get; set; } public string? Description { get; set; } @@ -79,6 +79,6 @@ namespace Elsa.Builders return activity; }; - public IWorkflowBlueprint Build() => WorkflowBuilder.Build(); + //public IWorkflowBlueprint Build() => WorkflowBuilder.Build(); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs b/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs new file mode 100644 index 000000000..f465338e0 --- /dev/null +++ b/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Elsa.Services; +using Elsa.Services.Models; +using Microsoft.Extensions.DependencyInjection; +using NetBox.Extensions; + +namespace Elsa.Builders +{ + public class CompositeActivityBuilder : ActivityBuilder, ICompositeActivityBuilder + { + private readonly IActivityActivator _activityActivator; + private readonly Func _workflowBuilderFactory; + + public CompositeActivityBuilder( + IIdGenerator idGenerator, + IActivityActivator activityActivator, + IServiceProvider serviceProvider) : base() + { + _activityActivator = activityActivator; + ServiceProvider = serviceProvider; + ActivityId = idGenerator.Generate(); + ActivityBuilders = new List(); + ConnectionBuilders = new List(); + _workflowBuilderFactory = serviceProvider.GetRequiredService; + } + + public IServiceProvider ServiceProvider { get; } + protected IList ActivityBuilders { get; } + protected IList ConnectionBuilders { get; } + + public IReadOnlyCollection Activities => ActivityBuilders.ToList().AsReadOnly(); + + public IActivityBuilder New(Type activityType, IDictionary? propertyValueProviders = default) + { + var activityBuilder = new ActivityBuilder(activityType, this, propertyValueProviders); + return activityBuilder; + } + + public IActivityBuilder New( + Action? branch = default, + IDictionary? propertyValueProviders = default) + where T : class, IActivity => + New(typeof(T), propertyValueProviders); + + public IActivityBuilder New( + Action>? setup = default, + Action? branch = default) where T : class, IActivity + { + var propertyValuesBuilder = new SetupActivity(); + setup?.Invoke(propertyValuesBuilder); + + var valueProviders = propertyValuesBuilder.ValueProviders.ToDictionary( + x => x.Key, + x => (IActivityPropertyValueProvider)new DelegateActivityPropertyValueProvider(x.Value)); + + return New(branch, valueProviders); + } + + public IActivityBuilder StartWith( + Action>? setup = default, + Action? branch = default) where T : class, IActivity + { + var activityBuilder = New(setup, branch); + return Add(activityBuilder, branch); + } + + public IActivityBuilder StartWith(Action? branch = default) + where T : class, IActivity => + Add(branch); + + public IActivityBuilder Add( + Action>? setup = default, + Action? branch = default) where T : class, IActivity + { + var activityBuilder = New(setup, branch); + return Add(activityBuilder, branch); + } + + public IActivityBuilder Add( + Action? branch = default, + IDictionary? propertyValueProviders = default) + where T : class, IActivity + { + var activityBuilder = new ActivityBuilder(typeof(T), this, propertyValueProviders); + return Add(activityBuilder); + } + + public IActivityBuilder Add( + IActivityBuilder activityBuilder, + Action? branch = default) + { + branch?.Invoke(activityBuilder); + ActivityBuilders.Add(activityBuilder); + return activityBuilder; + } + + public IConnectionBuilder Connect( + IActivityBuilder source, + IActivityBuilder target, + string outcome = OutcomeNames.Done) => + Connect(() => source, () => target, outcome); + + + public IConnectionBuilder Connect( + Func source, + Func target, + string outcome = OutcomeNames.Done) + { + var connectionBuilder = new ConnectionBuilder(this, source, target, outcome); + ConnectionBuilders.Add(connectionBuilder); + return connectionBuilder; + } + + public ICompositeActivityBlueprint Build(string activityIdPrefix = "activity") + { + 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 = $"{activityIdPrefix}-{++index}"; + + activityBlueprints.AddRange(ActivityBuilders.Select(BuildActivityBlueprint)); + + // Build composite activities. + var compositeActivityBuilders = ActivityBuilders.Where(x => x is ICompositeActivityBuilder).Cast(); + BuildCompositeActivities(compositeActivityBuilders, activityBlueprints, connections, activityPropertyProviders); + + var activityBlueprintDictionary = activityBlueprints.ToDictionary(x => x.Id); + + connections.AddRange(ConnectionBuilders.Select(x => new Connection(activityBlueprintDictionary[x.Source().ActivityId], activityBlueprintDictionary[x.Target().ActivityId], x.Outcome))); + + activityPropertyProviders.AddRange( + ActivityBuilders + .Select(x => (x.ActivityId, x.PropertyValueProviders)) + .ToDictionary(x => x.ActivityId!, x => x.PropertyValueProviders!)); + + var workflow = new CompositeActivityBlueprint + { + Connections = connections, + Activities = activityBlueprints, + ActivityPropertyProviders = new ActivityPropertyProviders(activityPropertyProviders) + }; + + return workflow; + } + + private void BuildCompositeActivities( + IEnumerable activityBuilders, + ICollection activityBlueprints, + ICollection connections, + IDictionary> activityPropertyProviders) + { + foreach (var activityBuilder in activityBuilders) + { + var compositeActivity = (CompositeActivity)_activityActivator.ActivateActivity(activityBuilder.ActivityType.Name); + var workflowBuilder = _workflowBuilderFactory(); + + compositeActivity.Build(workflowBuilder); + + var workflow = workflowBuilder.Build($"activity-{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 = (ICompositeActivityBlueprint)activityBlueprints.Single(x => x.Id == activityBuilder.ActivityId); + compositeActivityBlueprint.Activities = workflow.Activities; + } + } + + private IActivityBlueprint BuildActivityBlueprint(IActivityBuilder builder, int index) + { + var isComposite = typeof(CompositeActivity).IsAssignableFrom(builder.ActivityType); + return isComposite + ? new CompositeActivityBlueprint(builder.ActivityId, builder.Name, builder.ActivityType.Name, builder.PersistWorkflow, builder.BuildActivityAsync()) + : new ActivityBlueprint(builder.ActivityId, builder.Name, builder.ActivityType.Name, builder.PersistWorkflow, builder.BuildActivityAsync()); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Builders/ConnectionBuilder.cs b/src/core/Elsa.Core/Builders/ConnectionBuilder.cs index 1e270737f..c62de7344 100644 --- a/src/core/Elsa.Core/Builders/ConnectionBuilder.cs +++ b/src/core/Elsa.Core/Builders/ConnectionBuilder.cs @@ -4,12 +4,12 @@ namespace Elsa.Builders { public class ConnectionBuilder : IConnectionBuilder { - public IWorkflowBuilder WorkflowBuilder { get; } + public ICompositeActivityBuilder WorkflowBuilder { get; } public Func Source { get; } public Func Target { get; } public string Outcome { get; } - public ConnectionBuilder(IWorkflowBuilder workflowBuilder, Func source, Func target, string outcome = OutcomeNames.Done) + public ConnectionBuilder(ICompositeActivityBuilder workflowBuilder, Func source, Func target, string outcome = OutcomeNames.Done) { Source = source; Target = target; diff --git a/src/core/Elsa.Core/Builders/OutcomeBuilder.cs b/src/core/Elsa.Core/Builders/OutcomeBuilder.cs index 35fb8b172..10f664b78 100644 --- a/src/core/Elsa.Core/Builders/OutcomeBuilder.cs +++ b/src/core/Elsa.Core/Builders/OutcomeBuilder.cs @@ -7,14 +7,14 @@ namespace Elsa.Builders { public class OutcomeBuilder : IOutcomeBuilder { - public OutcomeBuilder(IWorkflowBuilder workflowBuilder, IActivityBuilder source, string outcome = "Done") + public OutcomeBuilder(ICompositeActivityBuilder workflowBuilder, IActivityBuilder source, string outcome = "Done") { WorkflowBuilder = workflowBuilder; Source = source; Outcome = outcome; } - public IWorkflowBuilder WorkflowBuilder { get; } + public ICompositeActivityBuilder WorkflowBuilder { get; } public IActivityBuilder Source { get; } public string Outcome { get; } @@ -41,6 +41,6 @@ namespace Elsa.Builders return activityBuilder; } - public IWorkflowBlueprint Build() => WorkflowBuilder.Build(); + public IWorkflowBlueprint Build() => ((IWorkflowBuilder)WorkflowBuilder).BuildBlueprint(); } } \ 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 6a2c9ba67..98506932b 100644 --- a/src/core/Elsa.Core/Builders/WorkflowBuilder.cs +++ b/src/core/Elsa.Core/Builders/WorkflowBuilder.cs @@ -1,40 +1,23 @@ using System; -using System.Collections.Generic; -using System.Linq; using Elsa.Models; using Elsa.Services; using Elsa.Services.Models; using Microsoft.Extensions.DependencyInjection; -using NetBox.Extensions; namespace Elsa.Builders { - public class WorkflowBuilder : IWorkflowBuilder + public class WorkflowBuilder : CompositeActivityBuilder, IWorkflowBuilder { - private readonly IIdGenerator _idGenerator; - private readonly IActivityActivator _activityActivator; - private readonly Func _workflowBuilderFactory; - private readonly IList _activityBuilders; - private readonly IList _connectionBuilders; - - public WorkflowBuilder(IIdGenerator idGenerator, IActivityActivator activityActivator, Func workflowBuilderFactory, IServiceProvider serviceProvider) + public WorkflowBuilder(IIdGenerator idGenerator, IActivityActivator activityActivator, IServiceProvider serviceProvider) : base( + idGenerator, + activityActivator, + serviceProvider) { - _idGenerator = idGenerator; - _activityActivator = activityActivator; - _workflowBuilderFactory = workflowBuilderFactory; - ServiceProvider = serviceProvider; - Id = idGenerator.Generate(); Version = 1; IsEnabled = true; Variables = new Variables(); - _activityBuilders = new List(); - _connectionBuilders = new List(); } - public IServiceProvider ServiceProvider { get; } - public string Id { get; private set; } - public string? Name { get; private set; } - public string? Description { get; private set; } public int Version { get; private set; } public bool IsSingleton { get; private set; } @@ -43,19 +26,6 @@ namespace Elsa.Builders public WorkflowPersistenceBehavior PersistenceBehavior { get; private set; } public bool DeleteCompletedInstances { get; private set; } public bool IsEnabled { get; private set; } - public IReadOnlyCollection Activities => _activityBuilders.ToList().AsReadOnly(); - - public IWorkflowBuilder WithId(string value) - { - Id = value; - return this; - } - - public IWorkflowBuilder WithName(string? value) - { - Name = value; - return this; - } public IWorkflowBuilder WithDescription(string? value) { @@ -121,137 +91,27 @@ namespace Elsa.Builders return this; } - public IActivityBuilder New( - Type activityType, - IDictionary? propertyValueProviders = default) - { - var activityBuilder = new ActivityBuilder(activityType, this, propertyValueProviders); - return activityBuilder; - } - - public IActivityBuilder New( - Action? branch = default, - IDictionary? propertyValueProviders = default) - where T : class, IActivity => - New(typeof(T), propertyValueProviders); - - public IActivityBuilder New( - Action>? setup = default, - Action? branch = default) where T : class, IActivity - { - var propertyValuesBuilder = new SetupActivity(); - setup?.Invoke(propertyValuesBuilder); - - var valueProviders = propertyValuesBuilder.ValueProviders.ToDictionary( - x => x.Key, - x => (IActivityPropertyValueProvider)new DelegateActivityPropertyValueProvider(x.Value)); - - return New(branch, valueProviders); - } - - public IActivityBuilder StartWith( - Action>? setup = default, - Action? branch = default) where T : class, IActivity - { - var activityBuilder = New(setup, branch); - return Add(activityBuilder, branch); - } - - public IActivityBuilder StartWith(Action? branch = default) - where T : class, IActivity => - Add(branch); - - public IActivityBuilder Add( - Action>? setup = default, - Action? branch = default) where T : class, IActivity - { - var activityBuilder = New(setup, branch); - return Add(activityBuilder, branch); - } - - public IActivityBuilder Add( - Action? branch = default, - IDictionary? propertyValueProviders = default) - where T : class, IActivity - { - var activityBuilder = new ActivityBuilder(typeof(T), this, propertyValueProviders); - return Add(activityBuilder); - } - - public IActivityBuilder Add( - IActivityBuilder activityBuilder, - Action? branch = default) - { - branch?.Invoke(activityBuilder); - _activityBuilders.Add(activityBuilder); - return activityBuilder; - } - - public IConnectionBuilder Connect( - IActivityBuilder source, - IActivityBuilder target, - string outcome = OutcomeNames.Done) => - Connect(() => source, () => target, outcome); - - - public IConnectionBuilder Connect( - Func source, - Func target, - string outcome = OutcomeNames.Done) - { - var connectionBuilder = new ConnectionBuilder(this, source, target, outcome); - _connectionBuilders.Add(connectionBuilder); - return connectionBuilder; - } - - public IActivityBuilder Then( - Action>? setup = default, - Action? branch = default) - where T : class, IActivity - { - var activityBuilder = New(setup, branch); - return Add(activityBuilder, branch); - } - - public IActivityBuilder Then(Action? branch = default) - where T : class, IActivity => - StartWith(branch); - - public IWorkflowBlueprint Build(IWorkflow workflow, string activityIdPrefix = "activity") + public IWorkflowBlueprint Build(IWorkflow workflow, string activityIdPrefix) { WithId(workflow.GetType().Name); workflow.Build(this); - return Build(activityIdPrefix); + return BuildBlueprint(activityIdPrefix); + } + + public IWorkflowBlueprint Build(Type workflowType, string activityIdPrefix) + { + var workflow = (IWorkflow)ActivatorUtilities.GetServiceOrCreateInstance(ServiceProvider, workflowType); + return Build(workflow, activityIdPrefix); } - public IWorkflowBlueprint Build(string activityIdPrefix = "activity") + public IWorkflowBlueprint Build(string activityIdPrefix) where T : IWorkflow => Build(typeof(T), activityIdPrefix); + + public IWorkflowBlueprint BuildBlueprint(string activityIdPrefix) { - 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 = $"{activityIdPrefix}-{++index}"; + var compositeRoot = base.Build(activityIdPrefix); - activityBlueprints.AddRange(_activityBuilders.Select(BuildActivityBlueprint)); - - // Build composite activities. - BuildCompositeActivities(_activityBuilders, activityBlueprints, connections, activityPropertyProviders); - - var activityBlueprintDictionary = activityBlueprints.ToDictionary(x => x.Id); - - connections.AddRange(_connectionBuilders.Select(x => new Connection(activityBlueprintDictionary[x.Source().ActivityId], activityBlueprintDictionary[x.Target().ActivityId], x.Outcome))); - - activityPropertyProviders.AddRange(_activityBuilders - .Select(x => (x.ActivityId, x.PropertyValueProviders)) - .ToDictionary(x => x.ActivityId!, x => x.PropertyValueProviders!)); - - var workflow = new WorkflowBlueprint( - definitionId, + return new WorkflowBlueprint( + compositeRoot.Id, Version, IsSingleton, IsEnabled, @@ -263,45 +123,9 @@ namespace Elsa.Builders ContextOptions, PersistenceBehavior, DeleteCompletedInstances, - activityBlueprints, - connections, - new ActivityPropertyProviders(activityPropertyProviders)); - - return workflow; + compositeRoot.Activities, + compositeRoot.Connections, + compositeRoot.ActivityPropertyProviders); } - - private void BuildCompositeActivities(IEnumerable activityBuilders, ICollection activityBlueprints, ICollection connections, IDictionary> activityPropertyProviders) - { - 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(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()); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index 9ef0ae9aa..af70f8c0c 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -116,6 +116,7 @@ namespace Microsoft.Extensions.DependencyInjection .AddWorkflowProvider() .AddWorkflowProvider() .AddTransient() + .AddTransient() .AddTransient>(sp => sp.GetRequiredService) .AddAutoMapperProfile() .AddAutoMapperProfile() diff --git a/src/samples/Elsa.Samples.CompositeActivitiesConsole/Activities/CountDownActivity.cs b/src/samples/Elsa.Samples.CompositeActivitiesConsole/Activities/CountDownActivity.cs index 4fe3be5a6..7bb9d90c0 100644 --- a/src/samples/Elsa.Samples.CompositeActivitiesConsole/Activities/CountDownActivity.cs +++ b/src/samples/Elsa.Samples.CompositeActivitiesConsole/Activities/CountDownActivity.cs @@ -6,9 +6,9 @@ namespace Elsa.Samples.CompositeActivitiesConsole.Activities { public class CountDownActivity : CompositeActivity { - public override void Build(IWorkflowBuilder workflowBuilder) + public override void Build(ICompositeActivityBuilder composite) { - workflowBuilder + composite .WriteLine("3!") .WriteLine("2!") .WriteLine("1!");