Update DSL implementation and integration (#4073)
This commit is contained in:
parent
7d56d00739
commit
6f455f7e70
7
Elsa.sln
7
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
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace Elsa.Features.Implementations;
|
|||
/// <inheritdoc />
|
||||
public class Module : IModule
|
||||
{
|
||||
private record HostedServiceDescriptor(int Order, Type HostedServiceType);
|
||||
private record HostedServiceDescriptor(int Order, Type Type);
|
||||
|
||||
private IDictionary<Type, IFeature> _features = new Dictionary<Type, IFeature>();
|
||||
private readonly ISet<IFeature> _configuredFeatures = new HashSet<IFeature>();
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -17,22 +17,22 @@ public interface IModule
|
|||
/// A dictionary into which features can stash away values for later use.
|
||||
/// </summary>
|
||||
IDictionary<object, object> Properties { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates and configures a feature of the specified type.
|
||||
/// </summary>
|
||||
T Configure<T>(Action<T>? configure = default) where T : class, IFeature;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates and configures a feature of the specified type.
|
||||
/// </summary>
|
||||
T Configure<T>(Func<IModule, T> factory, Action<T>? configure = default) where T : class, IFeature;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Configures a <see cref="IHostedService"/> using an optional priority to control in which order it will be registered with the service container.
|
||||
/// </summary>
|
||||
IModule ConfigureHostedService<T>(int priority = 0) where T : class, IHostedService;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Will apply all configured features, causing the <see cref="Services"/> collection to be populated.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public class TestApplicationBuilder
|
|||
.UseScheduling()
|
||||
.UseJavaScript()
|
||||
.UseLiquid()
|
||||
.UseDsl()
|
||||
.UseWorkflows(workflows => workflows
|
||||
.WithStandardOutStreamProvider(_ => new StandardOutStreamProvider(new XunitConsoleTextWriter(_testOutputHelper)))
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,33 @@
|
|||
using Elsa.Dsl.Models;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
|
||||
namespace Elsa.Dsl.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a registry for mapping functions to activities that can be invoked from a DSL script.
|
||||
/// </summary>
|
||||
public interface IFunctionActivityRegistry
|
||||
{
|
||||
void RegisterFunction(string functionName, string activityTypeName, IEnumerable<string>? propertyNames = default);
|
||||
/// <summary>
|
||||
/// Registers a function that is mapped to an activity that can be invoked from a DSL script.
|
||||
/// </summary>
|
||||
/// <param name="functionName">The name of the function.</param>
|
||||
/// <param name="activityTypeName">The name of the activity type.</param>
|
||||
/// <param name="propertyNames">The names of the properties that are mapped to the function arguments.</param>
|
||||
/// <param name="configure">An optional action that can be used to configure the activity.</param>
|
||||
void RegisterFunction(string functionName, string activityTypeName, IEnumerable<string>? propertyNames = default, Action<IActivity>? configure = default);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a function that is mapped to an activity that can be invoked from a DSL script.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The descriptor that describes the function.</param>
|
||||
void RegisterFunction(FunctionActivityDescriptor descriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a function to an activity that can be invoked from a DSL script.
|
||||
/// </summary>
|
||||
/// <param name="functionName">The name of the function.</param>
|
||||
/// <param name="arguments">The arguments that are passed to the function.</param>
|
||||
/// <returns>An activity that can be invoked from a DSL script.</returns>
|
||||
IActivity ResolveFunction(string functionName, IEnumerable<object?>? arguments = default);
|
||||
}
|
||||
|
|
@ -5,20 +5,21 @@ namespace Elsa.Dsl.Interpreters;
|
|||
|
||||
public partial class WorkflowDefinitionBuilderInterpreter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<ElsaParser.ObjectContext>(0);
|
||||
return _expressionValue.Get(objectContext);
|
||||
var stringContext = x.GetChild<ElsaParser.StringValueExprContext>(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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override IWorkflowBuilder VisitObjectExpr(ElsaParser.ObjectExprContext context)
|
||||
{
|
||||
VisitChildren(context);
|
||||
|
|
@ -13,6 +16,7 @@ public partial class WorkflowDefinitionBuilderInterpreter
|
|||
return DefaultResult;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IWorkflowBuilder VisitObjectStat(ElsaParser.ObjectStatContext context)
|
||||
{
|
||||
VisitChildren(context);
|
||||
|
|
@ -22,9 +26,34 @@ public partial class WorkflowDefinitionBuilderInterpreter
|
|||
return DefaultResult;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<JsonElement>("{}");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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)!;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<IWorkflowBuilder>
|
||||
{
|
||||
private readonly ITypeSystem _typeSystem;
|
||||
private readonly IActivityRegistry _activityRegistry;
|
||||
private readonly IFunctionActivityRegistry _functionActivityRegistry;
|
||||
private readonly IExpressionHandlerRegistry _expressionHandlerRegistry;
|
||||
private readonly IWorkflowBuilder _workflowBuilder;
|
||||
private readonly ParseTreeProperty<object> _object = new();
|
||||
private readonly ParseTreeProperty<object?> _expressionValue = new();
|
||||
|
|
@ -22,22 +21,17 @@ public partial class WorkflowDefinitionBuilderInterpreter : ElsaParserBaseVisito
|
|||
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IWorkflowBuilder DefaultResult => _workflowBuilder;
|
||||
|
||||
private void VisitMany(IEnumerable<IParseTree> contexts)
|
||||
{
|
||||
foreach (var parseTree in contexts) Visit(parseTree);
|
||||
}
|
||||
}
|
||||
12
src/modules/Elsa.Dsl/Models/FunctionActivityDescriptor.cs
Normal file
12
src/modules/Elsa.Dsl/Models/FunctionActivityDescriptor.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using Elsa.Workflows.Core.Contracts;
|
||||
|
||||
namespace Elsa.Dsl.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a function that is mapped to an activity that can be invoked from a DSL script.
|
||||
/// </summary>
|
||||
/// <param name="FunctionName">The name of the function.</param>
|
||||
/// <param name="ActivityTypeName">The name of the activity type.</param>
|
||||
/// <param name="PropertyNames">The names of the properties that are mapped to the function arguments.</param>
|
||||
/// <param name="Configure">An optional action that can be used to configure the activity.</param>
|
||||
public record FunctionActivityDescriptor(string FunctionName, string ActivityTypeName, IEnumerable<string>? PropertyNames = default, Action<IActivity>? Configure = default);
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -21,13 +19,13 @@ public class DslEngine : IDslEngine
|
|||
/// </summary>
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class FunctionActivityRegistry : IFunctionActivityRegistry
|
||||
{
|
||||
private readonly ITypeSystem _typeSystem;
|
||||
private readonly IActivityRegistry _activityRegistry;
|
||||
private readonly IDictionary<string, FunctionActivityDescriptor> _dictionary = new Dictionary<string, FunctionActivityDescriptor>();
|
||||
|
||||
public FunctionActivityRegistry(ITypeSystem typeSystem)
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="FunctionActivityRegistry"/> class.
|
||||
/// </summary>
|
||||
public FunctionActivityRegistry(IActivityRegistry activityRegistry)
|
||||
{
|
||||
_typeSystem = typeSystem;
|
||||
_activityRegistry = activityRegistry;
|
||||
}
|
||||
|
||||
public void RegisterFunction(string functionName, string activityTypeName, IEnumerable<string>? propertyNames = default)
|
||||
/// <inheritdoc />
|
||||
public void RegisterFunction(string functionName, string activityTypeName, IEnumerable<string>? propertyNames = default, Action<IActivity>? 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<string>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterFunction(FunctionActivityDescriptor descriptor)
|
||||
{
|
||||
_dictionary.Add(descriptor.FunctionName, descriptor);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IActivity ResolveFunction(string functionName, IEnumerable<object?>? 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<string>();
|
||||
var propertyDescriptors = activityDescriptor.Inputs.Cast<PropertyDescriptor>().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<PropertyInfo> Properties);
|
||||
}
|
||||
|
|
@ -15,7 +15,11 @@ public abstract class PersistenceFeatureBase<TDbContext> : FeatureBase where TDb
|
|||
public bool UseContextPooling { get; set; }
|
||||
public bool RunMigrations { get; set; } = true;
|
||||
public ServiceLifetime DbContextFactoryLifetime { get; set; } = ServiceLifetime.Singleton;
|
||||
public Action<IServiceProvider, DbContextOptionsBuilder> DbContextOptionsBuilder = (_, _) => { };
|
||||
|
||||
public Action<IServiceProvider, DbContextOptionsBuilder> 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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@
|
|||
<ProjectReference Include="..\Elsa.Expressions\Elsa.Expressions.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Mediator\Elsa.Mediator.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2044" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Module.UseWorkflowManagement(management => management.AddActivitiesFrom<JavaScriptFeature>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
|
|
@ -44,7 +36,6 @@ public class JavaScriptFeature : FeatureBase
|
|||
Services
|
||||
.AddSingleton<IExpressionSyntaxProvider, JavaScriptExpressionSyntaxProvider>()
|
||||
.AddSingleton<IJavaScriptEvaluator, JintJavaScriptEvaluator>()
|
||||
.AddSingleton<IActivityPropertyOptionsProvider, RunJavaScriptOptionsProvider>()
|
||||
.AddSingleton<ITypeDefinitionService, TypeDefinitionService>()
|
||||
.AddExpressionHandler<JavaScriptExpressionHandler, JavaScriptExpression>()
|
||||
;
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public TypeAliasRegistry(IOptions<ManagementOptions> managementOptions)
|
||||
public TypeAliasRegistry()
|
||||
{
|
||||
this.RegisterType<object>("any");
|
||||
this.RegisterType<ExpandoObject>("any");
|
||||
|
|
@ -30,12 +28,6 @@ public class TypeAliasRegistry : ITypeAliasRegistry
|
|||
this.RegisterType<DateTimeOffset>("Date");
|
||||
this.RegisterType<DateOnly>("Date");
|
||||
this.RegisterType<TimeOnly>("Date");
|
||||
|
||||
foreach (var variableDescriptor in managementOptions.Value.VariableDescriptors)
|
||||
{
|
||||
if(!_typeAliasDictionary.ContainsKey(variableDescriptor.Type))
|
||||
RegisterType(variableDescriptor.Type, variableDescriptor.Type.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using Elsa.Workflows.Management.Entities;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
namespace Elsa.JavaScript.TypeDefinitions.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Provides context to intellisense providers.
|
||||
/// </summary>
|
||||
public record TypeDefinitionContext(WorkflowDefinition WorkflowDefinition, string? ActivityTypeName, string? PropertyName, CancellationToken CancellationToken);
|
||||
public record TypeDefinitionContext(Workflow Workflow, string? ActivityTypeName, string? PropertyName, CancellationToken CancellationToken);
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class ConfigureJavaScriptEngine : INotificationHandler<EvaluatingJavaScri
|
|||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<TypeDefinition>> 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<EvaluatingJavaScri
|
|||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<FunctionDefinition>> 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<EvaluatingJavaScri
|
|||
}
|
||||
|
||||
private IEnumerable<Type> GetProviderTypes(WorkflowExecutionContext workflowExecutionContext) => workflowExecutionContext.Workflow.GetWorkflowContextProviderTypes();
|
||||
private IEnumerable<Type> GetProviderTypes(WorkflowDefinition workflowDefinition) => workflowDefinition.GetWorkflowContextProviderTypes();
|
||||
private IEnumerable<Type> GetProviderTypes(Workflow workflow) => workflow.GetWorkflowContextProviderTypes();
|
||||
}
|
||||
|
|
@ -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<IBlobStorageProvider>(sp => new BlobStorageProvider(BlobStorage(sp)));
|
||||
Services.AddWorkflowDefinitionProvider<FluentStorageWorkflowDefinitionProvider>();
|
||||
Services.AddWorkflowDefinitionProvider<FluentStorageWorkflowProvider>();
|
||||
}
|
||||
|
||||
private static string GetDefaultWorkflowsDirectory()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using Elsa.WorkflowProviders.FluentStorage.Contracts;
|
||||
using FluentStorage.Blobs;
|
||||
|
||||
namespace Elsa.WorkflowProviders.FluentStorage.Services;
|
||||
namespace Elsa.WorkflowProviders.FluentStorage.Providers;
|
||||
|
||||
/// <summary>
|
||||
/// A provider of <see cref="IBlobStorage"/>.
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// A workflow definition provider that loads workflow definitions from a storage using FluentStorage (See https://github.com/robinrodricks/FluentStorage).
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FluentStorageWorkflowProvider"/> class.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "FluentStorage";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<IEnumerable<MaterializedWorkflow>> 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<MaterializedWorkflow>();
|
||||
|
||||
foreach (var blob in blobs)
|
||||
{
|
||||
var result = await ReadWorkflowDefinitionAsync(blob, cancellationToken);
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private async Task<MaterializedWorkflow> 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<MaterializedWorkflow> 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<WorkflowDefinitionModel>(json);
|
||||
var workflow = _workflowDefinitionMapper.Map(workflowDefinitionModel);
|
||||
|
||||
return new MaterializedWorkflow(workflow, JsonWorkflowMaterializer.MaterializerName);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// A workflow definition provider that loads workflow definitions from a storage using FluentStorage (See https://github.com/robinrodricks/FluentStorage).
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FluentStorageWorkflowDefinitionProvider"/> class.
|
||||
/// </summary>
|
||||
public FluentStorageWorkflowDefinitionProvider(
|
||||
IBlobStorageProvider blobStorageProvider,
|
||||
IActivitySerializer activitySerializer,
|
||||
ISystemClock systemClock,
|
||||
WorkflowDefinitionMapper workflowDefinitionMapper,
|
||||
VariableDefinitionMapper variableDefinitionMapper)
|
||||
{
|
||||
_blobStorageProvider = blobStorageProvider;
|
||||
_activitySerializer = activitySerializer;
|
||||
_systemClock = systemClock;
|
||||
_workflowDefinitionMapper = workflowDefinitionMapper;
|
||||
_variableDefinitionMapper = variableDefinitionMapper;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "FluentStorage";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<IEnumerable<WorkflowDefinitionResult>> 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<WorkflowDefinitionResult>();
|
||||
|
||||
foreach (var blob in blobs)
|
||||
{
|
||||
var result = await ReadWorkflowDefinitionAsync(blob, cancellationToken);
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private async Task<WorkflowDefinitionResult> ReadWorkflowDefinitionAsync(Blob blob, CancellationToken cancellationToken)
|
||||
{
|
||||
var blobStorage = _blobStorageProvider.GetBlobStorage();
|
||||
var workflowJson = await blobStorage.ReadTextAsync(blob.FullPath, cancellationToken: cancellationToken);
|
||||
var workflowDefinitionModel = _activitySerializer.Deserialize<WorkflowDefinitionModel>(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<string, object>(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Request>
|
||||
{
|
||||
private readonly ITypeDefinitionService _typeDefinitionService;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IWorkflowDefinitionService _workflowDefinitionService;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Get(ITypeDefinitionService typeDefinitionService, IServiceProvider serviceProvider)
|
||||
public Get(ITypeDefinitionService typeDefinitionService, IWorkflowDefinitionService workflowDefinitionService)
|
||||
{
|
||||
_typeDefinitionService = typeDefinitionService;
|
||||
_serviceProvider = serviceProvider;
|
||||
_workflowDefinitionService = workflowDefinitionService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -53,11 +54,14 @@ internal class Get : Endpoint<Request>
|
|||
await SendBytesAsync(data, fileName, "application/x-typescript", cancellation: cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<WorkflowDefinition?> GetWorkflowDefinition(string workflowDefinitionId, CancellationToken cancellationToken)
|
||||
private async Task<Workflow?> GetWorkflowDefinition(string workflowDefinitionId, CancellationToken cancellationToken)
|
||||
{
|
||||
var workflowDefinitionService = _serviceProvider.GetService<IWorkflowDefinitionService>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,13 +11,54 @@ namespace Elsa.Workflows.Core.Models;
|
|||
[DebuggerDisplay("{TypeName}")]
|
||||
public class ActivityDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// The fully qualified name of the activity type.
|
||||
/// </summary>
|
||||
public string TypeName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The namespace of the activity type.
|
||||
/// </summary>
|
||||
public string Namespace { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the activity type.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The version of the activity type.
|
||||
/// </summary>
|
||||
public int Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The category of the activity type.
|
||||
/// </summary>
|
||||
public string Category { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The display name of the activity type.
|
||||
/// </summary>
|
||||
public string? DisplayName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of the activity type.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The input properties of the activity type.
|
||||
/// </summary>
|
||||
public ICollection<InputDescriptor> Inputs { get; init; } = new List<InputDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// The output properties of the activity type.
|
||||
/// </summary>
|
||||
public ICollection<OutputDescriptor> Outputs { get; init; } = new List<OutputDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// The attributes of the activity type.
|
||||
/// </summary>
|
||||
[JsonIgnore] public ICollection<Attribute> Attributes { get; set; } = new List<Attribute>();
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -26,8 +67,19 @@ public class ActivityDescriptor
|
|||
[JsonIgnore]
|
||||
public Func<ActivityConstructorContext, IActivity> Constructor { get; init; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The kind of activity.
|
||||
/// </summary>
|
||||
public ActivityKind Kind { get; set; } = ActivityKind.Action;
|
||||
|
||||
/// <summary>
|
||||
/// The ports of the activity type.
|
||||
/// </summary>
|
||||
public ICollection<Port> Ports { get; init; } = new List<Port>();
|
||||
|
||||
/// <summary>
|
||||
/// The custom properties of the activity type.
|
||||
/// </summary>
|
||||
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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);
|
||||
|
|
@ -33,7 +33,7 @@ public class ActivityDescriber : IActivityDescriber
|
|||
public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Dsl\Elsa.Dsl.csproj" />
|
||||
<ProjectReference Include="..\Elsa.JavaScript\Elsa.JavaScript.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Mediator\Elsa.Mediator.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public static class ModuleExtensions
|
|||
});
|
||||
return module;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds the default workflow management feature to the specified module.
|
||||
/// </summary>
|
||||
|
|
@ -32,7 +32,7 @@ public static class ModuleExtensions
|
|||
feature.Module.Configure(configure);
|
||||
return feature;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds the workflow instance feature to workflow management module.
|
||||
/// </summary>
|
||||
|
|
@ -42,13 +42,31 @@ public static class ModuleExtensions
|
|||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the JavaScript integration feature.
|
||||
/// </summary>
|
||||
public static WorkflowManagementFeature UseJavaScriptIntegration(this WorkflowManagementFeature feature, Action<JavaScriptIntegrationFeature>? configure = default)
|
||||
{
|
||||
feature.Module.Configure(configure);
|
||||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the Elsa DSL integration feature.
|
||||
/// </summary>
|
||||
public static WorkflowManagementFeature UseDslIntegration(this WorkflowManagementFeature feature, Action<DslIntegrationFeature>? configure = default)
|
||||
{
|
||||
feature.Module.Configure(configure);
|
||||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all types implementing <see cref="IActivity"/> to the system.
|
||||
/// </summary>
|
||||
public static IModule AddActivitiesFrom<TMarkerType>(this IModule module) => module.UseWorkflowManagement(management => management.AddActivitiesFrom<TMarkerType>());
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified activity type to the system.
|
||||
/// </summary>
|
||||
public static IModule AddActivity<T>(this IModule module) where T:IActivity => module.UseWorkflowManagement(management => management.AddActivity<T>());
|
||||
public static IModule AddActivity<T>(this IModule module) where T : IActivity => module.UseWorkflowManagement(management => management.AddActivity<T>());
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Installs JavaScript activities.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(DslFeature))]
|
||||
[PublicAPI]
|
||||
public class DslIntegrationFeature : FeatureBase
|
||||
{
|
||||
private readonly IDictionary<string, FunctionActivityDescriptor> _dictionary = new Dictionary<string, FunctionActivityDescriptor>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public DslIntegrationFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a function to an activity.
|
||||
/// </summary>
|
||||
/// <param name="functionName">The name of the function.</param>
|
||||
/// <param name="activityTypeName">The name of the activity type.</param>
|
||||
/// <param name="propertyNames">The names of the properties that are mapped to the function arguments.</param>
|
||||
/// <param name="configure">An optional action that can be used to configure the activity.</param>
|
||||
public void MapActivityFunction(string functionName, string activityTypeName, IEnumerable<string>? propertyNames = default, Action<IActivity>? configure = default)
|
||||
{
|
||||
_dictionary.Add(functionName, new FunctionActivityDescriptor(functionName, activityTypeName, propertyNames, configure));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ConfigureHostedServices()
|
||||
{
|
||||
ConfigureHostedService<MapActivityDslFunctionsHostedService>(-1);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
Services.Configure<DslIntegrationOptions>(options =>
|
||||
{
|
||||
options.FunctionActivityDescriptors = _dictionary;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Installs JavaScript activities.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(JavaScriptFeature))]
|
||||
[PublicAPI]
|
||||
public class JavaScriptIntegrationFeature : FeatureBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public JavaScriptIntegrationFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Module.UseWorkflowManagement(management => management.AddActivity<RunJavaScript>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ConfigureHostedServices()
|
||||
{
|
||||
ConfigureHostedService<RegisterVariableTypesWithJavaScriptHostedService>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
Services
|
||||
.AddSingleton<IActivityPropertyOptionsProvider, RunJavaScriptOptionsProvider>()
|
||||
.AddFunctionDefinitionProvider<InputFunctionsDefinitionProvider>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<WorkflowDefinition, MemoryWorkflowDefinitionStore>()
|
||||
.AddMemoryStore<WorkflowInstance, MemoryWorkflowInstanceStore>()
|
||||
.AddActivityProvider<TypedActivityProvider>()
|
||||
.AddSingleton<IWorkflowDefinitionService, WorkflowDefinitionService>()
|
||||
.AddSingleton<IWorkflowDefinitionPublisher, WorkflowDefinitionPublisher>()
|
||||
.AddSingleton<IWorkflowDefinitionImporter, WorkflowDefinitionImporter>()
|
||||
.AddSingleton<IWorkflowDefinitionManager, WorkflowDefinitionManager>()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Registers function activity descriptors with the DSL.
|
||||
/// </summary>
|
||||
public class MapActivityDslFunctionsHostedService : IHostedService
|
||||
{
|
||||
private readonly IFunctionActivityRegistry _functionActivityRegistry;
|
||||
private readonly IDictionary<string,FunctionActivityDescriptor> _descriptors;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MapActivityDslFunctionsHostedService"/> class.
|
||||
/// </summary>
|
||||
public MapActivityDslFunctionsHostedService(IOptions<DslIntegrationOptions> options, IFunctionActivityRegistry functionActivityRegistry)
|
||||
{
|
||||
_functionActivityRegistry = functionActivityRegistry;
|
||||
_descriptors = options.Value.FunctionActivityDescriptors;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var descriptor in _descriptors.Values)
|
||||
_functionActivityRegistry.RegisterFunction(descriptor);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Registers variable types with the JavaScript type alias registry
|
||||
/// </summary>
|
||||
public class RegisterVariableTypesWithJavaScriptHostedService : IHostedService
|
||||
{
|
||||
private readonly ITypeAliasRegistry _typeAliasRegistry;
|
||||
private readonly IOptions<ManagementOptions> _managementOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegisterVariableTypesWithJavaScriptHostedService"/> class.
|
||||
/// </summary>
|
||||
public RegisterVariableTypesWithJavaScriptHostedService(ITypeAliasRegistry typeAliasRegistry, IOptions<ManagementOptions> managementOptions)
|
||||
{
|
||||
_typeAliasRegistry = typeAliasRegistry;
|
||||
_managementOptions = managementOptions;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var variableDescriptor in _managementOptions.Value.VariableDescriptors)
|
||||
_typeAliasRegistry.RegisterType(variableDescriptor.Type, variableDescriptor.Type.Name);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -44,6 +44,26 @@ public class WorkflowDefinitionMapper
|
|||
source.CustomProperties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a <see cref="WorkflowDefinitionModel"/> to a <see cref="Workflow"/>.
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="WorkflowDefinitionModel"/>.</param>
|
||||
/// <returns>The mapped <see cref="Workflow"/>.</returns>
|
||||
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<string, object>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a <see cref="WorkflowDefinition"/> to a <see cref="Workflow"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
using Elsa.Dsl.Models;
|
||||
|
||||
namespace Elsa.Workflows.Management.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Options for the DSL integration.
|
||||
/// </summary>
|
||||
public class DslIntegrationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of function activity descriptors that are available to the DSL.
|
||||
/// </summary>
|
||||
public IDictionary<string, FunctionActivityDescriptor> FunctionActivityDescriptors { get; set; } = new Dictionary<string, FunctionActivityDescriptor>();
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Produces <see cref="FunctionDefinition"/>s for common functions.
|
||||
/// </summary>
|
||||
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<IEnumerable<FunctionDefinition>> 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<FunctionDefinition>() : GetFunctionDefinitionsAsync(workflowDefinition);
|
||||
}
|
||||
|
||||
private IEnumerable<FunctionDefinition> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ namespace Elsa.Workflows.Runtime.Contracts;
|
|||
/// <summary>
|
||||
/// Represents a source of workflow definitions.
|
||||
/// </summary>
|
||||
public interface IWorkflowDefinitionProvider
|
||||
public interface IWorkflowProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the provider.
|
||||
|
|
@ -17,5 +17,5 @@ public interface IWorkflowDefinitionProvider
|
|||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The workflow definitions.</returns>
|
||||
ValueTask<IEnumerable<WorkflowDefinitionResult>> GetWorkflowDefinitionsAsync(CancellationToken cancellationToken = default);
|
||||
ValueTask<IEnumerable<MaterializedWorkflow>> GetWorkflowDefinitionsAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the <see cref="ClrWorkflowDefinitionProvider"/> to the service collection.
|
||||
/// Adds the <see cref="ClrWorkflowProvider"/> to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <typeparam name="T">The type of the workflow definition provider.</typeparam>
|
||||
/// <returns>The service collection.</returns>
|
||||
public static IServiceCollection AddWorkflowDefinitionProvider<T>(this IServiceCollection services) where T : class, IWorkflowDefinitionProvider => services.AddSingleton<IWorkflowDefinitionProvider, T>();
|
||||
public static IServiceCollection AddWorkflowDefinitionProvider<T>(this IServiceCollection services) where T : class, IWorkflowProvider => services.AddSingleton<IWorkflowProvider, T>();
|
||||
}
|
||||
|
|
@ -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<ITriggerIndexer, TriggerIndexer>()
|
||||
.AddSingleton<IWorkflowInstanceFactory, WorkflowInstanceFactory>()
|
||||
.AddSingleton<IWorkflowDefinitionService, WorkflowDefinitionService>()
|
||||
.AddSingleton<IWorkflowHostFactory, WorkflowHostFactory>()
|
||||
.AddSingleton<IBackgroundActivityInvoker, DefaultBackgroundActivityInvoker>()
|
||||
.AddSingleton(WorkflowRuntime)
|
||||
|
|
@ -140,7 +140,7 @@ public class WorkflowRuntimeFeature : FeatureBase
|
|||
.AddSingleton<IEventPublisher, EventPublisher>()
|
||||
|
||||
// Lazy services.
|
||||
.AddSingleton<Func<IEnumerable<IWorkflowDefinitionProvider>>>(sp => sp.GetServices<IWorkflowDefinitionProvider>)
|
||||
.AddSingleton<Func<IEnumerable<IWorkflowProvider>>>(sp => sp.GetServices<IWorkflowProvider>)
|
||||
.AddSingleton<Func<IEnumerable<IWorkflowMaterializer>>>(sp => sp.GetServices<IWorkflowMaterializer>)
|
||||
|
||||
// Memory stores.
|
||||
|
|
@ -153,7 +153,7 @@ public class WorkflowRuntimeFeature : FeatureBase
|
|||
.AddSingleton(DistributedLockProvider)
|
||||
|
||||
// Workflow definition providers.
|
||||
.AddWorkflowDefinitionProvider<ClrWorkflowDefinitionProvider>()
|
||||
.AddWorkflowDefinitionProvider<ClrWorkflowProvider>()
|
||||
|
||||
// Workflow state exporter.
|
||||
.AddSingleton(WorkflowStateExporter)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously updates the workflow definition store from <see cref="IWorkflowDefinitionProvider"/> implementations and creates triggers.
|
||||
/// Updates the workflow store from <see cref="IWorkflowProvider"/> implementations and creates triggers.
|
||||
/// </summary>
|
||||
public class PopulateWorkflowDefinitionStore : IHostedService
|
||||
{
|
||||
private readonly Func<IEnumerable<IWorkflowDefinitionProvider>> _workflowDefinitionProviders;
|
||||
private readonly Func<IEnumerable<IWorkflowProvider>> _workflowDefinitionProviders;
|
||||
private readonly ITriggerIndexer _triggerIndexer;
|
||||
private readonly IWorkflowDefinitionStore _workflowDefinitionStore;
|
||||
private readonly IActivitySerializer _activitySerializer;
|
||||
private readonly IPayloadSerializer _payloadSerializer;
|
||||
private readonly ISystemClock _systemClock;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public PopulateWorkflowDefinitionStore(
|
||||
Func<IEnumerable<IWorkflowDefinitionProvider>> workflowDefinitionProviders,
|
||||
Func<IEnumerable<IWorkflowProvider>> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -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);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Management.Entities;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a workflow definition and its workflow.
|
||||
/// </summary>
|
||||
/// <param name="Workflow">The workflow materialized from its workflow definition.</param>
|
||||
/// <param name="MaterializerName">The name of the materializer that materialized the workflow.</param>
|
||||
/// <param name="MaterializerContext">The context of the materializer that materialized the workflow.</param>
|
||||
public record MaterializedWorkflow(Workflow Workflow, string MaterializerName, object? MaterializerContext = default);
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Management.Entities;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a workflow definition and its workflow.
|
||||
/// </summary>
|
||||
/// <param name="Definition">The workflow definition.</param>
|
||||
/// <param name="Workflow">The workflow materialized from its workflow definition.</param>
|
||||
public record WorkflowDefinitionResult(WorkflowDefinition Definition, Workflow Workflow);
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Provides workflows to the system that are registered with <see cref="WorkflowRuntimeFeature"/>
|
||||
/// </summary>
|
||||
public class ClrWorkflowProvider : IWorkflowProvider
|
||||
{
|
||||
private readonly IWorkflowBuilderFactory _workflowBuilderFactory;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly RuntimeOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public ClrWorkflowProvider(
|
||||
IOptions<RuntimeOptions> options,
|
||||
IWorkflowBuilderFactory workflowBuilderFactory,
|
||||
IServiceProvider serviceProvider
|
||||
)
|
||||
{
|
||||
_workflowBuilderFactory = workflowBuilderFactory;
|
||||
_serviceProvider = serviceProvider;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "CLR";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<IEnumerable<MaterializedWorkflow>> 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<MaterializedWorkflow> BuildWorkflowDefinition(Func<IServiceProvider, ValueTask<IWorkflow>> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Provides workflows to the system that are registered with <see cref="WorkflowRuntimeFeature"/>
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public ClrWorkflowDefinitionProvider(
|
||||
IOptions<RuntimeOptions> options,
|
||||
IWorkflowBuilderFactory workflowBuilderFactory,
|
||||
IActivitySerializer activitySerializer,
|
||||
IPayloadSerializer payloadSerializer,
|
||||
ISystemClock systemClock,
|
||||
IServiceProvider serviceProvider
|
||||
)
|
||||
{
|
||||
_workflowBuilderFactory = workflowBuilderFactory;
|
||||
_activitySerializer = activitySerializer;
|
||||
_payloadSerializer = payloadSerializer;
|
||||
_systemClock = systemClock;
|
||||
_serviceProvider = serviceProvider;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "CLR";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<IEnumerable<WorkflowDefinitionResult>> 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<WorkflowDefinitionResult> BuildWorkflowDefinition(Func<IServiceProvider, ValueTask<IWorkflow>> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\bundles\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Identity\Elsa.Identity.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.WorkflowProviders.FluentStorage\Elsa.WorkflowProviders.FluentStorage.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Workflows\hello-world.elsa">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Workflows\hello-world-functional.elsa">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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();
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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!");
|
||||
|
|
@ -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!"
|
||||
};
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Elsa.Mediator": "Warning",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.EntityFrameworkCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Loading…
Reference in a new issue