Refactor output capture API

This commit is contained in:
Sipke Schoorstra 2022-04-07 12:44:12 +02:00
parent e7c36ed7ff
commit 5d8d77deea
23 changed files with 106 additions and 65 deletions

View file

@ -53,29 +53,31 @@ public class Inline<T> : Activity<T>
{
private readonly Func<ActivityExecutionContext, ValueTask<T>> _activity;
public Inline(Func<ActivityExecutionContext, ValueTask<T>> activity, RegisterLocationReference? output = default)
public Inline(Func<ActivityExecutionContext, ValueTask<T>> activity, RegisterLocationReference? captureTarget = default)
{
_activity = activity;
if (output != null) Result = new Output<T?>(output);
if (captureTarget != null)
this.CaptureOutput(captureTarget);
}
public Inline(Func<ValueTask<T>> activity, RegisterLocationReference? output = default) : this(_ => activity(), output)
public Inline(Func<ValueTask<T>> activity, RegisterLocationReference? captureTarget = default) : this(_ => activity(), captureTarget)
{
}
public Inline(Func<ActivityExecutionContext, T> activity, RegisterLocationReference? output = default) : this(c =>
public Inline(Func<ActivityExecutionContext, T> activity, RegisterLocationReference? captureTarget = default) : this(c =>
{
var result = activity(c);
return new ValueTask<T>(result);
}, output)
}, captureTarget)
{
}
public Inline(Func<T> activity, RegisterLocationReference? output = default) : this(c =>
public Inline(Func<T> activity, RegisterLocationReference? captureTarget = default) : this(c =>
{
var result = activity();
return new ValueTask<T>(result);
}, output)
}, captureTarget)
{
}

View file

@ -17,13 +17,6 @@ public class OutputExpression : IExpression
public Output? Output { get; set; }
}
public class OutputExpression<T> : OutputExpression
{
public OutputExpression(Output<T>? output) : base(output)
{
}
}
public class OutputExpressionHandler : IExpressionHandler
{
public ValueTask<object?> EvaluateAsync(IExpression expression, Type returnType, ExpressionExecutionContext context)

View file

@ -37,7 +37,7 @@ public static class ActivityExtensions
/// <summary>
/// Creates an input from the activity's result.
/// </summary>
public static Input<T?> CreateInput<T>(this Activity<T> activity) => activity.Result.CreateInput();
public static Input<T?> CreateInput<T>(this Activity<T> activity) => activity.Result.CreateInput<T?>();
public static IEnumerable<Variable> GetVariables(this IActivity activity)
{

View file

@ -7,5 +7,5 @@ public static class OutputExtensions
/// <summary>
/// Creates an input that references the specified output's value.
/// </summary>
public static Input<T> CreateInput<T>(this Output<T>? output) => new(output);
public static Input<T> CreateInput<T>(this Output output) => new(output);
}

View file

@ -0,0 +1,32 @@
using System;
using System.Linq.Expressions;
using System.Reflection;
namespace Elsa
{
public static class PropertyAccessorExtensions
{
public static void SetPropertyValue<T, TProperty>(this T target, Expression<Func<T, TProperty>> expression, TProperty value)
{
var property = expression.GetProperty();
if (property != null)
property.SetValue(target, value, null);
}
public static TProperty? GetPropertyValue<T, TProperty>(this T target, Expression<Func<T, TProperty>> expression)
{
var property = expression.GetProperty();
return (TProperty?)property?.GetValue(target);
}
public static PropertyInfo? GetProperty<T, TProperty>(this Expression<Func<T, TProperty>> expression) =>
expression.Body is MemberExpression memberExpression
? memberExpression.Member as PropertyInfo
: expression.Body is UnaryExpression unaryExpression
? unaryExpression.Operand is MemberExpression unaryMemberExpression
? unaryMemberExpression.Member as PropertyInfo
: default
: default;
}
}

View file

@ -1,3 +1,4 @@
using System.Linq.Expressions;
using Elsa.Contracts;
using Elsa.Helpers;
@ -42,18 +43,28 @@ public abstract class ActivityWithResult : Activity
{
}
public Output? Result { get; set; }
public Output Result { get; } = new();
}
public abstract class Activity<T> : ActivityWithResult
{
protected Activity() : base()
protected Activity()
{
}
protected Activity(string activityType) : base(activityType)
{
}
}
public new Output<T?>? Result { get; set; }
public static class ActivityWithResultExtensions
{
public static T CaptureOutput<T>(this T activity, Expression<Func<T, Output>> propertyExpression, RegisterLocationReference locationReference) where T:IActivity
{
var output = activity.GetPropertyValue(propertyExpression)!;
output.Targets.Add(locationReference);
return activity;
}
public static T CaptureOutput<T>(this T activity, RegisterLocationReference locationReference) where T : ActivityWithResult => activity.CaptureOutput(x => x.Result, locationReference);
}

View file

@ -150,8 +150,8 @@ public class ActivityExecutionContext
}
public void Set(RegisterLocationReference locationReference, object? value) => ExpressionExecutionContext.Set(locationReference, value);
public void Set(Output? output, object? value) => ExpressionExecutionContext.Set(output, value);
public void Set<T>(Output<T>? output, T value) => ExpressionExecutionContext.Set(output, value);
public void Set(Output output, object? value) => ExpressionExecutionContext.Set(output, value);
public void Set<T>(Output output, T value) => ExpressionExecutionContext.Set(output, value);
public async Task<T?> EvaluateAsync<T>(Input<T> input)
{

View file

@ -4,12 +4,12 @@ public abstract class Argument
{
protected Argument(){}
protected Argument(RegisterLocationReference locationReference, Func<object?, object?>? valueConverter = default)
protected Argument(RegisterLocationReference locationReference /*, Func<object?, object?>? valueConverter = default*/)
{
LocationReference = locationReference;
ValueConverter = valueConverter;
//ValueConverter = valueConverter;
}
public RegisterLocationReference LocationReference { get; set; } = default!;
public Func<object?, object?>? ValueConverter { get; set; }
//public Func<object?, object?>? ValueConverter { get; set; }
}

View file

@ -34,7 +34,7 @@ public class ExpressionExecutionContext
public object Get(RegisterLocationReference locationReference) => GetLocation(locationReference).Value!;
public T Get<T>(RegisterLocationReference locationReference) => (T)Get(locationReference);
public T? Get<T>(Input<T>? input) => input != null ? (T?)GetLocation(input.LocationReference).Value : default;
public T? Get<T>(Output<T>? output) => output != null ? (T?)GetLocation(output.LocationReference).Value : default;
public T? Get<T>(Output output) => (T?)GetLocation(output.LocationReference).Value;
public object? Get(Output? output) => output != null ? GetLocation(output.LocationReference).Value : default;
public T? GetVariable<T>(string name) => (T?)GetVariable(name);
public T? GetVariable<T>() => (T?)GetVariable(typeof(T).Name);
@ -56,13 +56,12 @@ public class ExpressionExecutionContext
location.Value = value;
}
public void Set(Output? output, object? value)
public void Set(Output output, object? value)
{
if (output?.LocationReference == null)
return;
var convertedValue = output.ValueConverter?.Invoke(value) ?? value;
Set(output.LocationReference, convertedValue);
//var convertedValue = output.ValueConverter?.Invoke(value) ?? value;
var convertedValue = value;
var targets = new[] { output.LocationReference }.Concat(output.Targets);
foreach (var target in targets) Set(target, convertedValue);
}
public T GetRequiredService<T>() where T : notnull => _serviceProvider.GetRequiredService<T>();

View file

@ -37,15 +37,11 @@ public class Input<T> : Input
{
}
public Input(Variable<T> variable) : base(new VariableExpression(variable), variable, typeof(T))
public Input(Variable variable) : base(new VariableExpression(variable), variable, typeof(T))
{
}
public Input(Output<T>? output) : base(new OutputExpression<T>(output), output?.LocationReference ?? new Literal(), typeof(T))
{
}
public Input(Output? output) : base(new OutputExpression(output), output?.LocationReference ?? new Literal(), typeof(T))
public Input(Output output) : base(new OutputExpression(output), output.LocationReference, typeof(T))
{
}

View file

@ -2,14 +2,25 @@ namespace Elsa.Models;
public class Output : Argument
{
public Output(RegisterLocationReference locationReference, Func<object?, object?>? valueConverter = default) : base(locationReference, valueConverter)
public Output() : base(new Literal())
{
}
public Output(RegisterLocationReference locationReference) : this()
{
Targets.Add(locationReference);
}
public ICollection<RegisterLocationReference> Targets { get; } = new List<RegisterLocationReference>();
}
public class Output<T> : Output
{
public Output(RegisterLocationReference locationReference, Func<object?, object?>? valueConverter = default) : base(locationReference, valueConverter)
public Output()
{
}
public Output(Variable<T> locationReference) : base(locationReference)
{
}
}

View file

@ -1,3 +1,5 @@
using Elsa.Helpers;
namespace Elsa.Models;
public class Variable : RegisterLocationReference
@ -36,6 +38,6 @@ public class Variable<T> : Variable
{
}
public new T? Get(ActivityExecutionContext context) => (T?)base.Get(context);
public new T? Get(ExpressionExecutionContext context) => (T?)base.Get(context);
public new T? Get(ActivityExecutionContext context) => base.Get(context).ConvertTo<T?>();
public new T? Get(ExpressionExecutionContext context) => base.Get(context).ConvertTo<T?>();
}

View file

@ -10,7 +10,7 @@ public class ReadLine : Activity<string>
{
}
public ReadLine(Variable variable, Func<object?, object?>? valueConverter = default) => Result = new Output<string?>(variable, valueConverter);
public ReadLine(RegisterLocationReference captureTarget) => this.CaptureOutput(captureTarget);
protected override void Execute(ActivityExecutionContext context)
{

View file

@ -1,3 +1,4 @@
using System.Text.Json.Serialization;
using Elsa.Attributes;
using Elsa.Contracts;
using Elsa.Models;
@ -5,8 +6,9 @@ using Elsa.Models;
namespace Elsa.Modules.Activities.Activities.Primitives;
[Activity("Elsa", "Primitives", "Wait for an event to be triggered.")]
public class Event : Activity
public class Event : Activity<object?>
{
[JsonConstructor]
public Event()
{
}
@ -29,7 +31,6 @@ public class Event : Activity
public Event(Input<string> eventName) => EventName = eventName;
[Input] public Input<string> EventName { get; set; } = default!;
[Output] public Output<object?>? Payload { get; set; }
protected override void Execute(ActivityExecutionContext context)
{

View file

@ -46,7 +46,7 @@ public class MessageReceived : Trigger<object>
/// <summary>
/// The received transport message.
/// </summary>
public Output<ReceivedServiceBusMessageModel>? ReceivedMessage { get; set; }
public Output<ReceivedServiceBusMessageModel> ReceivedMessage { get; } = new();
/// <summary>
/// The formatter to use to parse the message.

View file

@ -1,12 +1,14 @@
using Elsa.Attributes;
using System.Text.Json.Serialization;
using Elsa.Attributes;
using Elsa.Models;
using Elsa.Scripting.JavaScript.Contracts;
namespace Elsa.Modules.JavaScript.Activities;
[Activity("Elsa.Scripting.RunJavaScript", "Executes JavaScript code", "Scripting")]
public class RunJavaScript : Activity
public class RunJavaScript : Activity<object?>
{
[JsonConstructor]
public RunJavaScript()
{
}
@ -17,7 +19,6 @@ public class RunJavaScript : Activity
}
public Input<string> Script { get; set; } = new("");
public Output<object?>? Result { get; set; }
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{

View file

@ -11,7 +11,7 @@ public class CompositeActivitiesWorkflow : IWorkflow
{
public void Build(IWorkflowDefinitionBuilder workflow)
{
var name = new Variable<string>();
var name = new Variable<string?>();
workflow.WithRoot(new Sequence
{

View file

@ -11,20 +11,19 @@ public class ReceiveMessageWorkflow : IWorkflow
{
public void Build(IWorkflowDefinitionBuilder workflow)
{
var receivedMessage = new Variable<string>();
var receivedMessageVariable = new Variable<string>();
workflow.WithRoot(new Sequence
{
Variables = { receivedMessage },
Variables = { receivedMessageVariable },
Activities =
{
new MessageReceived
{
CanStartWorkflow = true,
QueueOrTopic = new Input<string>("inbox"),
Result = new Output<object?>(receivedMessage)
},
new WriteLine(context => $"Message received: {receivedMessage.Get(context)}")
}.CaptureOutput(receivedMessageVariable),
new WriteLine(context => $"Message received: {receivedMessageVariable.Get(context)}")
}
});
}

View file

@ -18,10 +18,7 @@ public class RunJavaScriptWorkflow : IWorkflow
Variables = { scriptResult },
Activities =
{
new RunJavaScript("1 + 1")
{
Result = new Output<object?>(scriptResult)
},
new RunJavaScript("1 + 1").CaptureOutput(scriptResult),
new WriteLine(context => $"Result: {scriptResult.Get(context)}")
}
});

View file

@ -50,7 +50,7 @@ class Program
var workflow13 = new Func<IActivity>(BlockingParallelForEachWorkflow.Create);
var workflow14 = new Func<IActivity>(FlowchartWorkflow.Create);
var workflowFactory = workflow10;
var workflowFactory = workflow3;
var workflowGraph = workflowFactory();
var workflow = Workflow.FromActivity(workflowGraph);

View file

@ -17,7 +17,7 @@ public static class ConditionalWorkflow
Activities =
{
new WriteLine("What's your age?"),
new ReadLine(age, s => int.Parse((string)s!)),
new ReadLine(age),
new If
{
Condition = new Input<bool>(context => age.Get(context) >= 16),

View file

@ -13,7 +13,7 @@ public static class FlowchartWorkflow
var age = new Variable<int>();
var flowchart = new Flowchart();
var step1 = new WriteLine("Please enter your age.");
var step2 = new ReadLine(age, x => int.Parse((string)x!));
var step2 = new ReadLine(age);
var step3 = new If
{
Condition = new Input<bool>(context => age.Get(context) > 16),

View file

@ -18,10 +18,7 @@ public static class VariablesWorkflow
Activities =
{
new WriteLine(context => greeting.Get(context)),
new ReadLine
{
Result = new Output<string?>(name)
},
new ReadLine(name),
new WriteLine(new DelegateReference(context => $"Nice to meet you, {name.Get(context)}!")),
}
};