Introduce Activity Behaviors

Activity Behaviors allow for code reuse between activities. Useful for certain signal handling like Break for example.
This commit is contained in:
Sipke Schoorstra 2022-04-20 14:31:09 +02:00
parent a8cf10c7e5
commit 604b5c3e82
8 changed files with 69 additions and 45 deletions

View file

@ -1,8 +1,8 @@
using System.Text.Json.Serialization;
using Elsa.Attributes;
using Elsa.Behaviors;
using Elsa.Contracts;
using Elsa.Models;
using Elsa.Signals;
namespace Elsa.Activities;
@ -20,7 +20,7 @@ public class For : Activity
[JsonConstructor]
public For()
{
OnSignalReceived<BreakSignal>(OnBreakAsync);
Behaviors.Add<BreakBehavior>();
}
public For(int start, int end, ForOperator forOperator = ForOperator.LessThanOrEqual) : this()
@ -85,16 +85,4 @@ public class For : Activity
HandleIteration(ownerActivityExecutionContext);
return ValueTask.CompletedTask;
}
private async ValueTask OnBreakAsync(BreakSignal signal, SignalContext context)
{
// Prevent bubbling.
context.StopPropagation();
// Remove child activity execution contexts.
context.ActivityExecutionContext.RemoveChildren();
// Mark this activity as completed.
await context.ActivityExecutionContext.CompleteActivityAsync();
}
}

View file

@ -1,8 +1,8 @@
using System.Text.Json.Serialization;
using Elsa.Attributes;
using Elsa.Behaviors;
using Elsa.Contracts;
using Elsa.Models;
using Elsa.Signals;
namespace Elsa.Activities;
@ -13,7 +13,7 @@ public class ForEach : Activity
public ForEach()
{
OnSignalReceived<BreakSignal>(OnBreakAsync);
Behaviors.Add<BreakBehavior>();
}
/// <summary>
@ -67,18 +67,6 @@ public class ForEach : Activity
{
await HandleIteration(context);
}
private async ValueTask OnBreakAsync(BreakSignal signal, SignalContext context)
{
// Prevent bubbling.
context.StopPropagation();
// Remove child activity execution contexts.
context.ActivityExecutionContext.RemoveChildren();
// Mark this activity as completed.
await context.ActivityExecutionContext.CompleteActivityAsync();
}
}
public class ForEach<T> : ForEach

View file

@ -1,8 +1,8 @@
using System.Text.Json.Serialization;
using Elsa.Attributes;
using Elsa.Behaviors;
using Elsa.Contracts;
using Elsa.Models;
using Elsa.Signals;
namespace Elsa.Activities;
@ -18,8 +18,7 @@ public class While : Activity
public While(IActivity? body = default)
{
Body = body!;
OnSignalReceived<BreakSignal>(OnBreakAsync);
Behaviors.Add<BreakBehavior>();
}
public While(Input<bool> condition, IActivity? body = default) : this(body)
@ -67,16 +66,4 @@ public class While : Activity
else
await context.CompleteActivityAsync();
}
private async ValueTask OnBreakAsync(BreakSignal signal, SignalContext context)
{
// Prevent bubbling.
context.StopPropagation();
// Remove child activity execution contexts.
context.ActivityExecutionContext.RemoveChildren();
// Mark this activity as completed.
await context.ActivityExecutionContext.CompleteActivityAsync();
}
}

View file

@ -0,0 +1,33 @@
using Elsa.Contracts;
using Elsa.Models;
using Elsa.Signals;
namespace Elsa.Behaviors;
/// <summary>
/// Implements a "break" behavior that handles the <see cref="BreakSignal"/> signal.
/// </summary>
public class BreakBehavior : IBehavior
{
public async ValueTask HandleSignalAsync(object signal, SignalContext context)
{
if (signal is not BreakSignal breakSignal)
return;
await OnBreakAsync(breakSignal, context);
}
public ValueTask ExecuteAsync(ActivityExecutionContext context) => ValueTask.CompletedTask;
private async ValueTask OnBreakAsync(BreakSignal signal, SignalContext context)
{
// Prevent bubbling.
context.StopPropagation();
// Remove child activity execution contexts.
context.ActivityExecutionContext.RemoveChildren();
// Mark this activity as completed.
await context.ActivityExecutionContext.CompleteActivityAsync();
}
}

View file

@ -0,0 +1,11 @@
using Elsa.Models;
namespace Elsa.Contracts;
public interface IBehavior : ISignalHandler
{
/// <summary>
/// Invoked when the activity executes.
/// </summary>
ValueTask ExecuteAsync(ActivityExecutionContext context);
}

View file

@ -2,7 +2,7 @@ using Elsa.Models;
namespace Elsa.Contracts;
public interface ISignalHandler : IActivity
public interface ISignalHandler
{
ValueTask HandleSignalAsync(object signal, SignalContext context);
}

View file

@ -0,0 +1,8 @@
using Elsa.Contracts;
namespace Elsa;
public static class BehaviorCollectionExtensions
{
public static void Add<T>(this ICollection<IBehavior> behaviors) where T : IBehavior => behaviors.Add((T)Activator.CreateInstance(typeof(T))!);
}

View file

@ -5,9 +5,10 @@ using Elsa.Signals;
namespace Elsa.Models;
public abstract class Activity : ISignalHandler
public abstract class Activity : IActivity, ISignalHandler
{
private readonly ICollection<SignalHandlerRegistration> _signalHandlers = new List<SignalHandlerRegistration>();
protected Activity()
{
TypeName = TypeNameHelper.GenerateTypeName(GetType());
@ -25,6 +26,8 @@ public abstract class Activity : ISignalHandler
public IDictionary<string, object> ApplicationProperties { get; set; } = new Dictionary<string, object>();
public IDictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
public ICollection<IBehavior> Behaviors { get; } = new List<IBehavior>();
/// <summary>
/// A value indicating whether this activity should complete automatically.
/// Default is true.
@ -89,6 +92,9 @@ public abstract class Activity : ISignalHandler
async ValueTask IActivity.ExecuteAsync(ActivityExecutionContext context)
{
await ExecuteAsync(context);
// Invoke behaviors.
foreach (var behavior in Behaviors) await behavior.ExecuteAsync(context);
}
async ValueTask ISignalHandler.HandleSignalAsync(object signal, SignalContext context)
@ -102,6 +108,9 @@ public abstract class Activity : ISignalHandler
foreach (var registration in handlers)
await registration.Handler(signal, context);
// Invoke behaviors.
foreach (var behavior in Behaviors) await behavior.HandleSignalAsync(signal, context);
}
}