Add ability to control when to evaluate input
This commit is contained in:
parent
b947177ece
commit
dbd682eacb
|
|
@ -26,7 +26,7 @@ public class Fork : Activity
|
|||
[Outbound]
|
||||
public ICollection<IActivity> Branches { get; set; } = new List<IActivity>();
|
||||
|
||||
protected override void Execute(ActivityExecutionContext context) => context.PostActivities(Branches.Reverse(), CompleteChildAsync);
|
||||
protected override void Execute(ActivityExecutionContext context) => context.ScheduleActivities(Branches.Reverse(), CompleteChildAsync);
|
||||
|
||||
private async ValueTask CompleteChildAsync(ActivityExecutionContext context, ActivityExecutionContext childContext)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public class While : Activity
|
|||
{
|
||||
}
|
||||
|
||||
[Input] public Input<bool> Condition { get; set; } = new(false);
|
||||
[Input(AutoEvaluate = false)] public Input<bool> Condition { get; set; } = new(false);
|
||||
[Outbound] public IActivity Body { get; set; }
|
||||
|
||||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
|
|
@ -58,7 +58,8 @@ public class While : Activity
|
|||
|
||||
private async ValueTask HandleIterationAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var loop = context.Get(Condition);
|
||||
var loop = await context.EvaluateInputPropertyAsync<While, bool>(x => x.Condition);
|
||||
//var loop = context.Get(Condition);
|
||||
|
||||
if (loop)
|
||||
context.ScheduleActivity(Body, OnBodyCompleted);
|
||||
|
|
|
|||
|
|
@ -76,4 +76,10 @@ public class InputAttribute : Attribute
|
|||
/// A value indicating whether this property should be visible.
|
||||
/// </summary>
|
||||
public bool IsBrowsable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// True if the activity invoker should evaluate the expression, false otherwise.
|
||||
/// When set to false, it is up to the activity itself to evaluate its input before using it.
|
||||
/// </summary>
|
||||
public bool AutoEvaluate { get; set; } = true;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Linq.Expressions;
|
||||
using Elsa.Contracts;
|
||||
using Elsa.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -26,5 +27,55 @@ public static class ActivityExecutionContextExtensions
|
|||
public static Variable SetVariable(this ActivityExecutionContext context, string name, object? value) => context.ExpressionExecutionContext.SetVariable(name, value);
|
||||
public static T? GetVariable<T>(this ActivityExecutionContext context, string name) => context.ExpressionExecutionContext.GetVariable<T?>(name);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates each input property of the activity.
|
||||
/// </summary>
|
||||
public static async Task EvaluateInputPropertiesAsync(this ActivityExecutionContext context)
|
||||
{
|
||||
var activity = context.Activity;
|
||||
var inputs = activity.GetInputs();
|
||||
var assignedInputs = inputs.Where(x => x.LocationReference != null!).ToList();
|
||||
var evaluator = context.GetRequiredService<IExpressionEvaluator>();
|
||||
var expressionExecutionContext = context.ExpressionExecutionContext;
|
||||
|
||||
foreach (var input in assignedInputs)
|
||||
{
|
||||
var locationReference = input.LocationReference;
|
||||
var value = await evaluator.EvaluateAsync(input, expressionExecutionContext);
|
||||
locationReference.Set(context, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<T?> EvaluateInputPropertyAsync<TActivity, T>(this ActivityExecutionContext context, Expression<Func<TActivity, Input<T>>> propertyExpression)
|
||||
{
|
||||
var inputName = propertyExpression.GetProperty()!.Name;
|
||||
var input = await EvaluateInputPropertyAsync(context, inputName);
|
||||
return context.Get((Input<T>)input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates a specific input property of the activity.
|
||||
/// </summary>
|
||||
public static async Task<Input> EvaluateInputPropertyAsync(this ActivityExecutionContext context, string inputName)
|
||||
{
|
||||
var activity = context.Activity;
|
||||
var input = activity.GetInput(inputName);
|
||||
|
||||
if (input == null)
|
||||
throw new Exception($"No input with name {inputName} could be found");
|
||||
|
||||
if (input.LocationReference == null!)
|
||||
throw new Exception("Input not initialized");
|
||||
|
||||
var evaluator = context.GetRequiredService<IExpressionEvaluator>();
|
||||
var expressionExecutionContext = context.ExpressionExecutionContext;
|
||||
|
||||
var locationReference = input.LocationReference;
|
||||
var value = await evaluator.EvaluateAsync(input, expressionExecutionContext);
|
||||
locationReference.Set(context, value);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
public static ILogger GetLogger(this ActivityExecutionContext context) => (ILogger)context.GetRequiredService(typeof(ILogger<>).MakeGenericType(context.Activity.GetType()));
|
||||
}
|
||||
|
|
@ -19,6 +19,16 @@ public static class ActivityExtensions
|
|||
|
||||
return query.Select(x => x!).ToList();
|
||||
}
|
||||
|
||||
public static Input? GetInput(this IActivity activity, string inputName)
|
||||
{
|
||||
var inputProp = activity.GetType().GetProperties().FirstOrDefault(x => typeof(Input).IsAssignableFrom(x.PropertyType) && x.Name == inputName);
|
||||
|
||||
if (inputProp == null)
|
||||
return null;
|
||||
|
||||
return (Input?)inputProp.GetValue(activity);
|
||||
}
|
||||
|
||||
public static IEnumerable<Output> GetOutputs(this IActivity activity)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -84,13 +84,47 @@ public class ActivityExecutionContext
|
|||
WorkflowExecutionContext.Schedule(activity, owner, completionCallback, locationReferences, tag);
|
||||
}
|
||||
|
||||
public void PostActivities(params IActivity?[] activities) => PostActivities((IEnumerable<IActivity?>)activities);
|
||||
public void ScheduleActivities(params IActivity?[] activities) => ScheduleActivities((IEnumerable<IActivity?>)activities);
|
||||
|
||||
public void PostActivities(IEnumerable<IActivity?> activities, ActivityCompletionCallback? completionCallback = default)
|
||||
public void ScheduleActivities(IEnumerable<IActivity?> activities, ActivityCompletionCallback? completionCallback = default)
|
||||
{
|
||||
foreach (var activity in activities)
|
||||
ScheduleActivity(activity, completionCallback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a signal up the current branch.
|
||||
/// </summary>
|
||||
public async ValueTask SignalAsync(object signal)
|
||||
{
|
||||
var ancestorContexts = GetAncestors();
|
||||
|
||||
foreach (var ancestorContext in ancestorContexts)
|
||||
{
|
||||
var signalContext = new SignalContext(ancestorContext, this, CancellationToken);
|
||||
|
||||
if (ancestorContext.Activity is not ISignalHandler handler)
|
||||
continue;
|
||||
|
||||
|
||||
await handler.HandleSignalAsync(signal, signalContext);
|
||||
|
||||
if (signalContext.StopPropagationRequested)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Complete the current activity. This should only be called by activities that explicitly suppress automatic-completion.
|
||||
/// </summary>
|
||||
public async ValueTask CompleteActivityAsync()
|
||||
{
|
||||
// Send a signal.
|
||||
await SignalAsync(new ActivityCompleted());
|
||||
|
||||
// Remove the context.
|
||||
WorkflowExecutionContext.ActivityExecutionContexts.Remove(this);
|
||||
}
|
||||
|
||||
public void CreateBookmarks(IEnumerable<object> bookmarkData, ExecuteActivityDelegate? callback = default)
|
||||
{
|
||||
|
|
@ -169,39 +203,6 @@ public class ActivityExecutionContext
|
|||
/// </summary>
|
||||
public void PreventContinuation() => Continue = false;
|
||||
|
||||
/// <summary>
|
||||
/// Send a signal up the current branch.
|
||||
/// </summary>
|
||||
public async ValueTask SignalAsync(object signal)
|
||||
{
|
||||
var ancestorContexts = GetAncestors();
|
||||
|
||||
foreach (var ancestorContext in ancestorContexts)
|
||||
{
|
||||
var signalContext = new SignalContext(ancestorContext, this, CancellationToken);
|
||||
|
||||
if (ancestorContext.Activity is not ISignalHandler handler)
|
||||
continue;
|
||||
|
||||
await handler.HandleSignalAsync(signal, signalContext);
|
||||
|
||||
if (signalContext.StopPropagationRequested)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Complete the current activity. This should only be called by activities that explicitly suppress automatic-completion.
|
||||
/// </summary>
|
||||
public async ValueTask CompleteActivityAsync()
|
||||
{
|
||||
// Send a signal.
|
||||
await SignalAsync(new ActivityCompleted());
|
||||
|
||||
// Remove the context.
|
||||
WorkflowExecutionContext.ActivityExecutionContexts.Remove(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a flattened list of the current context's ancestors.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class ActivityInvokerMiddleware : IActivityExecutionMiddleware
|
|||
var workflowExecution = context.WorkflowExecutionContext;
|
||||
|
||||
// Evaluate input properties.
|
||||
await EvaluateInputPropertiesAsync(context);
|
||||
await context.EvaluateInputPropertiesAsync();
|
||||
var activity = context.Activity;
|
||||
|
||||
// Execute activity.
|
||||
|
|
@ -62,20 +62,4 @@ public class ActivityInvokerMiddleware : IActivityExecutionMiddleware
|
|||
}
|
||||
|
||||
private void LogExecutionRecord(ActivityExecutionContext context, string eventName, string? message = default, string? source = default, object? payload = default) => context.AddExecutionLogEntry(eventName, message, source, payload);
|
||||
|
||||
private async Task EvaluateInputPropertiesAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var activity = context.Activity;
|
||||
var inputs = activity.GetInputs();
|
||||
var assignedInputs = inputs.Where(x => x.LocationReference != null!).ToList();
|
||||
var evaluator = context.GetRequiredService<IExpressionEvaluator>();
|
||||
var expressionExecutionContext = context.ExpressionExecutionContext;
|
||||
|
||||
foreach (var input in assignedInputs)
|
||||
{
|
||||
var locationReference = input.LocationReference;
|
||||
var value = await evaluator.EvaluateAsync(input, expressionExecutionContext);
|
||||
locationReference.Set(context, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,6 @@ public class Flowchart : Container
|
|||
var outboundConnections = Connections.Where(x => x.Source == parent).ToList();
|
||||
var children = outboundConnections.Select(x => x.Target).ToList();
|
||||
|
||||
context.PostActivities(children, OnChildCompleted);
|
||||
context.ScheduleActivities(children, OnChildCompleted);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,24 +21,14 @@ public class ComplexIfWorkflow : IWorkflow
|
|||
Activities =
|
||||
{
|
||||
new WriteLine("Start"),
|
||||
new If(_condition)
|
||||
new If
|
||||
{
|
||||
Then = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new WriteLine("Executing"),
|
||||
new WriteLine("True!"),
|
||||
}
|
||||
},
|
||||
Else = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new WriteLine("Executing"),
|
||||
new WriteLine("False!"),
|
||||
}
|
||||
}
|
||||
Then = new WriteLine(""),
|
||||
Else = new Sequence(
|
||||
|
||||
new WriteLine("Executing"),
|
||||
new WriteLine("False!")
|
||||
)
|
||||
},
|
||||
new WriteLine("End")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,36 +12,21 @@ public class BreakWhileBlockForkWorkflow : IWorkflow
|
|||
{
|
||||
var currentValue = new Variable<int?>(0);
|
||||
|
||||
workflow.WithRoot(new Sequence
|
||||
workflow.WithVariable(currentValue);
|
||||
workflow.WithRoot(new While(context => currentValue.Get(context) < 3)
|
||||
{
|
||||
Variables = { currentValue },
|
||||
Activities =
|
||||
{
|
||||
While.True(new Sequence
|
||||
Body =
|
||||
new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new WriteLine(context => $"Current value: {currentValue.Get<int>(context)}"),
|
||||
new SetVariable<int?>(currentValue, context => currentValue.Get(context) + 1),
|
||||
new Fork
|
||||
{
|
||||
Branches =
|
||||
{
|
||||
new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new SetVariable
|
||||
{
|
||||
Variable = currentValue,
|
||||
Value = new Input<object?>(context => currentValue.Get(context) + 1)
|
||||
},
|
||||
new If(context => currentValue.Get(context) == 3)
|
||||
{
|
||||
Then = new Break()
|
||||
},
|
||||
new WriteLine("Branch 1"),
|
||||
}
|
||||
},
|
||||
new WriteLine("Branch 1"),
|
||||
new Sequence
|
||||
{
|
||||
Activities =
|
||||
|
|
@ -54,8 +39,7 @@ public class BreakWhileBlockForkWorkflow : IWorkflow
|
|||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue