From 6f455f7e70e4ee60cc9c137b18e38a5666893fc4 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sun, 28 May 2023 11:54:12 +0200 Subject: [PATCH] Update DSL implementation and integration (#4073) --- Elsa.sln | 7 ++ .../Elsa.Features/Implementations/Module.cs | 6 +- src/common/Elsa.Features/Services/IModule.cs | 8 +- .../TestApplicationBuilder.cs | 1 + .../Contracts/IFunctionActivityRegistry.cs | 26 +++- .../VisitBrackets.cs | 9 +- .../VisitObject.cs | 41 ++++-- .../VisitProperty.cs | 24 ++-- .../WorkflowDefinitionBuilderInterpreter.cs | 20 ++- .../Models/FunctionActivityDescriptor.cs | 12 ++ src/modules/Elsa.Dsl/Services/DslEngine.cs | 16 ++- .../Services/FunctionActivityRegistry.cs | 118 ++++++++++-------- .../Common/PersistenceFeatureBase.cs | 6 +- .../Elsa.JavaScript/Elsa.JavaScript.csproj | 2 +- .../Features/JavaScriptFeature.cs | 9 -- .../Services/TypeAliasRegistry.cs | 10 +- .../Models/TypeDefinitionContext.cs | 4 +- .../CommonFunctionsDefinitionProvider.cs | 13 +- .../VariableTypeDefinitionProvider.cs | 2 +- .../JavaScript/ConfigureJavaScriptEngine.cs | 6 +- .../Features/FluentStorageFeature.cs | 4 +- .../BlobStorageProvider.cs | 2 +- .../FluentStorageWorkflowProvider.cs | 110 ++++++++++++++++ ...FluentStorageWorkflowDefinitionProvider.cs | 96 -------------- .../JavaScript/TypeDefinitions/Endpoint.cs | 18 +-- .../Models/ActivityDescriptor.cs | 54 ++++++++ .../Services/ActivityDescriber.cs | 6 +- .../RunJavaScript}/RunJavaScript.cs | 0 .../RunJavaScriptOptionsProvider.cs | 0 .../Elsa.Workflows.Management.csproj | 2 + .../Extensions/ModuleExtensions.cs | 26 +++- .../Features/DslIntegrationFeature.cs | 54 ++++++++ .../Features/JavaScriptIntegrationFeature.cs | 47 +++++++ .../Features/WorkflowManagementFeature.cs | 2 + .../MapActivityDslFunctionsHostedService.cs | 37 ++++++ ...ariableTypesWithJavaScriptHostedService.cs | 36 ++++++ .../Mappers/WorkflowDefinitionMapper.cs | 20 +++ .../Options/DslIntegrationOptions.cs | 14 +++ .../InputFunctionsDefinitionProvider.cs | 46 +++++++ ...nitionProvider.cs => IWorkflowProvider.cs} | 4 +- .../DependencyInjectionExtensions.cs | 5 +- .../Features/WorkflowRuntimeFeature.cs | 6 +- .../PopulateWorkflowDefinitionStore.cs | 77 +++++++----- .../Models/MaterializedWorkflow.cs | 12 ++ .../Models/WorkflowDefinitionResult.cs | 11 -- .../Providers/ClrWorkflowProvider.cs | 58 +++++++++ .../Services/ClrWorkflowDefinitionProvider.cs | 90 ------------- ....Samples.AspNet.DslWorkflowProvider.csproj | 27 ++++ .../Program.cs | 62 +++++++++ .../Properties/launchSettings.json | 37 ++++++ .../Workflows/hello-world-functional.elsa | 8 ++ .../Workflows/hello-world.elsa | 20 +++ .../appsettings.json | 11 ++ 53 files changed, 957 insertions(+), 385 deletions(-) create mode 100644 src/modules/Elsa.Dsl/Models/FunctionActivityDescriptor.cs rename src/modules/Elsa.WorkflowProviders.FluentStorage/{Services => Providers}/BlobStorageProvider.cs (91%) create mode 100644 src/modules/Elsa.WorkflowProviders.FluentStorage/Providers/FluentStorageWorkflowProvider.cs delete mode 100644 src/modules/Elsa.WorkflowProviders.FluentStorage/Services/FluentStorageWorkflowDefinitionProvider.cs rename src/modules/{Elsa.JavaScript/Activities => Elsa.Workflows.Management/Activities/RunJavaScript}/RunJavaScript.cs (100%) rename src/modules/{Elsa.JavaScript/Activities => Elsa.Workflows.Management/Activities/RunJavaScript}/RunJavaScriptOptionsProvider.cs (100%) create mode 100644 src/modules/Elsa.Workflows.Management/Features/DslIntegrationFeature.cs create mode 100644 src/modules/Elsa.Workflows.Management/Features/JavaScriptIntegrationFeature.cs create mode 100644 src/modules/Elsa.Workflows.Management/HostedServices/MapActivityDslFunctionsHostedService.cs create mode 100644 src/modules/Elsa.Workflows.Management/HostedServices/RegisterVariableTypesWithJavaScriptHostedService.cs create mode 100644 src/modules/Elsa.Workflows.Management/Options/DslIntegrationOptions.cs create mode 100644 src/modules/Elsa.Workflows.Management/Scripting/JavaScript/InputFunctionsDefinitionProvider.cs rename src/modules/Elsa.Workflows.Runtime/Contracts/{IWorkflowDefinitionProvider.cs => IWorkflowProvider.cs} (73%) create mode 100644 src/modules/Elsa.Workflows.Runtime/Models/MaterializedWorkflow.cs delete mode 100644 src/modules/Elsa.Workflows.Runtime/Models/WorkflowDefinitionResult.cs create mode 100644 src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowProvider.cs delete mode 100644 src/modules/Elsa.Workflows.Runtime/Services/ClrWorkflowDefinitionProvider.cs create mode 100644 src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Elsa.Samples.AspNet.DslWorkflowProvider.csproj create mode 100644 src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Program.cs create mode 100644 src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Properties/launchSettings.json create mode 100644 src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Workflows/hello-world-functional.elsa create mode 100644 src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Workflows/hello-world.elsa create mode 100644 src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/appsettings.json diff --git a/Elsa.sln b/Elsa.sln index 48812db792..8c078cad5d 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -202,6 +202,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Environments", "src\mo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.EntityFrameworkCore.MySql", "src\modules\Elsa.EntityFrameworkCore.MySql\Elsa.EntityFrameworkCore.MySql.csproj", "{8DC74562-1F06-4AFA-8308-8A779E8812A3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.DslWorkflowProvider", "src\samples\aspnet\Elsa.Samples.AspNet.DslWorkflowProvider\Elsa.Samples.AspNet.DslWorkflowProvider.csproj", "{4BC5D3D2-9425-41D7-9804-D145B92520EE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -516,6 +518,10 @@ Global {8DC74562-1F06-4AFA-8308-8A779E8812A3}.Debug|Any CPU.Build.0 = Debug|Any CPU {8DC74562-1F06-4AFA-8308-8A779E8812A3}.Release|Any CPU.ActiveCfg = Release|Any CPU {8DC74562-1F06-4AFA-8308-8A779E8812A3}.Release|Any CPU.Build.0 = Release|Any CPU + {4BC5D3D2-9425-41D7-9804-D145B92520EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BC5D3D2-9425-41D7-9804-D145B92520EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BC5D3D2-9425-41D7-9804-D145B92520EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BC5D3D2-9425-41D7-9804-D145B92520EE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} @@ -606,5 +612,6 @@ Global {165ACC9D-B67C-4B49-A818-ECFD247C899C} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} {B5CDF747-8066-40D5-9BAE-CBE397BC9B51} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} {8DC74562-1F06-4AFA-8308-8A779E8812A3} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {4BC5D3D2-9425-41D7-9804-D145B92520EE} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5} EndGlobalSection EndGlobal diff --git a/src/common/Elsa.Features/Implementations/Module.cs b/src/common/Elsa.Features/Implementations/Module.cs index 32c5698d5f..5756548163 100644 --- a/src/common/Elsa.Features/Implementations/Module.cs +++ b/src/common/Elsa.Features/Implementations/Module.cs @@ -14,7 +14,7 @@ namespace Elsa.Features.Implementations; /// public class Module : IModule { - private record HostedServiceDescriptor(int Order, Type HostedServiceType); + private record HostedServiceDescriptor(int Order, Type Type); private IDictionary _features = new Dictionary(); private readonly ISet _configuredFeatures = new HashSet(); @@ -84,12 +84,12 @@ public void Apply() } foreach (var hostedServiceDescriptor in _hostedServiceDescriptors.OrderBy(x => x.Order)) - Services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IHostedService), hostedServiceDescriptor.HostedServiceType)); + Services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IHostedService), hostedServiceDescriptor.Type)); // Make sure to use the complete list of features when applying them. foreach (var feature in _features.Values) feature.Apply(); - + // Add a registry of enabled features to the service collection for client applications to reflect on what features are installed. var registry = new InstalledFeatureRegistry(); foreach (var feature in _features.Values) diff --git a/src/common/Elsa.Features/Services/IModule.cs b/src/common/Elsa.Features/Services/IModule.cs index 94be83a9c6..9712be22fa 100644 --- a/src/common/Elsa.Features/Services/IModule.cs +++ b/src/common/Elsa.Features/Services/IModule.cs @@ -17,22 +17,22 @@ public interface IModule /// A dictionary into which features can stash away values for later use. /// IDictionary Properties { get; } - + /// /// Creates and configures a feature of the specified type. /// T Configure(Action? configure = default) where T : class, IFeature; - + /// /// Creates and configures a feature of the specified type. /// T Configure(Func factory, Action? configure = default) where T : class, IFeature; - + /// /// Configures a using an optional priority to control in which order it will be registered with the service container. /// IModule ConfigureHostedService(int priority = 0) where T : class, IHostedService; - + /// /// Will apply all configured features, causing the collection to be populated. /// diff --git a/src/common/Elsa.Testing.Shared/TestApplicationBuilder.cs b/src/common/Elsa.Testing.Shared/TestApplicationBuilder.cs index f2b4fbb24c..614803f129 100644 --- a/src/common/Elsa.Testing.Shared/TestApplicationBuilder.cs +++ b/src/common/Elsa.Testing.Shared/TestApplicationBuilder.cs @@ -37,6 +37,7 @@ public TestApplicationBuilder(ITestOutputHelper testOutputHelper) .UseScheduling() .UseJavaScript() .UseLiquid() + .UseDsl() .UseWorkflows(workflows => workflows .WithStandardOutStreamProvider(_ => new StandardOutStreamProvider(new XunitConsoleTextWriter(_testOutputHelper))) ); diff --git a/src/modules/Elsa.Dsl/Contracts/IFunctionActivityRegistry.cs b/src/modules/Elsa.Dsl/Contracts/IFunctionActivityRegistry.cs index 14c44d42ff..79006699ac 100644 --- a/src/modules/Elsa.Dsl/Contracts/IFunctionActivityRegistry.cs +++ b/src/modules/Elsa.Dsl/Contracts/IFunctionActivityRegistry.cs @@ -1,9 +1,33 @@ +using Elsa.Dsl.Models; using Elsa.Workflows.Core.Contracts; namespace Elsa.Dsl.Contracts; +/// +/// Provides a registry for mapping functions to activities that can be invoked from a DSL script. +/// public interface IFunctionActivityRegistry { - void RegisterFunction(string functionName, string activityTypeName, IEnumerable? propertyNames = default); + /// + /// Registers a function that is mapped to an activity that can be invoked from a DSL script. + /// + /// The name of the function. + /// The name of the activity type. + /// The names of the properties that are mapped to the function arguments. + /// An optional action that can be used to configure the activity. + void RegisterFunction(string functionName, string activityTypeName, IEnumerable? propertyNames = default, Action? configure = default); + + /// + /// Registers a function that is mapped to an activity that can be invoked from a DSL script. + /// + /// The descriptor that describes the function. + void RegisterFunction(FunctionActivityDescriptor descriptor); + + /// + /// Resolves a function to an activity that can be invoked from a DSL script. + /// + /// The name of the function. + /// The arguments that are passed to the function. + /// An activity that can be invoked from a DSL script. IActivity ResolveFunction(string functionName, IEnumerable? arguments = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitBrackets.cs b/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitBrackets.cs index ebaa5f75d4..0f2373117d 100644 --- a/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitBrackets.cs +++ b/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitBrackets.cs @@ -5,20 +5,21 @@ namespace Elsa.Dsl.Interpreters; public partial class WorkflowDefinitionBuilderInterpreter { + /// public override IWorkflowBuilder VisitBracketsExpr(ElsaParser.BracketsExprContext context) { var propertyType = _expressionType.Get(context.Parent); - var targetElementType = propertyType.GetGenericArguments().First(); + var targetElementType = propertyType?.GetGenericArguments().First() ?? typeof(object); var contents = context.exprList().expr(); var items = contents.Select(x => { Visit(x); - var objectContext = x.GetChild(0); - return _expressionValue.Get(objectContext); + var stringContext = x.GetChild(0) ?? x; + return _expressionValue.Get(stringContext); }).ToList(); - var stronglyTypedListType = typeof(ICollection<>).MakeGenericType(targetElementType); + var stronglyTypedListType = targetElementType; var stronglyTypedList = items.ConvertTo(stronglyTypedListType); _expressionValue.Put(context, stronglyTypedList); diff --git a/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitObject.cs b/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitObject.cs index 4940cf5832..a090d0b921 100644 --- a/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitObject.cs +++ b/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitObject.cs @@ -1,9 +1,12 @@ -using Elsa.Workflows.Core.Contracts; +using System.Text.Json; +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Core.Models; namespace Elsa.Dsl.Interpreters; public partial class WorkflowDefinitionBuilderInterpreter { + /// public override IWorkflowBuilder VisitObjectExpr(ElsaParser.ObjectExprContext context) { VisitChildren(context); @@ -13,6 +16,7 @@ public override IWorkflowBuilder VisitObjectExpr(ElsaParser.ObjectExprContext co return DefaultResult; } + /// public override IWorkflowBuilder VisitObjectStat(ElsaParser.ObjectStatContext context) { VisitChildren(context); @@ -22,9 +26,34 @@ public override IWorkflowBuilder VisitObjectStat(ElsaParser.ObjectStatContext co return DefaultResult; } + /// public override IWorkflowBuilder VisitObject(ElsaParser.ObjectContext context) + { + var @object = GetObject(context); + + _object.Put(context, @object); + _expressionValue.Put(context, @object); + VisitChildren(context); + + return DefaultResult; + } + + private object GetObject(ElsaParser.ObjectContext context) { var objectTypeName = context.ID().GetText(); + + // First, check if the symbol matches an activity type. + var activityType = _activityRegistry.Find(x => x.Name == objectTypeName); + + if (activityType != null) + { + // TODO: Refactor this to remove the dependency on JsonElement and JsonSerializerOptions. + // This limits the ability to use this class in other contexts, such as constructing activities from the DSL. + var jsonElement = JsonSerializer.Deserialize("{}"); + var ctorArgs = new ActivityConstructorContext(jsonElement, new JsonSerializerOptions()); + return activityType.Constructor(ctorArgs); + } + var objectTypeDescriptor = _typeSystem.ResolveTypeName(objectTypeName); if (objectTypeDescriptor == null) @@ -35,7 +64,7 @@ public override IWorkflowBuilder VisitObject(ElsaParser.ObjectContext context) _expressionValue.Put(context, definedVariable.Value); return DefaultResult; } - + // Or a workflow variable? var workflowVariableQuery = from container in _containerStack @@ -50,17 +79,13 @@ from variable in container.Variables _expressionValue.Put(context, workflowVariable); return DefaultResult; } - + throw new Exception($"Unknown type: {objectTypeName}"); } var objectType = objectTypeDescriptor.Type; var @object = Activator.CreateInstance(objectType)!; - _object.Put(context, @object); - _expressionValue.Put(context, @object); - VisitChildren(context); - - return DefaultResult; + return @object; } } \ No newline at end of file diff --git a/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitProperty.cs b/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitProperty.cs index 370e592221..ba024c4adb 100644 --- a/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitProperty.cs +++ b/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/VisitProperty.cs @@ -1,5 +1,6 @@ using System.Reflection; using Elsa.Expressions.Helpers; +using Elsa.Expressions.Models; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Models; @@ -7,6 +8,7 @@ namespace Elsa.Dsl.Interpreters; public partial class WorkflowDefinitionBuilderInterpreter { + /// public override IWorkflowBuilder VisitProperty(ElsaParser.PropertyContext context) { var @object = _object.Get(context.Parent.Parent.Parent); @@ -21,7 +23,9 @@ public override IWorkflowBuilder VisitProperty(ElsaParser.PropertyContext contex VisitChildren(context); var propertyValue = _expressionValue.Get(context.expr()); - SetPropertyValue(@object, propertyInfo, propertyValue); + var propertyType = propertyInfo.PropertyType; + var parsedPropertyValue = typeof(Input).IsAssignableFrom(propertyType) ? propertyValue : propertyValue.ConvertTo(propertyType); + SetPropertyValue(@object, propertyInfo, parsedPropertyValue); return DefaultResult; } @@ -37,19 +41,25 @@ private void SetPropertyValue(object target, PropertyInfo propertyInfo, object? private Input CreateInputValue(PropertyInfo propertyInfo, object? propertyValue) { var underlyingType = propertyInfo.PropertyType.GetGenericArguments().First(); - var propertyValueType = propertyValue?.GetType(); + var parsedPropertyValue = propertyValue.ConvertTo(underlyingType); + var propertyValueType = parsedPropertyValue?.GetType(); var inputType = typeof(Input<>).MakeGenericType(underlyingType); if (propertyValueType != null) { - var hasCtorWithSpecifiedType = inputType.GetConstructors().Any(x => x.GetParameters().Any(y => y.ParameterType.IsAssignableFrom(propertyValueType))); + // Create a literal value. + var literalType = typeof(Literal<>).MakeGenericType(underlyingType); + var hasCtorWithSpecifiedType = inputType.GetConstructors().Any(x => x.GetParameters().Any(y => y.ParameterType.IsAssignableFrom(literalType))); if (hasCtorWithSpecifiedType) - return (Input)Activator.CreateInstance(inputType, propertyValue)!; + { + var literalValue = Activator.CreateInstance(literalType, parsedPropertyValue)!; + return (Input)Activator.CreateInstance(inputType, literalValue)!; + } } - return propertyValue is ExternalExpressionReference externalExpressionReference - ? (Input)Activator.CreateInstance(inputType, externalExpressionReference.Expression, externalExpressionReference.BlockReference)! - : (Input)Activator.CreateInstance(inputType, propertyValue.ConvertTo(underlyingType))!; + return parsedPropertyValue is ExternalExpressionReference externalExpressionReference + ? (Input)Activator.CreateInstance(inputType, externalExpressionReference.Expression, externalExpressionReference.BlockReference)! + : (Input)Activator.CreateInstance(inputType, parsedPropertyValue)!; } } \ No newline at end of file diff --git a/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/WorkflowDefinitionBuilderInterpreter.cs b/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/WorkflowDefinitionBuilderInterpreter.cs index 6b14ecec18..97415f2403 100644 --- a/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/WorkflowDefinitionBuilderInterpreter.cs +++ b/src/modules/Elsa.Dsl/Interpreters/WorkflowDefinitionBuilderInterpreter/WorkflowDefinitionBuilderInterpreter.cs @@ -1,7 +1,6 @@ using Antlr4.Runtime.Tree; using Elsa.Dsl.Contracts; using Elsa.Dsl.Models; -using Elsa.Expressions.Contracts; using Elsa.Workflows.Core.Activities; using Elsa.Workflows.Core.Contracts; @@ -10,8 +9,8 @@ namespace Elsa.Dsl.Interpreters; public partial class WorkflowDefinitionBuilderInterpreter : ElsaParserBaseVisitor { private readonly ITypeSystem _typeSystem; + private readonly IActivityRegistry _activityRegistry; private readonly IFunctionActivityRegistry _functionActivityRegistry; - private readonly IExpressionHandlerRegistry _expressionHandlerRegistry; private readonly IWorkflowBuilder _workflowBuilder; private readonly ParseTreeProperty _object = new(); private readonly ParseTreeProperty _expressionValue = new(); @@ -22,22 +21,17 @@ public partial class WorkflowDefinitionBuilderInterpreter : ElsaParserBaseVisito /// public WorkflowDefinitionBuilderInterpreter( - ITypeSystem typeSystem, - IFunctionActivityRegistry functionActivityRegistry, - IExpressionHandlerRegistry expressionHandlerRegistry, - IWorkflowBuilderFactory workflowBuilderFactory, - WorkflowDefinitionInterpreterSettings settings) + ITypeSystem typeSystem, + IActivityRegistry activityRegistry, + IFunctionActivityRegistry functionActivityRegistry, + IWorkflowBuilderFactory workflowBuilderFactory) { _typeSystem = typeSystem; + _activityRegistry = activityRegistry; _functionActivityRegistry = functionActivityRegistry; - _expressionHandlerRegistry = expressionHandlerRegistry; _workflowBuilder = workflowBuilderFactory.CreateBuilder(); } + /// protected override IWorkflowBuilder DefaultResult => _workflowBuilder; - - private void VisitMany(IEnumerable contexts) - { - foreach (var parseTree in contexts) Visit(parseTree); - } } \ No newline at end of file diff --git a/src/modules/Elsa.Dsl/Models/FunctionActivityDescriptor.cs b/src/modules/Elsa.Dsl/Models/FunctionActivityDescriptor.cs new file mode 100644 index 0000000000..223eb95835 --- /dev/null +++ b/src/modules/Elsa.Dsl/Models/FunctionActivityDescriptor.cs @@ -0,0 +1,12 @@ +using Elsa.Workflows.Core.Contracts; + +namespace Elsa.Dsl.Models; + +/// +/// Describes a function that is mapped to an activity that can be invoked from a DSL script. +/// +/// The name of the function. +/// The name of the activity type. +/// The names of the properties that are mapped to the function arguments. +/// An optional action that can be used to configure the activity. +public record FunctionActivityDescriptor(string FunctionName, string ActivityTypeName, IEnumerable? PropertyNames = default, Action? Configure = default); \ No newline at end of file diff --git a/src/modules/Elsa.Dsl/Services/DslEngine.cs b/src/modules/Elsa.Dsl/Services/DslEngine.cs index 1ee715ddb8..76680183cb 100644 --- a/src/modules/Elsa.Dsl/Services/DslEngine.cs +++ b/src/modules/Elsa.Dsl/Services/DslEngine.cs @@ -1,8 +1,6 @@ using Antlr4.Runtime; using Elsa.Dsl.Contracts; using Elsa.Dsl.Interpreters; -using Elsa.Dsl.Models; -using Elsa.Expressions.Contracts; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Models; @@ -12,8 +10,8 @@ namespace Elsa.Dsl.Services; public class DslEngine : IDslEngine { private readonly ITypeSystem _typeSystem; + private readonly IActivityRegistry _activityRegistry; private readonly IFunctionActivityRegistry _functionActivityRegistry; - private readonly IExpressionHandlerRegistry _expressionHandlerRegistry; private readonly IWorkflowBuilderFactory _workflowBuilderFactory; /// @@ -21,13 +19,13 @@ public class DslEngine : IDslEngine /// public DslEngine( ITypeSystem typeSystem, + IActivityRegistry activityRegistry, IFunctionActivityRegistry functionActivityRegistry, - IExpressionHandlerRegistry expressionHandlerRegistry, IWorkflowBuilderFactory workflowBuilderFactory) { _typeSystem = typeSystem; + _activityRegistry = activityRegistry; _functionActivityRegistry = functionActivityRegistry; - _expressionHandlerRegistry = expressionHandlerRegistry; _workflowBuilderFactory = workflowBuilderFactory; } @@ -39,7 +37,13 @@ public async Task ParseAsync(string script, CancellationToken cancella var tokens = new CommonTokenStream(lexer); var parser = new ElsaParser(tokens); var tree = parser.program(); - var interpreter = new WorkflowDefinitionBuilderInterpreter(_typeSystem, _functionActivityRegistry, _expressionHandlerRegistry, _workflowBuilderFactory, new WorkflowDefinitionInterpreterSettings()); + + var interpreter = new WorkflowDefinitionBuilderInterpreter( + _typeSystem, + _activityRegistry, + _functionActivityRegistry, + _workflowBuilderFactory); + var workflowBuilder = interpreter.Visit(tree); var workflow = await workflowBuilder.BuildWorkflowAsync(cancellationToken); diff --git a/src/modules/Elsa.Dsl/Services/FunctionActivityRegistry.cs b/src/modules/Elsa.Dsl/Services/FunctionActivityRegistry.cs index 1aa10f8317..77121d5cb0 100644 --- a/src/modules/Elsa.Dsl/Services/FunctionActivityRegistry.cs +++ b/src/modules/Elsa.Dsl/Services/FunctionActivityRegistry.cs @@ -1,60 +1,67 @@ -using System.Reflection; +using System.Text.Json; using Elsa.Dsl.Contracts; using Elsa.Dsl.Interpreters; +using Elsa.Dsl.Models; using Elsa.Expressions.Helpers; +using Elsa.Expressions.Models; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Models; namespace Elsa.Dsl.Services; +/// public class FunctionActivityRegistry : IFunctionActivityRegistry { - private readonly ITypeSystem _typeSystem; + private readonly IActivityRegistry _activityRegistry; private readonly IDictionary _dictionary = new Dictionary(); - public FunctionActivityRegistry(ITypeSystem typeSystem) + /// + /// Creates a new instance of the class. + /// + public FunctionActivityRegistry(IActivityRegistry activityRegistry) { - _typeSystem = typeSystem; + _activityRegistry = activityRegistry; } - public void RegisterFunction(string functionName, string activityTypeName, IEnumerable? propertyNames = default) + /// + public void RegisterFunction(string functionName, string activityTypeName, IEnumerable? propertyNames = default, Action? configure = default) { - var typeDescriptor = _typeSystem.ResolveTypeName(activityTypeName); - - if (typeDescriptor == null) - throw new Exception($"Could not find activity type {activityTypeName}. Did you forget to register it?"); - - - if (typeDescriptor.Kind != TypeKind.Activity) - throw new Exception($"Only activity types can be mapped to functions. You are trying to map {typeDescriptor.Type.Name}, which is a different kind: {typeDescriptor.Kind}"); - - var activityType = typeDescriptor.Type; - var propertyNameList = propertyNames?.ToList() ?? new List(); - var properties = propertyNameList.Select(propertyName => - { - var property = activityType.GetProperties().FirstOrDefault(x => x.Name == propertyName); - - if (property == null) - throw new Exception($"Activity type {typeDescriptor.Type.Name} does not have a property named {propertyName}"); - - return property; - }).ToList(); + var descriptor = new FunctionActivityDescriptor(functionName, activityTypeName, propertyNames, configure); + RegisterFunction(descriptor); + } - var descriptor = new FunctionActivityDescriptor(activityType, properties); - _dictionary.Add(functionName, descriptor); + /// + public void RegisterFunction(FunctionActivityDescriptor descriptor) + { + _dictionary.Add(descriptor.FunctionName, descriptor); } + /// public IActivity ResolveFunction(string functionName, IEnumerable? arguments = default) { if (!_dictionary.TryGetValue(functionName, out var descriptor)) throw new Exception($"Could not resolve function {functionName}. Did you forget to register it?"); - var activityType = descriptor.ActivityType; - var activity = (IActivity)Activator.CreateInstance(activityType)!; + var activityDescriptor = _activityRegistry.Find(x => x.Name == descriptor.ActivityTypeName || x.TypeName == descriptor.ActivityTypeName); + + if (activityDescriptor == null) + throw new Exception($"Could not find activity descriptor for activity type {descriptor.ActivityTypeName}"); + + var propertyNameList = descriptor.PropertyNames?.ToList() ?? new List(); + var propertyDescriptors = activityDescriptor.Inputs.Cast().Concat(activityDescriptor.Outputs).ToList(); + + var properties = propertyNameList + .Select(propertyName => propertyDescriptors.FirstOrDefault(x => x.Name == propertyName)) + .Where(x => x != null) + .Select(x => x!) + .ToList(); + + var dummyJsonElement = JsonDocument.Parse("{}").RootElement; + var constructorContext = new ActivityConstructorContext(dummyJsonElement, new JsonSerializerOptions()); + var activity = activityDescriptor.Constructor(constructorContext); // Apply each argument in order of the described properties. var index = 0; - var properties = descriptor.Properties.ToList(); if (arguments != null) foreach (var argument in arguments) @@ -63,50 +70,57 @@ public IActivity ResolveFunction(string functionName, IEnumerable? argu SetPropertyValue(activity, property, argument); } + descriptor.Configure?.Invoke(activity); return activity; } - - private void SetPropertyValue(object target, PropertyInfo propertyInfo, object? value) + + private void SetPropertyValue(IActivity target, PropertyDescriptor propertyDescriptor, object? value) { - if (typeof(Input).IsAssignableFrom(propertyInfo.PropertyType)) - value = CreateInputValue(propertyInfo, value); - else if (typeof(Output).IsAssignableFrom(propertyInfo.PropertyType)) - value = CreateOutputValue(propertyInfo, value); + value = propertyDescriptor switch + { + InputDescriptor inputDescriptor => CreateInputValue(inputDescriptor, value), + OutputDescriptor outputDescriptor => CreateOutputValue(outputDescriptor, value), + _ => value + }; - propertyInfo.SetValue(target, value, null); + propertyDescriptor.ValueSetter(target, value); } - private Input CreateInputValue(PropertyInfo propertyInfo, object? propertyValue) + private Input CreateInputValue(InputDescriptor inputDescriptor, object? propertyValue) { if (propertyValue is Input input) return input; - - var underlyingType = propertyInfo.PropertyType.GetGenericArguments().First(); - var propertyValueType = propertyValue?.GetType(); + + var underlyingType = inputDescriptor.Type; + var parsedPropertyValue = propertyValue.ConvertTo(underlyingType); + var propertyValueType = parsedPropertyValue?.GetType(); var inputType = typeof(Input<>).MakeGenericType(underlyingType); - if (propertyValue is ExternalExpressionReference externalExpressionReference) + if (parsedPropertyValue is ExternalExpressionReference externalExpressionReference) return (Input)Activator.CreateInstance(inputType, externalExpressionReference.Expression, externalExpressionReference.BlockReference)!; if (propertyValueType != null) { - var hasCtorWithSpecifiedType = inputType.GetConstructors().Any(x => x.GetParameters().Any(y => y.ParameterType.IsAssignableFrom(propertyValueType))); + // Create a literal value. + var literalType = typeof(Literal<>).MakeGenericType(underlyingType); + var hasCtorWithSpecifiedType = inputType.GetConstructors().Any(x => x.GetParameters().Any(y => y.ParameterType.IsAssignableFrom(literalType))); if (hasCtorWithSpecifiedType) - return (Input)Activator.CreateInstance(inputType, propertyValue)!; + { + var literalValue = Activator.CreateInstance(literalType, parsedPropertyValue)!; + return (Input)Activator.CreateInstance(inputType, literalValue)!; + } } - var convertedValue = propertyValue.ConvertTo(underlyingType); - - return (Input)Activator.CreateInstance(inputType, convertedValue)!; + return (Input)Activator.CreateInstance(inputType, parsedPropertyValue)!; } - - private Output CreateOutputValue(PropertyInfo propertyInfo, object? propertyValue) + + private Output CreateOutputValue(OutputDescriptor outputDescriptor, object? propertyValue) { if (propertyValue is Output output) return output; - - var underlyingType = propertyInfo.PropertyType.GetGenericArguments().First(); + + var underlyingType = outputDescriptor.Type; var propertyValueType = propertyValue?.GetType(); var outputType = typeof(Output<>).MakeGenericType(underlyingType); @@ -122,6 +136,4 @@ private Output CreateOutputValue(PropertyInfo propertyInfo, object? propertyValu return (Output)Activator.CreateInstance(outputType, convertedValue)!; } - - private record FunctionActivityDescriptor(Type ActivityType, ICollection Properties); } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Common/PersistenceFeatureBase.cs b/src/modules/Elsa.EntityFrameworkCore/Common/PersistenceFeatureBase.cs index c21505e8c1..5f76590868 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Common/PersistenceFeatureBase.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Common/PersistenceFeatureBase.cs @@ -15,7 +15,11 @@ protected PersistenceFeatureBase(IModule module) : base(module) public bool UseContextPooling { get; set; } public bool RunMigrations { get; set; } = true; public ServiceLifetime DbContextFactoryLifetime { get; set; } = ServiceLifetime.Singleton; - public Action DbContextOptionsBuilder = (_, _) => { }; + + public Action DbContextOptionsBuilder = (_, options) => options + .UseSqlite("Data Source=elsa.sqlite.db;Cache=Shared;", sqlite => sqlite + .MigrationsAssembly("Elsa.EntityFrameworkCore.Sqlite") + .MigrationsHistoryTable(ElsaDbContextBase.MigrationsHistoryTable, ElsaDbContextBase.ElsaSchema)); public override void ConfigureHostedServices() { diff --git a/src/modules/Elsa.JavaScript/Elsa.JavaScript.csproj b/src/modules/Elsa.JavaScript/Elsa.JavaScript.csproj index bdb72d863a..406934a944 100644 --- a/src/modules/Elsa.JavaScript/Elsa.JavaScript.csproj +++ b/src/modules/Elsa.JavaScript/Elsa.JavaScript.csproj @@ -15,10 +15,10 @@ - + diff --git a/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs b/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs index 72c0f8dd93..10175af02d 100644 --- a/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs +++ b/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs @@ -4,7 +4,6 @@ using Elsa.Features.Abstractions; using Elsa.Features.Attributes; using Elsa.Features.Services; -using Elsa.JavaScript.Activities; using Elsa.JavaScript.Contracts; using Elsa.JavaScript.Expressions; using Elsa.JavaScript.Extensions; @@ -14,7 +13,6 @@ using Elsa.JavaScript.TypeDefinitions.Providers; using Elsa.JavaScript.TypeDefinitions.Services; using Elsa.Mediator.Features; -using Elsa.Workflows.Core.Contracts; using Microsoft.Extensions.DependencyInjection; namespace Elsa.JavaScript.Features; @@ -31,12 +29,6 @@ public JavaScriptFeature(IModule module) : base(module) { } - /// - public override void Configure() - { - Module.UseWorkflowManagement(management => management.AddActivitiesFrom()); - } - /// public override void Apply() { @@ -44,7 +36,6 @@ public override void Apply() Services .AddSingleton() .AddSingleton() - .AddSingleton() .AddSingleton() .AddExpressionHandler() ; diff --git a/src/modules/Elsa.JavaScript/Services/TypeAliasRegistry.cs b/src/modules/Elsa.JavaScript/Services/TypeAliasRegistry.cs index e8233dc2e1..c345b6402d 100644 --- a/src/modules/Elsa.JavaScript/Services/TypeAliasRegistry.cs +++ b/src/modules/Elsa.JavaScript/Services/TypeAliasRegistry.cs @@ -1,8 +1,6 @@ using System.Dynamic; using Elsa.JavaScript.Contracts; using Elsa.JavaScript.Extensions; -using Elsa.Workflows.Management.Options; -using Microsoft.Extensions.Options; namespace Elsa.JavaScript.Services; @@ -14,7 +12,7 @@ public class TypeAliasRegistry : ITypeAliasRegistry /// /// Constructor. /// - public TypeAliasRegistry(IOptions managementOptions) + public TypeAliasRegistry() { this.RegisterType("any"); this.RegisterType("any"); @@ -30,12 +28,6 @@ public TypeAliasRegistry(IOptions managementOptions) this.RegisterType("Date"); this.RegisterType("Date"); this.RegisterType("Date"); - - foreach (var variableDescriptor in managementOptions.Value.VariableDescriptors) - { - if(!_typeAliasDictionary.ContainsKey(variableDescriptor.Type)) - RegisterType(variableDescriptor.Type, variableDescriptor.Type.Name); - } } /// diff --git a/src/modules/Elsa.JavaScript/TypeDefinitions/Models/TypeDefinitionContext.cs b/src/modules/Elsa.JavaScript/TypeDefinitions/Models/TypeDefinitionContext.cs index 4be199c84d..9346f57958 100644 --- a/src/modules/Elsa.JavaScript/TypeDefinitions/Models/TypeDefinitionContext.cs +++ b/src/modules/Elsa.JavaScript/TypeDefinitions/Models/TypeDefinitionContext.cs @@ -1,8 +1,8 @@ -using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Core.Models; namespace Elsa.JavaScript.TypeDefinitions.Models; /// /// Provides context to intellisense providers. /// -public record TypeDefinitionContext(WorkflowDefinition WorkflowDefinition, string? ActivityTypeName, string? PropertyName, CancellationToken CancellationToken); \ No newline at end of file +public record TypeDefinitionContext(Workflow Workflow, string? ActivityTypeName, string? PropertyName, CancellationToken CancellationToken); \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/CommonFunctionsDefinitionProvider.cs b/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/CommonFunctionsDefinitionProvider.cs index 6af692fda3..a149ee7327 100644 --- a/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/CommonFunctionsDefinitionProvider.cs +++ b/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/CommonFunctionsDefinitionProvider.cs @@ -80,7 +80,7 @@ protected override IEnumerable GetFunctionDefinitions(TypeDe .ReturnType("string")); // Variable getter and setters. - foreach (var variable in context.WorkflowDefinition.Variables) + foreach (var variable in context.Workflow.Variables) { var pascalName = variable.Name.Pascalize(); var variableType = variable.GetVariableType(); @@ -92,16 +92,5 @@ protected override IEnumerable GetFunctionDefinitions(TypeDe // set{Variable}. yield return CreateFunctionDefinition(builder => builder.Name($"set{pascalName}").Parameter("value", typeAlias)); } - - // Input argument getters. - foreach (var input in context.WorkflowDefinition.Inputs) - { - var pascalName = input.Name.Pascalize(); - var variableType = input.Type; - var typeAlias = _typeAliasRegistry.TryGetAlias(variableType, out var alias) ? alias : "any"; - - // get{Input}. - yield return CreateFunctionDefinition(builder => builder.Name($"get{pascalName}").ReturnType(typeAlias)); - } } } \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/VariableTypeDefinitionProvider.cs b/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/VariableTypeDefinitionProvider.cs index 167ab0152b..1d435c0fa5 100644 --- a/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/VariableTypeDefinitionProvider.cs +++ b/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/VariableTypeDefinitionProvider.cs @@ -27,7 +27,7 @@ protected override IEnumerable GetTypeDefinitions(TypeDefinition type => type == typeof(object) }; - var variables = context.WorkflowDefinition.Variables; + var variables = context.Workflow.Variables; var variableTypeQuery = from variable in variables diff --git a/src/modules/Elsa.WorkflowContexts/Scripting/JavaScript/ConfigureJavaScriptEngine.cs b/src/modules/Elsa.WorkflowContexts/Scripting/JavaScript/ConfigureJavaScriptEngine.cs index 4d1d84bac1..7579cd828a 100644 --- a/src/modules/Elsa.WorkflowContexts/Scripting/JavaScript/ConfigureJavaScriptEngine.cs +++ b/src/modules/Elsa.WorkflowContexts/Scripting/JavaScript/ConfigureJavaScriptEngine.cs @@ -50,7 +50,7 @@ public Task HandleAsync(EvaluatingJavaScript notification, CancellationToken can /// public ValueTask> GetTypeDefinitionsAsync(TypeDefinitionContext context) { - var providerTypes = GetProviderTypes(context.WorkflowDefinition); + var providerTypes = GetProviderTypes(context.Workflow); var contextTypes = providerTypes.Select(x => x.GetWorkflowContextType()); var typeDefinitions = contextTypes.Select(x => _typeDescriber.DescribeType(x)); return new(typeDefinitions); @@ -59,7 +59,7 @@ public ValueTask> GetTypeDefinitionsAsync(TypeDefini /// public ValueTask> GetFunctionDefinitionsAsync(TypeDefinitionContext context) { - var providerTypes = GetProviderTypes(context.WorkflowDefinition); + var providerTypes = GetProviderTypes(context.Workflow); var functionDefinitions = BuildFunctionDefinitions(providerTypes); return new(functionDefinitions); } @@ -80,5 +80,5 @@ private IEnumerable BuildFunctionDefinitions(IEnumerable GetProviderTypes(WorkflowExecutionContext workflowExecutionContext) => workflowExecutionContext.Workflow.GetWorkflowContextProviderTypes(); - private IEnumerable GetProviderTypes(WorkflowDefinition workflowDefinition) => workflowDefinition.GetWorkflowContextProviderTypes(); + private IEnumerable GetProviderTypes(Workflow workflow) => workflow.GetWorkflowContextProviderTypes(); } \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/Features/FluentStorageFeature.cs b/src/modules/Elsa.WorkflowProviders.FluentStorage/Features/FluentStorageFeature.cs index 67ccc4a909..fe0aaff798 100644 --- a/src/modules/Elsa.WorkflowProviders.FluentStorage/Features/FluentStorageFeature.cs +++ b/src/modules/Elsa.WorkflowProviders.FluentStorage/Features/FluentStorageFeature.cs @@ -3,7 +3,7 @@ using Elsa.Features.Attributes; using Elsa.Features.Services; using Elsa.WorkflowProviders.FluentStorage.Contracts; -using Elsa.WorkflowProviders.FluentStorage.Services; +using Elsa.WorkflowProviders.FluentStorage.Providers; using Elsa.Workflows.Management.Features; using FluentStorage; using FluentStorage.Blobs; @@ -33,7 +33,7 @@ public FluentStorageFeature(IModule module) : base(module) public override void Apply() { Services.AddSingleton(sp => new BlobStorageProvider(BlobStorage(sp))); - Services.AddWorkflowDefinitionProvider(); + Services.AddWorkflowDefinitionProvider(); } private static string GetDefaultWorkflowsDirectory() diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/BlobStorageProvider.cs b/src/modules/Elsa.WorkflowProviders.FluentStorage/Providers/BlobStorageProvider.cs similarity index 91% rename from src/modules/Elsa.WorkflowProviders.FluentStorage/Services/BlobStorageProvider.cs rename to src/modules/Elsa.WorkflowProviders.FluentStorage/Providers/BlobStorageProvider.cs index 397e5f3c4b..cd0e90914f 100644 --- a/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/BlobStorageProvider.cs +++ b/src/modules/Elsa.WorkflowProviders.FluentStorage/Providers/BlobStorageProvider.cs @@ -1,7 +1,7 @@ using Elsa.WorkflowProviders.FluentStorage.Contracts; using FluentStorage.Blobs; -namespace Elsa.WorkflowProviders.FluentStorage.Services; +namespace Elsa.WorkflowProviders.FluentStorage.Providers; /// /// A provider of . diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/Providers/FluentStorageWorkflowProvider.cs b/src/modules/Elsa.WorkflowProviders.FluentStorage/Providers/FluentStorageWorkflowProvider.cs new file mode 100644 index 0000000000..a324e8428a --- /dev/null +++ b/src/modules/Elsa.WorkflowProviders.FluentStorage/Providers/FluentStorageWorkflowProvider.cs @@ -0,0 +1,110 @@ +using Elsa.Common.Contracts; +using Elsa.Dsl.Contracts; +using Elsa.WorkflowProviders.FluentStorage.Contracts; +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Management.Mappers; +using Elsa.Workflows.Management.Materializers; +using Elsa.Workflows.Management.Models; +using Elsa.Workflows.Runtime.Contracts; +using Elsa.Workflows.Runtime.Models; +using FluentStorage.Blobs; +using JetBrains.Annotations; + +namespace Elsa.WorkflowProviders.FluentStorage.Providers; + +/// +/// A workflow definition provider that loads workflow definitions from a storage using FluentStorage (See https://github.com/robinrodricks/FluentStorage). +/// +[PublicAPI] +public class FluentStorageWorkflowProvider : IWorkflowProvider +{ + private readonly IBlobStorageProvider _blobStorageProvider; + private readonly IActivitySerializer _activitySerializer; + private readonly IDslEngine _dslEngine; + private readonly ISystemClock _systemClock; + private readonly IHasher _hasher; + private readonly WorkflowDefinitionMapper _workflowDefinitionMapper; + private readonly VariableDefinitionMapper _variableDefinitionMapper; + + /// + /// Initializes a new instance of the class. + /// + public FluentStorageWorkflowProvider( + IBlobStorageProvider blobStorageProvider, + IActivitySerializer activitySerializer, + IDslEngine dslEngine, + ISystemClock systemClock, + IHasher hasher, + WorkflowDefinitionMapper workflowDefinitionMapper, + VariableDefinitionMapper variableDefinitionMapper) + { + _blobStorageProvider = blobStorageProvider; + _activitySerializer = activitySerializer; + _dslEngine = dslEngine; + _systemClock = systemClock; + _hasher = hasher; + _workflowDefinitionMapper = workflowDefinitionMapper; + _variableDefinitionMapper = variableDefinitionMapper; + } + + /// + public string Name => "FluentStorage"; + + /// + public async ValueTask> GetWorkflowDefinitionsAsync(CancellationToken cancellationToken = default) + { + var options = new ListOptions + { + Recurse = true, + }; + + var blobStorage = _blobStorageProvider.GetBlobStorage(); + var blobs = await blobStorage.ListFilesAsync(options, cancellationToken); + var results = new List(); + + foreach (var blob in blobs) + { + var result = await ReadWorkflowDefinitionAsync(blob, cancellationToken); + results.Add(result); + } + + return results; + } + + private async Task ReadWorkflowDefinitionAsync(Blob blob, CancellationToken cancellationToken) + { + var blobStorage = _blobStorageProvider.GetBlobStorage(); + var fileExtension = blob.FullPath.Split('.').Last(); + var data = await blobStorage.ReadTextAsync(blob.FullPath, cancellationToken: cancellationToken); + + if (string.Equals("json", fileExtension, StringComparison.OrdinalIgnoreCase)) + return ReadJsonWorkflowDefinition(data); + + if (string.Equals("elsa", fileExtension, StringComparison.OrdinalIgnoreCase)) + return await ReadElsaDslWorkflowDefinitionAsync(blob, data, cancellationToken); + + throw new NotSupportedException($"The file extension '{fileExtension}' is not supported."); + } + + private async Task ReadElsaDslWorkflowDefinitionAsync(Blob blob, string dsl, CancellationToken cancellationToken) + { + var workflow = await _dslEngine.ParseAsync(dsl, cancellationToken); + + // TODO: Extend the DSL with support for setting the ID from there. + workflow.Identity = workflow.Identity with + { + Id = blob.Name, + DefinitionId = blob.Name + }; + + return new MaterializedWorkflow(workflow, JsonWorkflowMaterializer.MaterializerName); + } + + private MaterializedWorkflow ReadJsonWorkflowDefinition(string json) + { + var workflowDefinitionModel = _activitySerializer.Deserialize(json); + var workflow = _workflowDefinitionMapper.Map(workflowDefinitionModel); + + return new MaterializedWorkflow(workflow, JsonWorkflowMaterializer.MaterializerName); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/FluentStorageWorkflowDefinitionProvider.cs b/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/FluentStorageWorkflowDefinitionProvider.cs deleted file mode 100644 index 29720c3898..0000000000 --- a/src/modules/Elsa.WorkflowProviders.FluentStorage/Services/FluentStorageWorkflowDefinitionProvider.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Elsa.Common.Contracts; -using Elsa.WorkflowProviders.FluentStorage.Contracts; -using Elsa.Workflows.Core.Contracts; -using Elsa.Workflows.Management.Entities; -using Elsa.Workflows.Management.Mappers; -using Elsa.Workflows.Management.Materializers; -using Elsa.Workflows.Management.Models; -using Elsa.Workflows.Runtime.Contracts; -using Elsa.Workflows.Runtime.Models; -using FluentStorage.Blobs; -using JetBrains.Annotations; - -namespace Elsa.WorkflowProviders.FluentStorage.Services; - -/// -/// A workflow definition provider that loads workflow definitions from a storage using FluentStorage (See https://github.com/robinrodricks/FluentStorage). -/// -[PublicAPI] -public class FluentStorageWorkflowDefinitionProvider : IWorkflowDefinitionProvider -{ - private readonly IBlobStorageProvider _blobStorageProvider; - private readonly IActivitySerializer _activitySerializer; - private readonly ISystemClock _systemClock; - private readonly WorkflowDefinitionMapper _workflowDefinitionMapper; - private readonly VariableDefinitionMapper _variableDefinitionMapper; - - /// - /// Initializes a new instance of the class. - /// - public FluentStorageWorkflowDefinitionProvider( - IBlobStorageProvider blobStorageProvider, - IActivitySerializer activitySerializer, - ISystemClock systemClock, - WorkflowDefinitionMapper workflowDefinitionMapper, - VariableDefinitionMapper variableDefinitionMapper) - { - _blobStorageProvider = blobStorageProvider; - _activitySerializer = activitySerializer; - _systemClock = systemClock; - _workflowDefinitionMapper = workflowDefinitionMapper; - _variableDefinitionMapper = variableDefinitionMapper; - } - - /// - public string Name => "FluentStorage"; - - /// - public async ValueTask> GetWorkflowDefinitionsAsync(CancellationToken cancellationToken = default) - { - var options = new ListOptions - { - Recurse = true, - }; - - var blobStorage = _blobStorageProvider.GetBlobStorage(); - var blobs = await blobStorage.ListFilesAsync(options, cancellationToken); - var results = new List(); - - foreach (var blob in blobs) - { - var result = await ReadWorkflowDefinitionAsync(blob, cancellationToken); - results.Add(result); - } - - return results; - } - - private async Task ReadWorkflowDefinitionAsync(Blob blob, CancellationToken cancellationToken) - { - var blobStorage = _blobStorageProvider.GetBlobStorage(); - var workflowJson = await blobStorage.ReadTextAsync(blob.FullPath, cancellationToken: cancellationToken); - var workflowDefinitionModel = _activitySerializer.Deserialize(workflowJson); - var variables = _variableDefinitionMapper.Map(workflowDefinitionModel.Variables).ToList(); - var rootJson = _activitySerializer.Serialize(workflowDefinitionModel.Root!); - - var definition = new WorkflowDefinition - { - Id = workflowDefinitionModel.Id, - DefinitionId = workflowDefinitionModel.DefinitionId, - Version = workflowDefinitionModel.Version, - Name = workflowDefinitionModel.Name, - Description = workflowDefinitionModel.Description, - CustomProperties = workflowDefinitionModel.CustomProperties ?? new Dictionary(), - Variables = variables, - IsLatest = workflowDefinitionModel.IsLatest, - IsPublished = workflowDefinitionModel.IsPublished, - CreatedAt = workflowDefinitionModel.CreatedAt == default ? _systemClock.UtcNow : workflowDefinitionModel.CreatedAt, - MaterializerName = JsonWorkflowMaterializer.MaterializerName, - StringData = rootJson - }; - - var workflow = _workflowDefinitionMapper.Map(definition); - - return new WorkflowDefinitionResult(definition, workflow); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs index 2413292472..41c92a6c37 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs @@ -2,6 +2,7 @@ using Elsa.Common.Models; using Elsa.JavaScript.TypeDefinitions.Contracts; using Elsa.JavaScript.TypeDefinitions.Models; +using Elsa.Workflows.Core.Models; using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Entities; using Elsa.Workflows.Runtime.Contracts; @@ -18,13 +19,13 @@ namespace Elsa.Workflows.Api.Endpoints.Scripting.JavaScript.TypeDefinitions; internal class Get : Endpoint { private readonly ITypeDefinitionService _typeDefinitionService; - private readonly IServiceProvider _serviceProvider; + private readonly IWorkflowDefinitionService _workflowDefinitionService; /// - public Get(ITypeDefinitionService typeDefinitionService, IServiceProvider serviceProvider) + public Get(ITypeDefinitionService typeDefinitionService, IWorkflowDefinitionService workflowDefinitionService) { _typeDefinitionService = typeDefinitionService; - _serviceProvider = serviceProvider; + _workflowDefinitionService = workflowDefinitionService; } /// @@ -53,11 +54,14 @@ public override async Task HandleAsync(Request request, CancellationToken cancel await SendBytesAsync(data, fileName, "application/x-typescript", cancellation: cancellationToken); } - private async Task GetWorkflowDefinition(string workflowDefinitionId, CancellationToken cancellationToken) + private async Task GetWorkflowDefinition(string workflowDefinitionId, CancellationToken cancellationToken) { - var workflowDefinitionService = _serviceProvider.GetService(); - var workflowDefinition = workflowDefinitionService != null ? await workflowDefinitionService.FindAsync(workflowDefinitionId, VersionOptions.Latest, cancellationToken) : default; - return workflowDefinition; + var workflowDefinition = await _workflowDefinitionService.FindAsync(workflowDefinitionId, VersionOptions.Latest, cancellationToken); + + if (workflowDefinition == null) + return null; + + return await _workflowDefinitionService.MaterializeWorkflowAsync(workflowDefinition, cancellationToken); } } diff --git a/src/modules/Elsa.Workflows.Core/Models/ActivityDescriptor.cs b/src/modules/Elsa.Workflows.Core/Models/ActivityDescriptor.cs index d03c8750a8..e68187fbf7 100644 --- a/src/modules/Elsa.Workflows.Core/Models/ActivityDescriptor.cs +++ b/src/modules/Elsa.Workflows.Core/Models/ActivityDescriptor.cs @@ -11,13 +11,54 @@ namespace Elsa.Workflows.Core.Models; [DebuggerDisplay("{TypeName}")] public class ActivityDescriptor { + /// + /// The fully qualified name of the activity type. + /// public string TypeName { get; set; } = default!; + + /// + /// The namespace of the activity type. + /// + public string Namespace { get; set; } = default!; + + /// + /// The name of the activity type. + /// + public string Name { get; set; } = default!; + + /// + /// The version of the activity type. + /// public int Version { get; set; } + + /// + /// The category of the activity type. + /// public string Category { get; set; } = default!; + + /// + /// The display name of the activity type. + /// public string? DisplayName { get; set; } + + /// + /// The description of the activity type. + /// public string? Description { get; set; } + + /// + /// The input properties of the activity type. + /// public ICollection Inputs { get; init; } = new List(); + + /// + /// The output properties of the activity type. + /// public ICollection Outputs { get; init; } = new List(); + + /// + /// The attributes of the activity type. + /// [JsonIgnore] public ICollection Attributes { get; set; } = new List(); /// @@ -26,8 +67,19 @@ public class ActivityDescriptor [JsonIgnore] public Func Constructor { get; init; } = default!; + /// + /// The kind of activity. + /// public ActivityKind Kind { get; set; } = ActivityKind.Action; + + /// + /// The ports of the activity type. + /// public ICollection Ports { get; init; } = new List(); + + /// + /// The custom properties of the activity type. + /// public IDictionary CustomProperties { get; set; } = new Dictionary(); /// @@ -41,4 +93,6 @@ public class ActivityDescriptor public bool IsBrowsable { get; set; } } +// TODO: Refactor this to remove the dependency on JsonElement and JsonSerializerOptions. +// This limits the ability to use this class in other contexts, such as constructing activities from the DSL. public record ActivityConstructorContext(JsonElement Element, JsonSerializerOptions SerializerOptions); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs b/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs index 1f4c4a97f2..0a8746cf67 100644 --- a/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs +++ b/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs @@ -33,7 +33,7 @@ public ActivityDescriber(IPropertyOptionsResolver optionsResolver, IPropertyDefa public async Task DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType, CancellationToken cancellationToken = default) { var activityAttr = activityType.GetCustomAttribute(); - var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType); + var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa"; var typeName = activityAttr?.Type ?? activityType.Name; var typeVersion = activityAttr?.Version ?? 1; var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType); @@ -73,9 +73,11 @@ where typeof(IActivity).IsAssignableFrom(prop.PropertyType) || typeof(IEnumerabl var descriptor = new ActivityDescriptor { + TypeName = fullTypeName, + Namespace = ns, + Name = typeName, Category = category, Description = description, - TypeName = fullTypeName, Version = typeVersion, DisplayName = displayName, Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action, diff --git a/src/modules/Elsa.JavaScript/Activities/RunJavaScript.cs b/src/modules/Elsa.Workflows.Management/Activities/RunJavaScript/RunJavaScript.cs similarity index 100% rename from src/modules/Elsa.JavaScript/Activities/RunJavaScript.cs rename to src/modules/Elsa.Workflows.Management/Activities/RunJavaScript/RunJavaScript.cs diff --git a/src/modules/Elsa.JavaScript/Activities/RunJavaScriptOptionsProvider.cs b/src/modules/Elsa.Workflows.Management/Activities/RunJavaScript/RunJavaScriptOptionsProvider.cs similarity index 100% rename from src/modules/Elsa.JavaScript/Activities/RunJavaScriptOptionsProvider.cs rename to src/modules/Elsa.Workflows.Management/Activities/RunJavaScript/RunJavaScriptOptionsProvider.cs diff --git a/src/modules/Elsa.Workflows.Management/Elsa.Workflows.Management.csproj b/src/modules/Elsa.Workflows.Management/Elsa.Workflows.Management.csproj index 1b6595ea1b..419672871e 100644 --- a/src/modules/Elsa.Workflows.Management/Elsa.Workflows.Management.csproj +++ b/src/modules/Elsa.Workflows.Management/Elsa.Workflows.Management.csproj @@ -18,6 +18,8 @@ + + diff --git a/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs index d11ff23482..033260e5da 100644 --- a/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs +++ b/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs @@ -23,7 +23,7 @@ public static IModule UseWorkflowManagement(this IModule module, Action /// Adds the default workflow management feature to the specified module. /// @@ -32,7 +32,7 @@ public static WorkflowManagementFeature UseWorkflowDefinitions(this WorkflowMana feature.Module.Configure(configure); return feature; } - + /// /// Adds the workflow instance feature to workflow management module. /// @@ -42,13 +42,31 @@ public static WorkflowManagementFeature UseWorkflowInstances(this WorkflowManage return feature; } + /// + /// Adds the JavaScript integration feature. + /// + public static WorkflowManagementFeature UseJavaScriptIntegration(this WorkflowManagementFeature feature, Action? configure = default) + { + feature.Module.Configure(configure); + return feature; + } + + /// + /// Adds the Elsa DSL integration feature. + /// + public static WorkflowManagementFeature UseDslIntegration(this WorkflowManagementFeature feature, Action? configure = default) + { + feature.Module.Configure(configure); + return feature; + } + /// /// Adds all types implementing to the system. /// public static IModule AddActivitiesFrom(this IModule module) => module.UseWorkflowManagement(management => management.AddActivitiesFrom()); - + /// /// Adds the specified activity type to the system. /// - public static IModule AddActivity(this IModule module) where T:IActivity => module.UseWorkflowManagement(management => management.AddActivity()); + public static IModule AddActivity(this IModule module) where T : IActivity => module.UseWorkflowManagement(management => management.AddActivity()); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Features/DslIntegrationFeature.cs b/src/modules/Elsa.Workflows.Management/Features/DslIntegrationFeature.cs new file mode 100644 index 0000000000..44c5fe4251 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Features/DslIntegrationFeature.cs @@ -0,0 +1,54 @@ +using Elsa.Dsl.Features; +using Elsa.Dsl.Models; +using Elsa.Features.Abstractions; +using Elsa.Features.Attributes; +using Elsa.Features.Services; +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Management.HostedServices; +using Elsa.Workflows.Management.Options; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Workflows.Management.Features; + +/// +/// Installs JavaScript activities. +/// +[DependsOn(typeof(DslFeature))] +[PublicAPI] +public class DslIntegrationFeature : FeatureBase +{ + private readonly IDictionary _dictionary = new Dictionary(); + + /// + public DslIntegrationFeature(IModule module) : base(module) + { + } + + /// + /// Maps a function to an activity. + /// + /// The name of the function. + /// The name of the activity type. + /// The names of the properties that are mapped to the function arguments. + /// An optional action that can be used to configure the activity. + public void MapActivityFunction(string functionName, string activityTypeName, IEnumerable? propertyNames = default, Action? configure = default) + { + _dictionary.Add(functionName, new FunctionActivityDescriptor(functionName, activityTypeName, propertyNames, configure)); + } + + /// + public override void ConfigureHostedServices() + { + ConfigureHostedService(-1); + } + + /// + public override void Apply() + { + Services.Configure(options => + { + options.FunctionActivityDescriptors = _dictionary; + }); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Features/JavaScriptIntegrationFeature.cs b/src/modules/Elsa.Workflows.Management/Features/JavaScriptIntegrationFeature.cs new file mode 100644 index 0000000000..d8c6b46709 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Features/JavaScriptIntegrationFeature.cs @@ -0,0 +1,47 @@ +using Elsa.Extensions; +using Elsa.Features.Abstractions; +using Elsa.Features.Attributes; +using Elsa.Features.Services; +using Elsa.JavaScript.Activities; +using Elsa.JavaScript.Extensions; +using Elsa.JavaScript.Features; +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Management.HostedServices; +using Elsa.Workflows.Management.Scripting.JavaScript; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Workflows.Management.Features; + +/// +/// Installs JavaScript activities. +/// +[DependsOn(typeof(JavaScriptFeature))] +[PublicAPI] +public class JavaScriptIntegrationFeature : FeatureBase +{ + /// + public JavaScriptIntegrationFeature(IModule module) : base(module) + { + } + + /// + public override void Configure() + { + Module.UseWorkflowManagement(management => management.AddActivity()); + } + + /// + public override void ConfigureHostedServices() + { + ConfigureHostedService(); + } + + /// + public override void Apply() + { + Services + .AddSingleton() + .AddFunctionDefinitionProvider(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs index 294af9c5c9..8084d0ac0f 100644 --- a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs +++ b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs @@ -13,6 +13,7 @@ using Elsa.Workflows.Management.Activities.WorkflowDefinitionActivity; using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Management.HostedServices; using Elsa.Workflows.Management.Mappers; using Elsa.Workflows.Management.Materializers; using Elsa.Workflows.Management.Models; @@ -138,6 +139,7 @@ public override void Apply() .AddMemoryStore() .AddMemoryStore() .AddActivityProvider() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/modules/Elsa.Workflows.Management/HostedServices/MapActivityDslFunctionsHostedService.cs b/src/modules/Elsa.Workflows.Management/HostedServices/MapActivityDslFunctionsHostedService.cs new file mode 100644 index 0000000000..280b25c02b --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/HostedServices/MapActivityDslFunctionsHostedService.cs @@ -0,0 +1,37 @@ +using Elsa.Dsl.Contracts; +using Elsa.Dsl.Models; +using Elsa.Workflows.Management.Options; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; + +namespace Elsa.Workflows.Management.HostedServices; + +/// +/// Registers function activity descriptors with the DSL. +/// +public class MapActivityDslFunctionsHostedService : IHostedService +{ + private readonly IFunctionActivityRegistry _functionActivityRegistry; + private readonly IDictionary _descriptors; + + /// + /// Initializes a new instance of the class. + /// + public MapActivityDslFunctionsHostedService(IOptions options, IFunctionActivityRegistry functionActivityRegistry) + { + _functionActivityRegistry = functionActivityRegistry; + _descriptors = options.Value.FunctionActivityDescriptors; + } + + /// + public Task StartAsync(CancellationToken cancellationToken) + { + foreach (var descriptor in _descriptors.Values) + _functionActivityRegistry.RegisterFunction(descriptor); + + return Task.CompletedTask; + } + + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/HostedServices/RegisterVariableTypesWithJavaScriptHostedService.cs b/src/modules/Elsa.Workflows.Management/HostedServices/RegisterVariableTypesWithJavaScriptHostedService.cs new file mode 100644 index 0000000000..53698a5e02 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/HostedServices/RegisterVariableTypesWithJavaScriptHostedService.cs @@ -0,0 +1,36 @@ +using Elsa.JavaScript.Contracts; +using Elsa.Workflows.Management.Options; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; + +namespace Elsa.Workflows.Management.HostedServices; + +/// +/// Registers variable types with the JavaScript type alias registry +/// +public class RegisterVariableTypesWithJavaScriptHostedService : IHostedService +{ + private readonly ITypeAliasRegistry _typeAliasRegistry; + private readonly IOptions _managementOptions; + + /// + /// Initializes a new instance of the class. + /// + public RegisterVariableTypesWithJavaScriptHostedService(ITypeAliasRegistry typeAliasRegistry, IOptions managementOptions) + { + _typeAliasRegistry = typeAliasRegistry; + _managementOptions = managementOptions; + } + + /// + public Task StartAsync(CancellationToken cancellationToken) + { + foreach (var variableDescriptor in _managementOptions.Value.VariableDescriptors) + _typeAliasRegistry.RegisterType(variableDescriptor.Type, variableDescriptor.Type.Name); + + return Task.CompletedTask; + } + + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs index 570e4af716..fb01b82fa2 100644 --- a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs +++ b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs @@ -44,6 +44,26 @@ public Workflow Map(WorkflowDefinition source) source.CustomProperties); } + /// + /// Maps a to a . + /// + /// The source . + /// The mapped . + public Workflow Map(WorkflowDefinitionModel source) + { + var root = source.Root!; + var variables = _variableDefinitionMapper.Map(source.Variables).ToList(); + + return new( + new WorkflowIdentity(source.DefinitionId, source.Version, source.Id), + new WorkflowPublication(source.IsLatest, source.IsPublished), + new WorkflowMetadata(source.Name, source.Description, source.CreatedAt), + source.Options, + root, + variables, + source.CustomProperties ?? new Dictionary()); + } + /// /// Maps a to a . /// diff --git a/src/modules/Elsa.Workflows.Management/Options/DslIntegrationOptions.cs b/src/modules/Elsa.Workflows.Management/Options/DslIntegrationOptions.cs new file mode 100644 index 0000000000..356f9089ed --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Options/DslIntegrationOptions.cs @@ -0,0 +1,14 @@ +using Elsa.Dsl.Models; + +namespace Elsa.Workflows.Management.Options; + +/// +/// Options for the DSL integration. +/// +public class DslIntegrationOptions +{ + /// + /// A collection of function activity descriptors that are available to the DSL. + /// + public IDictionary FunctionActivityDescriptors { get; set; } = new Dictionary(); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Scripting/JavaScript/InputFunctionsDefinitionProvider.cs b/src/modules/Elsa.Workflows.Management/Scripting/JavaScript/InputFunctionsDefinitionProvider.cs new file mode 100644 index 0000000000..5927c62aee --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Scripting/JavaScript/InputFunctionsDefinitionProvider.cs @@ -0,0 +1,46 @@ +using Elsa.Common.Models; +using Elsa.JavaScript.Contracts; +using Elsa.JavaScript.TypeDefinitions.Abstractions; +using Elsa.JavaScript.TypeDefinitions.Models; +using Elsa.Workflows.Management.Contracts; +using Elsa.Workflows.Management.Entities; +using Humanizer; + +namespace Elsa.Workflows.Management.Scripting.JavaScript; + +/// +/// Produces s for common functions. +/// +internal class InputFunctionsDefinitionProvider : FunctionDefinitionProvider +{ + private readonly ITypeAliasRegistry _typeAliasRegistry; + private readonly IWorkflowDefinitionService _workflowDefinitionService; + + public InputFunctionsDefinitionProvider(ITypeAliasRegistry typeAliasRegistry, IWorkflowDefinitionService workflowDefinitionService) + { + _typeAliasRegistry = typeAliasRegistry; + _workflowDefinitionService = workflowDefinitionService; + } + + protected override async ValueTask> GetFunctionDefinitionsAsync(TypeDefinitionContext context) + { + var cancellationToken = context.CancellationToken; + var workflow = context.Workflow; + var workflowDefinition = await _workflowDefinitionService.FindAsync(workflow.Identity.DefinitionId, VersionOptions.SpecificVersion(workflow.Identity.Version), cancellationToken); + return workflowDefinition == null ? Array.Empty() : GetFunctionDefinitionsAsync(workflowDefinition); + } + + private IEnumerable GetFunctionDefinitionsAsync(WorkflowDefinition workflowDefinition) + { + // Input argument getters. + foreach (var input in workflowDefinition.Inputs) + { + var pascalName = input.Name.Pascalize(); + var variableType = input.Type; + var typeAlias = _typeAliasRegistry.TryGetAlias(variableType, out var alias) ? alias : "any"; + + // get{Input}. + yield return CreateFunctionDefinition(builder => builder.Name($"get{pascalName}").ReturnType(typeAlias)); + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionProvider.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowProvider.cs similarity index 73% rename from src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionProvider.cs rename to src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowProvider.cs index 970613f7c1..81c1d8314b 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowDefinitionProvider.cs +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowProvider.cs @@ -5,7 +5,7 @@ namespace Elsa.Workflows.Runtime.Contracts; /// /// Represents a source of workflow definitions. /// -public interface IWorkflowDefinitionProvider +public interface IWorkflowProvider { /// /// Gets the name of the provider. @@ -17,5 +17,5 @@ public interface IWorkflowDefinitionProvider /// /// The cancellation token. /// The workflow definitions. - ValueTask> GetWorkflowDefinitionsAsync(CancellationToken cancellationToken = default); + ValueTask> GetWorkflowDefinitionsAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs index c6bcee306b..17089eb2cd 100644 --- a/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs +++ b/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs @@ -1,4 +1,5 @@ using Elsa.Workflows.Runtime.Contracts; +using Elsa.Workflows.Runtime.Providers; using Elsa.Workflows.Runtime.Services; // ReSharper disable once CheckNamespace @@ -10,10 +11,10 @@ namespace Microsoft.Extensions.DependencyInjection; public static class DependencyInjectionExtensions { /// - /// Adds the to the service collection. + /// Adds the to the service collection. /// /// The service collection. /// The type of the workflow definition provider. /// The service collection. - public static IServiceCollection AddWorkflowDefinitionProvider(this IServiceCollection services) where T : class, IWorkflowDefinitionProvider => services.AddSingleton(); + public static IServiceCollection AddWorkflowDefinitionProvider(this IServiceCollection services) where T : class, IWorkflowProvider => services.AddSingleton(); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs index 623cc0d086..b39e808f05 100644 --- a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs +++ b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs @@ -17,6 +17,7 @@ using Elsa.Workflows.Runtime.HostedServices; using Elsa.Workflows.Runtime.Notifications; using Elsa.Workflows.Runtime.Options; +using Elsa.Workflows.Runtime.Providers; using Elsa.Workflows.Runtime.Services; using Medallion.Threading; using Medallion.Threading.FileSystem; @@ -124,7 +125,6 @@ public override void Apply() // Core. .AddSingleton() .AddSingleton() - .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(WorkflowRuntime) @@ -140,7 +140,7 @@ public override void Apply() .AddSingleton() // Lazy services. - .AddSingleton>>(sp => sp.GetServices) + .AddSingleton>>(sp => sp.GetServices) .AddSingleton>>(sp => sp.GetServices) // Memory stores. @@ -153,7 +153,7 @@ public override void Apply() .AddSingleton(DistributedLockProvider) // Workflow definition providers. - .AddWorkflowDefinitionProvider() + .AddWorkflowDefinitionProvider() // Workflow state exporter. .AddSingleton(WorkflowStateExporter) diff --git a/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs index d4b19eeb64..bc81f621b6 100644 --- a/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs @@ -1,34 +1,45 @@ +using Elsa.Common.Contracts; using Elsa.Common.Models; -using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Entities; using Elsa.Workflows.Management.Filters; using Elsa.Workflows.Runtime.Contracts; +using Elsa.Workflows.Runtime.Models; using Microsoft.Extensions.Hosting; using Open.Linq.AsyncExtensions; namespace Elsa.Workflows.Runtime.HostedServices; /// -/// Synchronously updates the workflow definition store from implementations and creates triggers. +/// Updates the workflow store from implementations and creates triggers. /// public class PopulateWorkflowDefinitionStore : IHostedService { - private readonly Func> _workflowDefinitionProviders; + private readonly Func> _workflowDefinitionProviders; private readonly ITriggerIndexer _triggerIndexer; private readonly IWorkflowDefinitionStore _workflowDefinitionStore; + private readonly IActivitySerializer _activitySerializer; + private readonly IPayloadSerializer _payloadSerializer; + private readonly ISystemClock _systemClock; /// /// Constructor. /// public PopulateWorkflowDefinitionStore( - Func> workflowDefinitionProviders, + Func> workflowDefinitionProviders, ITriggerIndexer triggerIndexer, - IWorkflowDefinitionStore workflowDefinitionStore) + IWorkflowDefinitionStore workflowDefinitionStore, + IActivitySerializer activitySerializer, + IPayloadSerializer payloadSerializer, + ISystemClock systemClock) { _workflowDefinitionProviders = workflowDefinitionProviders; _triggerIndexer = triggerIndexer; _workflowDefinitionStore = workflowDefinitionStore; + _activitySerializer = activitySerializer; + _payloadSerializer = payloadSerializer; + _systemClock = systemClock; } /// @@ -41,43 +52,53 @@ public async Task StartAsync(CancellationToken cancellationToken) foreach (var result in results) { - await AddOrUpdateAsync(result.Definition, cancellationToken); - await IndexTriggersAsync(result.Workflow, cancellationToken); + await AddOrUpdateAsync(result, cancellationToken); + await IndexTriggersAsync(result, cancellationToken); } } } - private async Task AddOrUpdateAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default) + private async Task AddOrUpdateAsync(MaterializedWorkflow materializedWorkflow, CancellationToken cancellationToken = default) { - // Check if there's already a workflow definition by the definition ID and version. + var workflow = materializedWorkflow.Workflow; + + // Check if there's already a workflow materializedWorkflow by the materializedWorkflow ID and version. var filter = new WorkflowDefinitionFilter { - DefinitionId = definition.DefinitionId, - VersionOptions = VersionOptions.SpecificVersion(definition.Version) + DefinitionId = workflow.Identity.DefinitionId, + VersionOptions = VersionOptions.SpecificVersion(workflow.Version) }; - - var existingDefinition = await _workflowDefinitionStore.FindAsync(filter, cancellationToken); - if (existingDefinition == null) - { - existingDefinition = definition; - } - else + // Serialize materializer context. + var materializerContext = materializedWorkflow.MaterializerContext; + var materializerContextJson = materializerContext != null ? _payloadSerializer.Serialize(materializerContext) : default; + + // Serialize the workflow root. + var workflowJson = _activitySerializer.Serialize(workflow.Root); + + // Check if there's already a workflow definition stored with this workflow. + var existingDefinition = await _workflowDefinitionStore.FindAsync(filter, cancellationToken) ?? new WorkflowDefinition { - existingDefinition.Description = definition.Description; - existingDefinition.Name = definition.Name; - existingDefinition.CustomProperties = definition.CustomProperties; - existingDefinition.Variables = definition.Variables; - existingDefinition.BinaryData = definition.BinaryData; - existingDefinition.StringData = definition.StringData; - existingDefinition.MaterializerContext = definition.MaterializerContext; - existingDefinition.MaterializerName = definition.MaterializerName; - } + DefinitionId = workflow.Identity.DefinitionId, + Id = workflow.Identity.Id, + Version = workflow.Identity.Version + }; + + existingDefinition.Description = workflow.WorkflowMetadata.Description; + existingDefinition.Name = workflow.WorkflowMetadata.Name; + existingDefinition.IsLatest = workflow.Publication.IsLatest; + existingDefinition.IsPublished = workflow.Publication.IsPublished; + existingDefinition.CustomProperties = workflow.CustomProperties; + existingDefinition.Variables = workflow.Variables; + existingDefinition.StringData = workflowJson; + existingDefinition.CreatedAt = workflow.WorkflowMetadata.CreatedAt == default ? _systemClock.UtcNow : workflow.WorkflowMetadata.CreatedAt; + existingDefinition.MaterializerContext = materializerContextJson; + existingDefinition.MaterializerName = materializedWorkflow.MaterializerName; await _workflowDefinitionStore.SaveAsync(existingDefinition, cancellationToken); } - private async Task IndexTriggersAsync(Workflow workflow, CancellationToken cancellationToken) => await _triggerIndexer.IndexTriggersAsync(workflow, cancellationToken); + private async Task IndexTriggersAsync(MaterializedWorkflow workflow, CancellationToken cancellationToken) => await _triggerIndexer.IndexTriggersAsync(workflow.Workflow, cancellationToken); /// public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; diff --git a/src/modules/Elsa.Workflows.Runtime/Models/MaterializedWorkflow.cs b/src/modules/Elsa.Workflows.Runtime/Models/MaterializedWorkflow.cs new file mode 100644 index 0000000000..a095818406 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Models/MaterializedWorkflow.cs @@ -0,0 +1,12 @@ +using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Management.Entities; + +namespace Elsa.Workflows.Runtime.Models; + +/// +/// Represents a workflow definition and its workflow. +/// +/// The workflow materialized from its workflow definition. +/// The name of the materializer that materialized the workflow. +/// The context of the materializer that materialized the workflow. +public record MaterializedWorkflow(Workflow Workflow, string MaterializerName, object? MaterializerContext = default); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Models/WorkflowDefinitionResult.cs b/src/modules/Elsa.Workflows.Runtime/Models/WorkflowDefinitionResult.cs deleted file mode 100644 index 4dbab13d25..0000000000 --- a/src/modules/Elsa.Workflows.Runtime/Models/WorkflowDefinitionResult.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Management.Entities; - -namespace Elsa.Workflows.Runtime.Models; - -/// -/// Represents a workflow definition and its workflow. -/// -/// The workflow definition. -/// The workflow materialized from its workflow definition. -public record WorkflowDefinitionResult(WorkflowDefinition Definition, Workflow Workflow); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowProvider.cs b/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowProvider.cs new file mode 100644 index 0000000000..9caf688f68 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Providers/ClrWorkflowProvider.cs @@ -0,0 +1,58 @@ +using Elsa.Workflows.Core.Contracts; +using Elsa.Workflows.Management.Materializers; +using Elsa.Workflows.Runtime.Contracts; +using Elsa.Workflows.Runtime.Features; +using Elsa.Workflows.Runtime.Models; +using Elsa.Workflows.Runtime.Options; +using Microsoft.Extensions.Options; + +namespace Elsa.Workflows.Runtime.Providers; + +/// +/// Provides workflows to the system that are registered with +/// +public class ClrWorkflowProvider : IWorkflowProvider +{ + private readonly IWorkflowBuilderFactory _workflowBuilderFactory; + private readonly IServiceProvider _serviceProvider; + private readonly RuntimeOptions _options; + + /// + /// Constructor. + /// + public ClrWorkflowProvider( + IOptions options, + IWorkflowBuilderFactory workflowBuilderFactory, + IServiceProvider serviceProvider + ) + { + _workflowBuilderFactory = workflowBuilderFactory; + _serviceProvider = serviceProvider; + _options = options.Value; + } + + /// + public string Name => "CLR"; + + /// + public async ValueTask> GetWorkflowDefinitionsAsync(CancellationToken cancellationToken = default) + { + var workflowDefinitionTasks = _options.Workflows.Values.Select(async x => await BuildWorkflowDefinition(x, cancellationToken)).ToList(); + var workflowDefinitions = await Task.WhenAll(workflowDefinitionTasks); + return workflowDefinitions; + } + + private async Task BuildWorkflowDefinition(Func> workflowFactory, CancellationToken cancellationToken) + { + var builder = _workflowBuilderFactory.CreateBuilder(); + var workflowBuilder = await workflowFactory(_serviceProvider); + var workflowBuilderType = workflowBuilder.GetType(); + + builder.DefinitionId = workflowBuilderType.Name; + await workflowBuilder.BuildAsync(builder, cancellationToken); + + var workflow = await builder.BuildWorkflowAsync(cancellationToken); + var materializerContext = new ClrWorkflowMaterializerContext(workflowBuilder.GetType()); + return new MaterializedWorkflow(workflow, ClrWorkflowMaterializer.MaterializerName, materializerContext); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/ClrWorkflowDefinitionProvider.cs b/src/modules/Elsa.Workflows.Runtime/Services/ClrWorkflowDefinitionProvider.cs deleted file mode 100644 index dca4224654..0000000000 --- a/src/modules/Elsa.Workflows.Runtime/Services/ClrWorkflowDefinitionProvider.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Elsa.Common.Contracts; -using Elsa.Workflows.Core.Contracts; -using Elsa.Workflows.Management.Entities; -using Elsa.Workflows.Management.Materializers; -using Elsa.Workflows.Runtime.Contracts; -using Elsa.Workflows.Runtime.Features; -using Elsa.Workflows.Runtime.Models; -using Elsa.Workflows.Runtime.Options; -using Microsoft.Extensions.Options; - -namespace Elsa.Workflows.Runtime.Services; - -/// -/// Provides workflows to the system that are registered with -/// -public class ClrWorkflowDefinitionProvider : IWorkflowDefinitionProvider -{ - private readonly IWorkflowBuilderFactory _workflowBuilderFactory; - private readonly IActivitySerializer _activitySerializer; - private readonly IPayloadSerializer _payloadSerializer; - private readonly ISystemClock _systemClock; - private readonly IServiceProvider _serviceProvider; - private readonly RuntimeOptions _options; - - /// - /// Constructor. - /// - public ClrWorkflowDefinitionProvider( - IOptions options, - IWorkflowBuilderFactory workflowBuilderFactory, - IActivitySerializer activitySerializer, - IPayloadSerializer payloadSerializer, - ISystemClock systemClock, - IServiceProvider serviceProvider - ) - { - _workflowBuilderFactory = workflowBuilderFactory; - _activitySerializer = activitySerializer; - _payloadSerializer = payloadSerializer; - _systemClock = systemClock; - _serviceProvider = serviceProvider; - _options = options.Value; - } - - /// - public string Name => "CLR"; - - /// - public async ValueTask> GetWorkflowDefinitionsAsync(CancellationToken cancellationToken = default) - { - var workflowDefinitionTasks = _options.Workflows.Values.Select(async x => await BuildWorkflowDefinition(x, cancellationToken)).ToList(); - var workflowDefinitions = await Task.WhenAll(workflowDefinitionTasks); - return workflowDefinitions; - } - - private async Task BuildWorkflowDefinition(Func> workflowFactory, CancellationToken cancellationToken) - { - var builder = _workflowBuilderFactory.CreateBuilder(); - var workflowBuilder = await workflowFactory(_serviceProvider); - var workflowBuilderType = workflowBuilder.GetType(); - - builder.DefinitionId = workflowBuilderType.Name; - await workflowBuilder.BuildAsync(builder, cancellationToken); - - var workflow = await builder.BuildWorkflowAsync(cancellationToken); - var workflowJson = _activitySerializer.Serialize(workflow.Root); - var materializerContext = new ClrWorkflowMaterializerContext(workflowBuilder.GetType()); - var materializerContextJson = _payloadSerializer.Serialize(materializerContext); - var name = string.IsNullOrWhiteSpace(workflow.WorkflowMetadata.Name) ? workflowBuilderType.Name : workflow.WorkflowMetadata.Name.Trim(); - - var definition = new WorkflowDefinition - { - Id = workflow.Identity.Id, - DefinitionId = workflow.Identity.DefinitionId, - Version = workflow.Identity.Version, - Name = name, - Description = workflow.WorkflowMetadata.Description, - CustomProperties = workflow.Metadata, - Variables = workflow.Variables, - IsLatest = workflow.Publication.IsLatest, - IsPublished = workflow.Publication.IsPublished, - CreatedAt = workflow.WorkflowMetadata.CreatedAt == default ? _systemClock.UtcNow : workflow.WorkflowMetadata.CreatedAt, - MaterializerName = ClrWorkflowMaterializer.MaterializerName, - MaterializerContext = materializerContextJson, - StringData = workflowJson - }; - - return new WorkflowDefinitionResult(definition, workflow); - } -} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Elsa.Samples.AspNet.DslWorkflowProvider.csproj b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Elsa.Samples.AspNet.DslWorkflowProvider.csproj new file mode 100644 index 0000000000..f6c7c8fd3e --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Elsa.Samples.AspNet.DslWorkflowProvider.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + Always + + + Always + + + + diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Program.cs b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Program.cs new file mode 100644 index 0000000000..cb7e0ae638 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Program.cs @@ -0,0 +1,62 @@ +using Elsa.EntityFrameworkCore.Modules.Management; +using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.Extensions; +using Elsa.Http; +using Elsa.Workflows.Core.Activities; + +var builder = WebApplication.CreateBuilder(args); +var services = builder.Services; + +// Add Elsa services. +services.AddElsa(elsa => elsa + // Add the Fluent Storage workflow definition provider. + .UseFluentStorageProvider() + + // Enable the Elsa DSL. + .UseWorkflowManagement(management => + { + management.UseEntityFrameworkCore(); + management.UseDslIntegration(dsl => + { + dsl.MapActivityFunction("println", nameof(WriteLine), new[] { nameof(WriteLine.Text) }); + dsl.MapActivityFunction("http_listen", nameof(HttpEndpoint), new[] { nameof(HttpEndpoint.Path), nameof(HttpEndpoint.SupportedMethods) }, activity => activity.SetCanStartWorkflow(true)); + dsl.MapActivityFunction("http_write", nameof(WriteHttpResponse), new[] { nameof(WriteHttpResponse.StatusCode), nameof(WriteHttpResponse.Content) }); + }); + }) + .UseWorkflowRuntime(runtime => + { + runtime.UseEntityFrameworkCore(); + runtime.UseExecutionLogRecords(); + runtime.UseAsyncWorkflowStateExporter(); + }) + + // Expose API endpoints. + .UseWorkflowsApi() + + // Configure identity so that we can create a default admin user. + .UseIdentity(identity => + { + identity.UseAdminUserProvider(); + identity.TokenOptions = options => + { + options.SigningKey = "secret-token-signing-key"; + options.AccessTokenLifetime = TimeSpan.FromDays(1); + }; + }) + + // Use default authentication (JWT). + .UseDefaultAuthentication(auth => auth.UseAdminApiKey()) + + // Use HTTP activities. + .UseHttp() +); + + +// Configure middleware pipeline. +var app = builder.Build(); +// Configure the HTTP request pipeline. +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.DslWorkflowProvider/Properties/launchSettings.json b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Properties/launchSettings.json new file mode 100644 index 0000000000..b96e8db2f0 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Properties/launchSettings.json @@ -0,0 +1,37 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:11864", + "sslPort": 44339 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5006", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7185;http://localhost:5006", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Workflows/hello-world-functional.elsa b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Workflows/hello-world-functional.elsa new file mode 100644 index 0000000000..7c6b5b7fd6 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Workflows/hello-world-functional.elsa @@ -0,0 +1,8 @@ +// Listen for HTTP requests on the path /hello-world-dsl. +http_listen("hello-world-functional-dsl", ["GET"]); + +// Print a message to the console. +println("Hello functional DSL World!"); + +// Write an HTTP response. +http_write("200", "Hello functional DSL World!"); \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Workflows/hello-world.elsa b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Workflows/hello-world.elsa new file mode 100644 index 0000000000..62a6c823d8 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/Workflows/hello-world.elsa @@ -0,0 +1,20 @@ +// Listen for HTTP requests on the path /hello-world-dsl. +HttpEndpoint +{ + Path: "hello-world-dsl", + SupportedMethods: ["GET"], + CanStartWorkflow: "true" +}; + +// Write a message to the console. +WriteLine +{ + Text: "Hello DSL World" +}; + +// Write an HTTP response. +WriteHttpResponse +{ + StatusCode: "200", + Content: "Hello DSL World!" +}; \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/appsettings.json b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/appsettings.json new file mode 100644 index 0000000000..613488557d --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.AspNet.DslWorkflowProvider/appsettings.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Elsa.Mediator": "Warning", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" + } + }, + "AllowedHosts": "*" +}