diff --git a/Elsa.sln b/Elsa.sln
index 48812db79..8c078cad5 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 32c5698d5..575654816 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 class Module : IModule
}
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 94be83a9c..9712be22f 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 f2b4fbb24..614803f12 100644
--- a/src/common/Elsa.Testing.Shared/TestApplicationBuilder.cs
+++ b/src/common/Elsa.Testing.Shared/TestApplicationBuilder.cs
@@ -37,6 +37,7 @@ public class TestApplicationBuilder
.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 14c44d42f..79006699a 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 ebaa5f75d..0f2373117 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 4940cf583..a090d0b92 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 partial class WorkflowDefinitionBuilderInterpreter
return DefaultResult;
}
+ ///
public override IWorkflowBuilder VisitObjectStat(ElsaParser.ObjectStatContext context)
{
VisitChildren(context);
@@ -22,9 +26,34 @@ public partial class WorkflowDefinitionBuilderInterpreter
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 partial class WorkflowDefinitionBuilderInterpreter
_expressionValue.Put(context, definedVariable.Value);
return DefaultResult;
}
-
+
// Or a workflow variable?
var workflowVariableQuery =
from container in _containerStack
@@ -50,17 +79,13 @@ public partial class WorkflowDefinitionBuilderInterpreter
_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 370e59222..ba024c4ad 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 partial class WorkflowDefinitionBuilderInterpreter
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 @@ public partial class WorkflowDefinitionBuilderInterpreter
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 6b14ecec1..97415f240 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 000000000..223eb9583
--- /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 1ee715ddb..76680183c 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 class DslEngine : IDslEngine
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 1aa10f831..77121d5cb 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(activityType, properties);
- _dictionary.Add(functionName, descriptor);
+ var descriptor = new FunctionActivityDescriptor(functionName, activityTypeName, propertyNames, configure);
+ RegisterFunction(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 class FunctionActivityRegistry : IFunctionActivityRegistry
SetPropertyValue(activity, property, argument);
}
+ descriptor.Configure?.Invoke(activity);
return activity;
}
-
- private void SetPropertyValue(object target, PropertyInfo propertyInfo, object? value)
- {
- if (typeof(Input).IsAssignableFrom(propertyInfo.PropertyType))
- value = CreateInputValue(propertyInfo, value);
- else if (typeof(Output).IsAssignableFrom(propertyInfo.PropertyType))
- value = CreateOutputValue(propertyInfo, value);
- propertyInfo.SetValue(target, value, null);
+ private void SetPropertyValue(IActivity target, PropertyDescriptor propertyDescriptor, object? value)
+ {
+ value = propertyDescriptor switch
+ {
+ InputDescriptor inputDescriptor => CreateInputValue(inputDescriptor, value),
+ OutputDescriptor outputDescriptor => CreateOutputValue(outputDescriptor, value),
+ _ => value
+ };
+
+ 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 @@ public class FunctionActivityRegistry : IFunctionActivityRegistry
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 c21505e8c..5f7659086 100644
--- a/src/modules/Elsa.EntityFrameworkCore/Common/PersistenceFeatureBase.cs
+++ b/src/modules/Elsa.EntityFrameworkCore/Common/PersistenceFeatureBase.cs
@@ -15,7 +15,11 @@ public abstract class PersistenceFeatureBase : FeatureBase where TDb
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 bdb72d863..406934a94 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 72c0f8dd9..10175af02 100644
--- a/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs
+++ b/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs
@@ -4,7 +4,6 @@ using Elsa.Extensions;
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.Contracts;
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 class JavaScriptFeature : FeatureBase
{
}
- ///
- public override void Configure()
- {
- Module.UseWorkflowManagement(management => management.AddActivitiesFrom());
- }
-
///
public override void Apply()
{
@@ -44,7 +36,6 @@ public class JavaScriptFeature : FeatureBase
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 e8233dc2e..c345b6402 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 class TypeAliasRegistry : ITypeAliasRegistry
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 4be199c84..9346f5795 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 6af692fda..a149ee732 100644
--- a/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/CommonFunctionsDefinitionProvider.cs
+++ b/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/CommonFunctionsDefinitionProvider.cs
@@ -80,7 +80,7 @@ internal class CommonFunctionsDefinitionProvider : FunctionDefinitionProvider
.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 @@ internal class CommonFunctionsDefinitionProvider : FunctionDefinitionProvider
// 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 167ab0152..1d435c0fa 100644
--- a/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/VariableTypeDefinitionProvider.cs
+++ b/src/modules/Elsa.JavaScript/TypeDefinitions/Providers/VariableTypeDefinitionProvider.cs
@@ -27,7 +27,7 @@ internal class VariableTypeDefinitionProvider : TypeDefinitionProvider
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 4d1d84bac..7579cd828 100644
--- a/src/modules/Elsa.WorkflowContexts/Scripting/JavaScript/ConfigureJavaScriptEngine.cs
+++ b/src/modules/Elsa.WorkflowContexts/Scripting/JavaScript/ConfigureJavaScriptEngine.cs
@@ -50,7 +50,7 @@ public class ConfigureJavaScriptEngine : INotificationHandler
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 class ConfigureJavaScriptEngine : INotificationHandler
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 @@ public class ConfigureJavaScriptEngine : INotificationHandler 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 67ccc4a90..fe0aaff79 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.Abstractions;
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 class FluentStorageFeature : FeatureBase
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 397e5f3c4..cd0e90914 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 000000000..a324e8428
--- /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 29720c389..000000000
--- 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 241329247..41c92a6c3 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 System.Text;
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 @@ internal class Get : Endpoint
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 d03c8750a..e68187fbf 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 1f4c4a97f..0a8746cf6 100644
--- a/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs
+++ b/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs
@@ -33,7 +33,7 @@ public class ActivityDescriber : IActivityDescriber
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 @@ public class ActivityDescriber : IActivityDescriber
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 1b6595ea1..419672871 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 d11ff2348..033260e5d 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 class ModuleExtensions
});
return module;
}
-
+
///
/// Adds the default workflow management feature to the specified module.
///
@@ -32,7 +32,7 @@ public static class ModuleExtensions
feature.Module.Configure(configure);
return feature;
}
-
+
///
/// Adds the workflow instance feature to workflow management module.
///
@@ -42,13 +42,31 @@ public static class ModuleExtensions
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 000000000..44c5fe425
--- /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 000000000..d8c6b4670
--- /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 294af9c5c..8084d0ac0 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.Core.Features;
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 class WorkflowManagementFeature : FeatureBase
.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 000000000..280b25c02
--- /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 000000000..53698a5e0
--- /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 570e4af71..fb01b82fa 100644
--- a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs
+++ b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs
@@ -44,6 +44,26 @@ public class WorkflowDefinitionMapper
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 000000000..356f9089e
--- /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 000000000..5927c62ae
--- /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 970613f7c..81c1d8314 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 c6bcee306..17089eb2c 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 623cc0d08..b39e808f0 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.Handlers;
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 class WorkflowRuntimeFeature : FeatureBase
// Core.
.AddSingleton()
.AddSingleton()
- .AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton(WorkflowRuntime)
@@ -140,7 +140,7 @@ public class WorkflowRuntimeFeature : FeatureBase
.AddSingleton()
// Lazy services.
- .AddSingleton>>(sp => sp.GetServices)
+ .AddSingleton>>(sp => sp.GetServices)
.AddSingleton>>(sp => sp.GetServices)
// Memory stores.
@@ -153,7 +153,7 @@ public class WorkflowRuntimeFeature : FeatureBase
.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 d4b19eeb6..bc81f621b 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 class PopulateWorkflowDefinitionStore : IHostedService
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)
+ // 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 = definition;
- }
- else
- {
- 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 000000000..a09581840
--- /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 4dbab13d2..000000000
--- 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 000000000..9caf688f6
--- /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 dca422465..000000000
--- 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 000000000..f6c7c8fd3
--- /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 000000000..cb7e0ae63
--- /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 000000000..b96e8db2f
--- /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 000000000..7c6b5b7fd
--- /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 000000000..62a6c823d
--- /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 000000000..613488557
--- /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": "*"
+}