Incremental work on correlation

This commit is contained in:
Sipke Schoorstra 2022-04-11 14:19:55 +02:00
parent 78a799edb9
commit 3120429f89
52 changed files with 301 additions and 128 deletions

View file

@ -11,15 +11,21 @@ namespace Elsa.Api.ApiResults;
public class DispatchWorkflowResult : IResult
{
public DispatchWorkflowResult(Workflow workflow) => Workflow = workflow;
public DispatchWorkflowResult(Workflow workflow, string? correlationId = default)
{
Workflow = workflow;
CorrelationId = correlationId;
}
public Workflow Workflow { get; }
public string? CorrelationId { get; set; }
public async Task ExecuteAsync(HttpContext httpContext)
{
var response = httpContext.Response;
var workflowDispatcher = httpContext.RequestServices.GetRequiredService<IWorkflowDispatcher>();
var definitionId = Workflow.Identity.DefinitionId;
var result = await workflowDispatcher.DispatchAsync(new DispatchWorkflowDefinitionRequest(definitionId, VersionOptions.Published));
var result = await workflowDispatcher.DispatchAsync(new DispatchWorkflowDefinitionRequest(definitionId, VersionOptions.Published, CorrelationId: CorrelationId));
response.StatusCode = (int)HttpStatusCode.OK;
await response.WriteAsJsonAsync(result, httpContext.RequestAborted);

View file

@ -9,9 +9,9 @@ namespace Elsa.Api.Endpoints.Workflows;
public static partial class Workflows
{
public static async Task<IResult> DispatchAsync(string definitionId, IWorkflowRegistry workflowRegistry, HttpResponse response, CancellationToken cancellationToken)
public static async Task<IResult> DispatchAsync(string definitionId, IWorkflowRegistry workflowRegistry, HttpResponse response, string? correlationId = default, CancellationToken cancellationToken = default)
{
var workflow = await workflowRegistry.FindByIdAsync(definitionId, VersionOptions.Published, cancellationToken);
return workflow == null ? Results.NotFound() : new DispatchWorkflowResult(workflow);
return workflow == null ? Results.NotFound() : new DispatchWorkflowResult(workflow, correlationId);
}
}

View file

@ -44,6 +44,12 @@ public class WorkflowDefinitionBuilder : IWorkflowDefinitionBuilder
return this;
}
public IWorkflowDefinitionBuilder WithVariables(params Variable[] variables)
{
foreach (var variable in variables) Variables.Add(variable);
return this;
}
public IWorkflowDefinitionBuilder WithMetadata(string name, object value)
{
Metadata[name] = value;

View file

@ -25,7 +25,7 @@ public interface IActivity
/// <summary>
/// Can contain application-specific information about this activity.
/// </summary>
IDictionary<string, object> ApplicationProperties { get; set; }
IDictionary<string, object?> ApplicationProperties { get; set; }
/// <summary>
/// Invoked when the activity executes.

View file

@ -13,6 +13,7 @@ public interface IWorkflowDefinitionBuilder
IWorkflowDefinitionBuilder WithVersion(int version);
IWorkflowDefinitionBuilder WithRoot(IActivity root);
IWorkflowDefinitionBuilder WithVariable(Variable variable);
IWorkflowDefinitionBuilder WithVariables(params Variable[] variables);
IWorkflowDefinitionBuilder WithApplicationProperty(string name, object value);
Workflow BuildWorkflow();
}

View file

@ -9,15 +9,15 @@
</PropertyGroup>
<ItemGroup>
<!-- <PackageReference Include="Dahomey.Json" Version="1.12.2" />-->
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="6.0.0-preview.4.21253.7" />
<PackageReference Include="System.Text.Json" Version="6.0.2" />
<PackageReference Include="Dahomey.Json" Version="1.12.2"/>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1"/>
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0"/>
<PackageReference Include="System.ComponentModel.Annotations" Version="6.0.0-preview.4.21253.7"/>
<PackageReference Include="System.Text.Json" Version="6.0.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\Dahomey.Json\src\Dahomey.Json\Dahomey.Json.csproj" />
</ItemGroup>
<!-- <ItemGroup>-->
<!-- <ProjectReference Include="..\..\..\..\..\Dahomey.Json\src\Dahomey.Json\Dahomey.Json.csproj" />-->
<!-- </ItemGroup>-->
</Project>

View file

@ -5,6 +5,7 @@ namespace Elsa;
public static class DictionaryExtensions
{
public static bool TryGetValue<T>(this IDictionary<string, object?> dictionary, string key, out T? value) => dictionary.TryGetValue<string, T>(key, out value);
public static bool TryGetValue<T>(this IDictionary<object, object?> dictionary, string key, out T? value) => dictionary.TryGetValue<object, T>(key, out value);
public static bool TryGetValue<TKey, T>(this IDictionary<TKey, object?> dictionary, TKey key, out T? value)
{
@ -18,10 +19,12 @@ public static class DictionaryExtensions
return true;
}
public static T? GetValue<TKey, T>(this IDictionary<TKey, object?> dictionary, TKey key) => ConvertValue<T>(dictionary[key]);
public static T? GetValue<TKey, T>(this IDictionary<TKey, T> dictionary, TKey key) => ConvertValue<T>(dictionary[key]);
public static T? GetValue<T>(this IDictionary<string, object> dictionary, string key) => ConvertValue<T>(dictionary[key]);
public static T? GetOrAdd<TKey, T>(this IDictionary<TKey, object?> dictionary, TKey key, Func<T> valueFactory) where T : notnull
public static T? GetOrAdd<T>(this IDictionary<string, object?> dictionary, string key, Func<T> valueFactory) where T : notnull => dictionary.GetOrAdd<string, T>(key, valueFactory);
public static T? GetOrAdd<TKey, T>(this IDictionary<TKey, object?> dictionary, TKey key, Func<T> valueFactory)
{
if (dictionary.TryGetValue<TKey, T>(key, out var value))
return value;

View file

@ -21,11 +21,6 @@ public abstract class Activity : IActivity
return ValueTask.CompletedTask;
}
public ValueTask<IEnumerable<object>> GetBookmarkPayloadsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
protected virtual void Execute(ActivityExecutionContext context)
{
}

View file

@ -26,18 +26,18 @@ public class Variable : RegisterLocationReference
public class Variable<T> : Variable
{
public Variable() : base(default(T))
public Variable() : base(typeof(T).Name, default(T))
{
}
public Variable(T value) : base(value ?? default)
public Variable(T value) : base(typeof(T).Name, value ?? default)
{
}
public Variable(string name, T value = default!) : base(name, value ?? default)
{
}
public new T? Get(ActivityExecutionContext context) => base.Get(context).ConvertTo<T?>();
public new T? Get(ExpressionExecutionContext context) => base.Get(context).ConvertTo<T?>();
}

View file

@ -16,6 +16,8 @@ public class WorkflowExecutionContext
public WorkflowExecutionContext(
IServiceProvider serviceProvider,
string id,
string correlationId,
Workflow workflow,
ActivityNode graph,
IActivityScheduler scheduler,
@ -27,7 +29,8 @@ public class WorkflowExecutionContext
_serviceProvider = serviceProvider;
Workflow = workflow;
Graph = graph;
Id = Guid.NewGuid().ToString("N");
Id = id;
CorrelationId = correlationId;
_nodes = graph.Flatten().Distinct().ToList();
Scheduler = scheduler;
Bookmark = bookmark;
@ -43,6 +46,7 @@ public class WorkflowExecutionContext
public ActivityNode Graph { get; }
public Register Register { get; }
public string Id { get; set; }
public string CorrelationId { get; set; }
public IReadOnlyCollection<ActivityNode> Nodes => new ReadOnlyCollection<ActivityNode>(_nodes);
public IDictionary<string, ActivityNode> NodeIdLookup { get; }
public IDictionary<IActivity, ActivityNode> NodeActivityLookup { get; }

View file

@ -26,6 +26,6 @@ public class ActivityExecutionPipeline : IActivityExecutionPipeline
public async Task ExecuteAsync(ActivityExecutionContext context) => await Pipeline(context);
private ActivityMiddlewareDelegate CreateDefaultPipeline() => Setup(x => x
.UseActivityDrivers()
.UseDefaultActivityInvoker()
);
}

View file

@ -1,13 +1,16 @@
using System.Text.Json;
using Elsa.Contracts;
using Elsa.Models;
using Microsoft.Extensions.Logging;
using Delegate = System.Delegate;
using JsonNode = System.Text.Json.Nodes.JsonNode;
using JsonObject = System.Text.Json.Nodes.JsonObject;
namespace Elsa.Pipelines.ActivityExecution.Components;
public static class InvokeDriversMiddlewareExtensions
{
public static IActivityExecutionBuilder UseActivityDrivers(this IActivityExecutionBuilder builder) => builder.UseMiddleware<ActivityInvokerMiddleware>();
public static IActivityExecutionBuilder UseDefaultActivityInvoker(this IActivityExecutionBuilder builder) => builder.UseMiddleware<ActivityInvokerMiddleware>();
}
public class ActivityInvokerMiddleware : IActivityExecutionMiddleware
@ -41,7 +44,8 @@ public class ActivityInvokerMiddleware : IActivityExecutionMiddleware
await executeDelegate(context);
// Record executed event.
LogExecutionRecord(context, WorkflowExecutionLogEventNames.Executed);
var payload = context.JournalData.Any() ? context.JournalData : default;
LogExecutionRecord(context, WorkflowExecutionLogEventNames.Executed, payload: payload);
// Reset execute delegate.
workflowExecution.ExecuteDelegate = null;

View file

@ -20,8 +20,7 @@ public class ExpressionEvaluator : IExpressionEvaluator
if (handler != null)
return await handler.EvaluateAsync(input, returnType, context);
var expressionType = input.GetType().GetGenericTypeDefinition();
throw new InvalidOperationException($"Could not find handler for expression type {expressionType}");
throw new InvalidOperationException($"Could not find handler for expression type {input.GetType().FullName}");
}
}

View file

@ -23,6 +23,9 @@ public class ExpressionHandlerRegistry : IExpressionHandlerRegistry
{
var expressionType = input.GetType();
if (expressionType.IsConstructedGenericType)
expressionType = expressionType.BaseType;
if (!Dictionary.TryGetValue(expressionType, out var handlerType))
return null;

View file

@ -69,7 +69,7 @@ public class IdentityGraphService : IIdentityGraphService
{
if (!string.IsNullOrWhiteSpace(activityNode.NodeId))
return activityNode.NodeId;
var fullTypeName = activityNode.Activity.TypeName;
var shortTypeName = fullTypeName.Split('.').Last();
var index = GetNextIndexFor(shortTypeName, identityCounters);

View file

@ -13,6 +13,7 @@ public class WorkflowRunner : IWorkflowRunner
private readonly IWorkflowStateSerializer _workflowStateSerializer;
private readonly IIdentityGraphService _identityGraphService;
private readonly IActivitySchedulerFactory _schedulerFactory;
private readonly IIdentityGenerator _identityGenerator;
public WorkflowRunner(
IServiceScopeFactory serviceScopeFactory,
@ -20,7 +21,8 @@ public class WorkflowRunner : IWorkflowRunner
IWorkflowExecutionPipeline pipeline,
IWorkflowStateSerializer workflowStateSerializer,
IIdentityGraphService identityGraphService,
IActivitySchedulerFactory schedulerFactory)
IActivitySchedulerFactory schedulerFactory,
IIdentityGenerator identityGenerator)
{
_serviceScopeFactory = serviceScopeFactory;
_activityWalker = activityWalker;
@ -28,6 +30,7 @@ public class WorkflowRunner : IWorkflowRunner
_workflowStateSerializer = workflowStateSerializer;
_identityGraphService = identityGraphService;
_schedulerFactory = schedulerFactory;
_identityGenerator = identityGenerator;
}
public async Task<InvokeWorkflowResult> RunAsync(Workflow workflow, IDictionary<string, object>? input, CancellationToken cancellationToken = default)
@ -105,14 +108,12 @@ public class WorkflowRunner : IWorkflowRunner
var scheduler = _schedulerFactory.CreateScheduler();
// Setup a workflow execution context.
var workflowExecutionContext = new WorkflowExecutionContext(serviceProvider, workflow, graph, scheduler, bookmark, input, executeActivityDelegate, cancellationToken);
var id = workflowState?.Id ?? _identityGenerator.GenerateId();
var correlationId = workflowState?.CorrelationId ?? _identityGenerator.GenerateId();
var workflowExecutionContext = new WorkflowExecutionContext(serviceProvider, id, correlationId, workflow, graph, scheduler, bookmark, input, executeActivityDelegate, cancellationToken);
// Restore workflow execution context from state, if provided.
if (workflowState != null)
{
var workflowStateService = serviceProvider.GetRequiredService<IWorkflowStateSerializer>();
workflowStateService.WriteState(workflowExecutionContext, workflowState);
}
if (workflowState != null) _workflowStateSerializer.WriteState(workflowExecutionContext, workflowState);
return workflowExecutionContext;
}

View file

@ -19,7 +19,8 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
{
var state = new WorkflowState
{
Id = workflowExecutionContext.Id
Id = workflowExecutionContext.Id,
CorrelationId = workflowExecutionContext.CorrelationId
};
//GetOutput(state, workflowExecutionContext);
@ -33,6 +34,7 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
public void WriteState(WorkflowExecutionContext workflowExecutionContext, WorkflowState state)
{
workflowExecutionContext.Id = state.Id;
workflowExecutionContext.CorrelationId = state.CorrelationId;
//SetOutput(state, workflowExecutionContext);
SetProperties(state, workflowExecutionContext);
SetActivityExecutionContexts(state, workflowExecutionContext);

View file

@ -3,6 +3,7 @@ namespace Elsa.State;
public class WorkflowState
{
public string Id { get; set; } = default!;
public string CorrelationId { get; set; } = default!;
public IDictionary<string, IDictionary<string, object?>> ActivityOutput { get; set; } = new Dictionary<string, IDictionary<string, object?>>();
public ICollection<CompletionCallbackState> CompletionCallbacks { get; set; } = new List<CompletionCallbackState>();
public ICollection<ActivityExecutionContextState> ActivityExecutionContexts { get; set; } = new List<ActivityExecutionContextState>();

View file

@ -63,14 +63,6 @@ public static class ServiceCollectionExtensions
.AddLogging();
}
private static IServiceCollection AddDefaultExpressionHandlers(this IServiceCollection services) =>
services
.AddExpressionHandler<LiteralExpressionHandler, LiteralExpression>()
.AddExpressionHandler<DelegateExpressionHandler, DelegateExpression>()
.AddExpressionHandler<VariableExpressionHandler, VariableExpression>()
.AddExpressionHandler<JsonExpressionHandler, JsonExpression>()
.AddExpressionHandler<ElsaExpressionHandler, ElsaExpression>();
public static IServiceCollection AddWorkflowProvider<T>(this IServiceCollection services) where T : class, IWorkflowProvider => services.AddSingleton<IWorkflowProvider, T>();
public static IServiceCollection AddStimulusHandler<T>(this IServiceCollection services) where T : class, IStimulusHandler => services.AddSingleton<IStimulusHandler, T>();
public static IServiceCollection AddInstructionInterpreter<T>(this IServiceCollection services) where T : class, IWorkflowInstructionInterpreter => services.AddSingleton<IWorkflowInstructionInterpreter, T>();

View file

@ -9,10 +9,11 @@ namespace Elsa.Modules.Activities.Activities.Workflows;
/// <summary>
/// Creates a new workflow instance of the specified workflow and dispatches it for execution.
/// </summary>
[Activity("Elsa", "Workflows","Create a new workflow instance of the specified workflow and dispatches it for execution.")]
[Activity("Elsa", "Workflows", "Create a new workflow instance of the specified workflow and dispatches it for execution.")]
public class DispatchWorkflow : Activity
{
public Input<string> WorkflowDefinitionId { get; set; } = new("");
public Input<string> CorrelationId { get; set; } = new("");
public Input<IDictionary<string, object>>? Input { get; set; }
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
@ -28,6 +29,7 @@ public class DispatchWorkflow : Activity
var workflowService = context.GetRequiredService<IWorkflowService>();
var input = context.Get(Input);
await workflowService.DispatchWorkflowAsync(workflowDefinitionId, VersionOptions.Published, input, context.CancellationToken);
var correlationId = CorrelationId.Get(context);
await workflowService.DispatchWorkflowAsync(workflowDefinitionId, VersionOptions.Published, input, correlationId, context.CancellationToken);
}
}

View file

@ -72,10 +72,11 @@ public class Worker : IAsyncDisposable
{
var payload = new MessageReceivedTriggerPayload(QueueOrTopic, Subscription);
var hash = _hasher.Hash(payload);
var correlationId = message.CorrelationId;
var messageModel = CreateMessageModel(message);
var input = new Dictionary<string, object>() { [MessageReceived.InputKey] = messageModel };
var stimulus = Stimulus.Standard(BookmarkName, hash, input);
var executionResults = (await _workflowService.ExecuteStimulusAsync(stimulus, cancellationToken)).ToList();
var stimulus = Stimulus.Standard(BookmarkName, hash, input, correlationId);
var executionResults = (await _workflowService.DispatchStimulusAsync(stimulus, cancellationToken)).ToList();
_logger.LogInformation("Triggered {WorkflowCount} workflows", executionResults.Count);
}

View file

@ -25,6 +25,7 @@ public class Delay : Activity
var resumeAt = clock.UtcNow.Add(timeSpan);
var payload = new DelayPayload(resumeAt);
context.JournalData.Add("ResumeAt", resumeAt);
context.CreateBookmark(payload);
}

View file

@ -15,16 +15,18 @@ public class RunWorkflowJob : Job
{
}
public RunWorkflowJob(string workflowId)
public RunWorkflowJob(string workflowId, string? correlationId = default)
{
WorkflowId = workflowId;
CorrelationId = correlationId;
}
public string WorkflowId { get; set; } = default!;
public string? CorrelationId { get; set; }
protected override async ValueTask ExecuteAsync(JobExecutionContext context)
{
var request = new DispatchWorkflowDefinitionRequest(WorkflowId, VersionOptions.Published);
var request = new DispatchWorkflowDefinitionRequest(WorkflowId, VersionOptions.Published, CorrelationId: CorrelationId);
var workflowDispatcher = context.GetRequiredService<IWorkflowDispatcher>();
await workflowDispatcher.DispatchAsync(request, context.CancellationToken);
}

View file

@ -0,0 +1,47 @@
using Elsa.Contracts;
using Elsa.Modules.WorkflowContexts.Models;
namespace Elsa.Modules.WorkflowContexts.Extensions;
public static class ActivityExtensions
{
private const string ActivityWorkflowContextSettingsKey = "ActivityWorkflowContextSettingsKey";
public static IDictionary<WorkflowContext, ActivityWorkflowContextSettings> GetWorkflowContextSettings(this IActivity activity)
{
return activity.ApplicationProperties.GetOrAdd(ActivityWorkflowContextSettingsKey, () => new Dictionary<WorkflowContext, ActivityWorkflowContextSettings>())!;
}
public static TActivity LoadContext<TActivity>(this TActivity activity, WorkflowContext workflowContext) where TActivity : IActivity
{
var settings = activity.GetActivityWorkflowContextSettings(workflowContext);
settings.Load = true;
return activity;
}
public static TActivity SaveContext<TActivity>(this TActivity activity, WorkflowContext workflowContext) where TActivity : IActivity
{
var settings = activity.GetActivityWorkflowContextSettings(workflowContext);
settings.Save = true;
return activity;
}
public static ActivityWorkflowContextSettings GetActivityWorkflowContextSettings<TActivity>(this TActivity activity, WorkflowContext workflowContext) where TActivity: IActivity
{
var dictionary = activity.GetWorkflowContextSettings()!;
return dictionary.GetActivityWorkflowContextSettings(workflowContext);
}
public static ActivityWorkflowContextSettings GetActivityWorkflowContextSettings(this IDictionary<WorkflowContext, ActivityWorkflowContextSettings> dictionary, WorkflowContext workflowContext)
{
var settings = dictionary.ContainsKey(workflowContext) ? dictionary[workflowContext] : default;
if(settings == null)
{
settings = new ActivityWorkflowContextSettings();
dictionary[workflowContext] = settings;
}
return settings;
}
}

View file

@ -1,5 +1,6 @@
using Elsa.Contracts;
using Elsa.Modules.WorkflowContexts.Middleware;
using Elsa.Pipelines.ActivityExecution;
using Elsa.Pipelines.WorkflowExecution;
namespace Elsa.Modules.WorkflowContexts.Extensions;
@ -9,5 +10,10 @@ public static class WorkflowExecutionBuilderExtensions
/// <summary>
/// Installs middleware that enables the use of workflow context.
/// </summary>
public static IWorkflowExecutionBuilder UseWorkflowContexts(this IWorkflowExecutionBuilder builder) => builder.UseMiddleware<WorkflowContextMiddleware>();
public static IWorkflowExecutionBuilder UseWorkflowContexts(this IWorkflowExecutionBuilder builder) => builder.UseMiddleware<WorkflowContextWorkflowExecutionMiddleware>();
/// <summary>
/// Installs middleware that enables the use of workflow context.
/// </summary>
public static IActivityExecutionBuilder UseWorkflowContexts(this IActivityExecutionBuilder builder) => builder.UseMiddleware<WorkflowContextActivityExecutionMiddleware>();
}

View file

@ -7,8 +7,8 @@ public static class WorkflowExecutionContextExtensions
{
public static void SetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext, WorkflowContext workflowContext, object? value) => workflowExecutionContext.TransientProperties.SetWorkflowContext(workflowContext, value);
public static void SetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext, WorkflowContext workflowContext, object? value) => expressionExecutionContext.TransientProperties.SetWorkflowContext(workflowContext, value);
public static T? GetWorkflowContext<T, TProvider>(this WorkflowExecutionContext workflowExecutionContext) => (T?)workflowExecutionContext.GetWorkflowContext(new WorkflowContext(typeof(TProvider)));
public static T? GetWorkflowContext<T, TProvider>(this ExpressionExecutionContext expressionExecutionContext) => (T?)expressionExecutionContext.TransientProperties.GetWorkflowContext(new WorkflowContext(typeof(TProvider)));
public static T? GetWorkflowContext<T, TProvider>(this WorkflowExecutionContext workflowExecutionContext) => (T?)workflowExecutionContext.TransientProperties.GetWorkflowContextByProviderType(typeof(TProvider));
public static T? GetWorkflowContext<T, TProvider>(this ExpressionExecutionContext expressionExecutionContext) => (T?)expressionExecutionContext.TransientProperties.GetWorkflowContextByProviderType(typeof(TProvider));
public static object? GetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext, WorkflowContext workflowContext) => workflowExecutionContext.TransientProperties.GetWorkflowContext(workflowContext);
public static object? GetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext, WorkflowContext workflowContext) => expressionExecutionContext.TransientProperties.GetWorkflowContext(workflowContext);
@ -20,7 +20,25 @@ public static class WorkflowExecutionContextExtensions
private static object? GetWorkflowContext(this IDictionary<object, object?> transientProperties, WorkflowContext workflowContext)
{
var contextDictionary = transientProperties.GetOrAdd("WorkflowContexts", () => new Dictionary<WorkflowContext, object?>())!;
var contextDictionary = transientProperties.GetWorkflowContextDictionary();
return contextDictionary[workflowContext];
}
private static object? GetWorkflowContextByProviderType(this IDictionary<object, object?> transientProperties, Type providerType) =>
transientProperties.FindWorkflowContext(x => x.ProviderType == providerType);
private static object? FindWorkflowContext(this IDictionary<object, object?> transientProperties, Func<WorkflowContext, bool> filter)
{
var contextDictionary = transientProperties.GetWorkflowContextDictionary();
var query =
from entry in contextDictionary
where filter(entry.Key)
select entry.Value;
return query.FirstOrDefault();
}
private static IDictionary<WorkflowContext, object?> GetWorkflowContextDictionary(this IDictionary<object, object?> transientProperties) =>
transientProperties.GetOrAdd("WorkflowContexts", () => new Dictionary<WorkflowContext, object?>())!;
}

View file

@ -0,0 +1,69 @@
using Elsa.Contracts;
using Elsa.Models;
using Elsa.Modules.WorkflowContexts.Contracts;
using Elsa.Modules.WorkflowContexts.Extensions;
using Elsa.Modules.WorkflowContexts.Models;
using Elsa.Pipelines.ActivityExecution;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Modules.WorkflowContexts.Middleware;
/// <summary>
/// Middleware that loads & save workflow context into the currently executing workflow using installed workflow context providers.
/// </summary>
public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMiddleware
{
private readonly ActivityMiddlewareDelegate _next;
private readonly IServiceScopeFactory _serviceScopeFactory;
public WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory)
{
_next = next;
_serviceScopeFactory = serviceScopeFactory;
}
public async ValueTask InvokeAsync(ActivityExecutionContext context)
{
// Check if the workflow contains any workflow contexts.
if (!context.WorkflowExecutionContext.Workflow.ApplicationProperties!.TryGetValue<ICollection<WorkflowContext>>("Elsa:WorkflowContexts", out var workflowContexts))
{
await _next(context);
return;
}
// For each workflow context, invoke its provider.
using (var scope = _serviceScopeFactory.CreateScope())
{
foreach (var workflowContext in workflowContexts!)
{
var load = context.Activity.GetActivityWorkflowContextSettings(workflowContext).Load;
if (!load) continue;
var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, workflowContext.ProviderType);
var value = await provider.LoadAsync(context.WorkflowExecutionContext);
// Store the loaded value into the workflow execution context.
context.WorkflowExecutionContext.SetWorkflowContext(workflowContext, value);
}
}
// Invoke the next middleware.
await _next(context);
// For each workflow context, invoke its provider to update the context.
using (var scope = _serviceScopeFactory.CreateScope())
{
foreach (var workflowContext in workflowContexts!)
{
var save = context.Activity.GetActivityWorkflowContextSettings(workflowContext).Load;
if (!save) continue;
// Get the loaded value from the workflow execution context.
var value = context.WorkflowExecutionContext.GetWorkflowContext(workflowContext);
var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, workflowContext.ProviderType);
await provider.SaveAsync(context.WorkflowExecutionContext, value);
}
}
}
}

View file

@ -11,12 +11,12 @@ namespace Elsa.Modules.WorkflowContexts.Middleware;
/// <summary>
/// Middleware that loads & save workflow context into the currently executing workflow using installed workflow context providers.
/// </summary>
public class WorkflowContextMiddleware : WorkflowExecutionMiddleware
public class WorkflowContextWorkflowExecutionMiddleware : WorkflowExecutionMiddleware
{
private readonly IServiceProvider _serviceProvider;
private readonly IServiceScopeFactory _serviceScopeFactory;
public WorkflowContextMiddleware(WorkflowMiddlewareDelegate next, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory) : base(next)
public WorkflowContextWorkflowExecutionMiddleware(WorkflowMiddlewareDelegate next, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory) : base(next)
{
_serviceProvider = serviceProvider;
_serviceScopeFactory = serviceScopeFactory;

View file

@ -0,0 +1,7 @@
namespace Elsa.Modules.WorkflowContexts.Models;
public class ActivityWorkflowContextSettings
{
public bool Load { get; set; }
public bool Save { get; set; }
}

View file

@ -22,7 +22,6 @@ public class WorkflowContext<T, TProvider> : WorkflowContext where TProvider:IWo
public T? Get(ExpressionExecutionContext context)
{
var workflowContexts = (IDictionary<WorkflowContext, object?>)context.TransientProperties["WorkflowContexts"]!;
return (T?)workflowContexts[this];
return workflowContexts.TryGetValue(this, out var workflowContext) ? (T?)workflowContext : default;
}
}

View file

@ -1,24 +1,25 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Persistence.Entities;
using Elsa.Persistence.EntityFrameworkCore.Contracts;
using Elsa.Serialization;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Elsa.Persistence.EntityFrameworkCore.Handlers.Serialization;
public class WorkflowExecutionLogRecordSerializer : IEntitySerializer<WorkflowExecutionLogRecord>
{
private readonly WorkflowSerializerOptionsProvider _workflowSerializerOptionsProvider;
private readonly JsonSerializerOptions _options;
public WorkflowExecutionLogRecordSerializer(WorkflowSerializerOptionsProvider workflowSerializerOptionsProvider)
public WorkflowExecutionLogRecordSerializer()
{
_workflowSerializerOptionsProvider = workflowSerializerOptionsProvider;
_options = new JsonSerializerOptions();
_options.Converters.Add(new JsonStringEnumConverter());
}
public void Serialize(ElsaDbContext dbContext, WorkflowExecutionLogRecord entity)
{
var options = _workflowSerializerOptionsProvider.CreatePersistenceOptions();
var json = entity.Payload != null ? JsonSerializer.Serialize(entity.Payload, options) : default!;
var json = entity.Payload != null ? JsonSerializer.Serialize(entity.Payload, _options) : default!;
dbContext.Entry(entity).Property("PayloadData").CurrentValue = json;
}
@ -26,10 +27,6 @@ public class WorkflowExecutionLogRecordSerializer : IEntitySerializer<WorkflowEx
{
var json = (string?)dbContext.Entry(entity).Property("PayloadData").CurrentValue;
if (!string.IsNullOrWhiteSpace(json))
{
var options = _workflowSerializerOptionsProvider.CreatePersistenceOptions();
entity.Payload = JsonSerializer.Deserialize<object>(json, options)!;
}
if (!string.IsNullOrWhiteSpace(json)) entity.Payload = JsonSerializer.Deserialize<object>(json, _options)!;
}
}

View file

@ -59,6 +59,8 @@ public static class ServiceCollectionExtensions
private static ActorSystemConfig GetSystemConfig() =>
ActorSystemConfig
.Setup()
.WithDeveloperSupervisionLogging(true)
.WithDeveloperReceiveLogging(TimeSpan.FromHours(1))
.WithDeadLetterThrottleCount(3)
.WithDeadLetterThrottleInterval(TimeSpan.FromSeconds(10000))
.WithDeveloperSupervisionLogging(true)

View file

@ -7,7 +7,6 @@ using Elsa.Persistence.Commands;
using Elsa.Persistence.Entities;
using Elsa.Persistence.Models;
using Elsa.Runtime.Contracts;
using Elsa.Runtime.ProtoActor.Services;
using Elsa.Runtime.Protos;
using Elsa.State;
using Proto;
@ -23,38 +22,37 @@ public class WorkflowDefinitionGrain : WorkflowDefinitionGrainBase
private readonly ICommandSender _commandSender;
private readonly IIdentityGenerator _identityGenerator;
private readonly ISystemClock _systemClock;
private readonly GrainClientFactory _grainClientFactory;
public WorkflowDefinitionGrain(
IWorkflowRegistry workflowRegistry,
ICommandSender commandSender,
IIdentityGenerator identityGenerator,
IWorkflowRegistry workflowRegistry,
ICommandSender commandSender,
IIdentityGenerator identityGenerator,
ISystemClock systemClock,
GrainClientFactory grainClientFactory,
IContext context) : base(context)
{
_workflowRegistry = workflowRegistry;
_commandSender = commandSender;
_identityGenerator = identityGenerator;
_systemClock = systemClock;
_grainClientFactory = grainClientFactory;
}
public override async Task<ExecuteWorkflowDefinitionResponse> Execute(ExecuteWorkflowDefinitionRequest request)
{
var workflowDefinitionId = request.Id;
var correlationId = request.CorrelationId;
var cancellationToken = Context.CancellationToken;
var workflowInstance = await CreateWorkflowInstanceAsync(workflowDefinitionId, cancellationToken);
var workflowInstance = await CreateWorkflowInstanceAsync(workflowDefinitionId, correlationId, cancellationToken);
var executeWorkflowInstanceMessage = new ExecuteWorkflowInstanceRequest
{
Id = workflowInstance.Id,
Input = request.Input
Input = request.Input,
CorrelationId = correlationId
};
var workflowInstanceGrainClient = Context.GetWorkflowInstanceGrain(workflowInstance.Id);
var workflowInstanceResponse = await workflowInstanceGrainClient.Execute(executeWorkflowInstanceMessage, cancellationToken);
if (workflowInstanceResponse == null)
throw new TimeoutException("Did not receive a response from the WorkflowInstance actor within the configured amount of time.");
@ -67,19 +65,21 @@ public class WorkflowDefinitionGrain : WorkflowDefinitionGrainBase
return response;
}
private async Task<WorkflowInstance> CreateWorkflowInstanceAsync(string workflowDefinitionId, CancellationToken cancellationToken)
private async Task<WorkflowInstance> CreateWorkflowInstanceAsync(string workflowDefinitionId, string? correlationId, CancellationToken cancellationToken)
{
var workflow = (await _workflowRegistry.FindByIdAsync(workflowDefinitionId, VersionOptions.Published, cancellationToken))!;
var workflowInstanceId = _identityGenerator.GenerateId();
var correlationId = _identityGenerator.GenerateId();
if (string.IsNullOrWhiteSpace(correlationId))
correlationId = _identityGenerator.GenerateId();
var workflowInstance = new WorkflowInstance
{
Id = workflowInstanceId,
Version = workflow.Identity.Version,
DefinitionId = workflowDefinitionId,
DefinitionVersionId = workflow.Identity.Id,
CorrelationId = correlationId,
CorrelationId = correlationId,
CreatedAt = _systemClock.UtcNow,
WorkflowStatus = WorkflowStatus.Idle,
WorkflowState = new WorkflowState

View file

@ -10,6 +10,7 @@ message ExecuteWorkflowDefinitionRequest {
string id = 1;
string versionOptions = 2;
optional Input input = 3;
optional string CorrelationId = 4;
}
message ExecuteWorkflowDefinitionResponse {
@ -21,6 +22,7 @@ message ExecuteWorkflowInstanceRequest {
string id = 1;
optional Bookmark bookmark = 2;
optional Input input = 3;
optional string CorrelationId = 4;
}
message ExecuteWorkflowInstanceResponse {

View file

@ -29,13 +29,14 @@ public class ProtoActorWorkflowInvoker : IWorkflowInvoker
public async Task<InvokeWorkflowResult> InvokeAsync(InvokeWorkflowDefinitionRequest request, CancellationToken cancellationToken = default)
{
var (definitionId, versionOptions, input) = request;
var (definitionId, versionOptions, input, correlationId) = request;
var message = new ExecuteWorkflowDefinitionRequest
{
Id = definitionId,
VersionOptions = versionOptions.ToString(),
Input = input!?.Serialize()
Input = input!?.Serialize(),
CorrelationId = correlationId ?? ""
};
var client = _grainClientFactory.CreateWorkflowDefinitionGrainClient(definitionId, versionOptions);
@ -52,18 +53,19 @@ public class ProtoActorWorkflowInvoker : IWorkflowInvoker
public async Task<InvokeWorkflowResult> InvokeAsync(InvokeWorkflowInstanceRequest request, CancellationToken cancellationToken = default)
{
var (instanceId, bookmark, input) = request;
var (instanceId, bookmark, input, correlationId) = request;
var bookmarkMessage = MapBookmark(bookmark);
var message = new ExecuteWorkflowInstanceRequest
{
Id = instanceId,
Bookmark = bookmarkMessage,
Input = input!?.Serialize()
Input = input!?.Serialize(),
CorrelationId = correlationId ?? ""
};
var client = _grainClientFactory.CreateWorkflowInstanceGrainClient(instanceId);
var response = await client.Execute(message, cancellationToken);
var response = await client.Execute(message, CancellationTokens.FromSeconds(600));
if (response == null)
throw new TimeoutException("Did not receive a response from the WorkflowInstance actor within the configured amount of time.");

View file

@ -9,11 +9,11 @@ namespace Elsa.Runtime.Contracts;
/// </summary>
public interface IWorkflowService
{
Task<InvokeWorkflowResult> ExecuteWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary<string, object>? input = default, CancellationToken cancellationToken = default);
Task<InvokeWorkflowResult> ExecuteWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary<string, object>? input = default, CancellationToken cancellationToken = default);
Task<DispatchWorkflowDefinitionResponse> DispatchWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary<string, object>? input = default, CancellationToken cancellationToken = default);
Task<DispatchWorkflowInstanceResponse> DispatchWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary<string, object>? input = default, CancellationToken cancellationToken = default);
Task<IEnumerable<ExecuteWorkflowInstructionResult>> ExecuteStimulusAsync(IStimulus stimulus, CancellationToken cancellationToken);
Task<IEnumerable<DispatchWorkflowInstructionResult>> DispatchStimulusAsync(IStimulus stimulus, CancellationToken cancellationToken);
Task<InvokeWorkflowResult> ExecuteWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary<string, object>? input = default, string? correlationId = default, CancellationToken cancellationToken = default);
Task<InvokeWorkflowResult> ExecuteWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary<string, object>? input = default, string? correlationId = default, CancellationToken cancellationToken = default);
Task<DispatchWorkflowDefinitionResponse> DispatchWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary<string, object>? input = default, string? correlationId = default, CancellationToken cancellationToken = default);
Task<DispatchWorkflowInstanceResponse> DispatchWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary<string, object>? input = default, string? correlationId = default, CancellationToken cancellationToken = default);
Task<IEnumerable<ExecuteWorkflowInstructionResult>> ExecuteStimulusAsync(IStimulus stimulus, CancellationToken cancellationToken = default);
Task<IEnumerable<DispatchWorkflowInstructionResult>> DispatchStimulusAsync(IStimulus stimulus, CancellationToken cancellationToken = default);
}

View file

@ -70,11 +70,12 @@ public static class ServiceCollectionExtensions
private static ChannelReader<T> CreateChannelReader<T>(IServiceProvider serviceProvider) => serviceProvider.GetRequiredService<Channel<T>>().Reader;
private static ChannelWriter<T> CreateChannelWriter<T>(IServiceProvider serviceProvider) => serviceProvider.GetRequiredService<Channel<T>>().Writer;
private static IServiceCollection AddDefaultExpressionHandlers(this IServiceCollection services) =>
public static IServiceCollection AddDefaultExpressionHandlers(this IServiceCollection services) =>
services
.AddExpressionHandler<LiteralExpressionHandler, LiteralExpression>()
.AddExpressionHandler<DelegateExpressionHandler, DelegateExpression>()
.AddExpressionHandler<VariableExpressionHandler, VariableExpression>()
.AddExpressionHandler<OutputExpressionHandler, OutputExpression>()
.AddExpressionHandler<ElsaExpressionHandler, ElsaExpression>();
public static IServiceCollection AddWorkflowProvider<T>(this IServiceCollection services) where T : class, IWorkflowProvider => services.AddSingleton<IWorkflowProvider, T>();

View file

@ -25,9 +25,9 @@ public class DispatchedWorkflowDefinitionWorker : BackgroundService
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
await foreach (var (definitionId, versionOptions, input) in _channelReader.ReadAllAsync(cancellationToken))
await foreach (var (definitionId, versionOptions, input, correlationId) in _channelReader.ReadAllAsync(cancellationToken))
{
var request = new InvokeWorkflowDefinitionRequest(definitionId, versionOptions, input);
var request = new InvokeWorkflowDefinitionRequest(definitionId, versionOptions, input, correlationId);
try
{

View file

@ -24,9 +24,9 @@ public class DispatchedWorkflowInstanceWorker : BackgroundService
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
await foreach (var (instanceId, bookmark, input) in _channelReader.ReadAllAsync(cancellationToken))
await foreach (var (instanceId, bookmark, input, correlationId) in _channelReader.ReadAllAsync(cancellationToken))
{
var request = new InvokeWorkflowInstanceRequest(instanceId, bookmark, input);
var request = new InvokeWorkflowInstanceRequest(instanceId, bookmark, input, correlationId);
try
{

View file

@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace Elsa.Runtime.Interpreters;
public record ResumeWorkflowInstruction(WorkflowBookmark WorkflowBookmark, IDictionary<string, object>? Input) : IWorkflowInstruction;
public record ResumeWorkflowInstruction(WorkflowBookmark WorkflowBookmark, IDictionary<string, object>? Input, string? CorrelationId) : IWorkflowInstruction;
public class ResumeWorkflowInstructionInterpreter : WorkflowInstructionInterpreter<ResumeWorkflowInstruction>
{

View file

@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging;
namespace Elsa.Runtime.Interpreters;
public record TriggerWorkflowInstruction(WorkflowTrigger WorkflowTrigger, IDictionary<string, object>? Input) : IWorkflowInstruction;
public record TriggerWorkflowInstruction(WorkflowTrigger WorkflowTrigger, IDictionary<string, object>? Input, string? CorrelationId) : IWorkflowInstruction;
public class TriggerWorkflowInstructionInterpreter : WorkflowInstructionInterpreter<TriggerWorkflowInstruction>
{
@ -41,7 +41,7 @@ public class TriggerWorkflowInstructionInterpreter : WorkflowInstructionInterpre
return null;
// Execute workflow.
var executeRequest = new InvokeWorkflowDefinitionRequest(workflowId, VersionOptions.Published, instruction.Input);
var executeRequest = new InvokeWorkflowDefinitionRequest(workflowId, VersionOptions.Published, instruction.Input, instruction.CorrelationId);
var workflowExecutionResult = await _workflowInvoker.InvokeAsync(executeRequest, cancellationToken);
return new ExecuteWorkflowInstructionResult(workflow, workflowExecutionResult);
@ -59,7 +59,7 @@ public class TriggerWorkflowInstructionInterpreter : WorkflowInstructionInterpre
return null;
// Execute workflow.
var dispatchRequest = new DispatchWorkflowDefinitionRequest(definitionId, VersionOptions.Published, instruction.Input);
var dispatchRequest = new DispatchWorkflowDefinitionRequest(definitionId, VersionOptions.Published, instruction.Input, instruction.CorrelationId);
await _workflowDispatcher.DispatchAsync(dispatchRequest, cancellationToken);
return new DispatchWorkflowInstructionResult();

View file

@ -2,4 +2,4 @@ using Elsa.Persistence.Models;
namespace Elsa.Runtime.Models;
public record DispatchWorkflowDefinitionRequest(string DefinitionId, VersionOptions VersionOptions, IDictionary<string, object>? Input = default);
public record DispatchWorkflowDefinitionRequest(string DefinitionId, VersionOptions VersionOptions, IDictionary<string, object>? Input = default, string? CorrelationId = default);

View file

@ -2,4 +2,4 @@ using Elsa.Models;
namespace Elsa.Runtime.Models;
public record DispatchWorkflowInstanceRequest(string InstanceId, Bookmark? Bookmark = default, IDictionary<string, object>? Input = default);
public record DispatchWorkflowInstanceRequest(string InstanceId, Bookmark? Bookmark = default, IDictionary<string, object>? Input = default, string? CorrelationId = default);

View file

@ -2,4 +2,4 @@ using Elsa.Persistence.Models;
namespace Elsa.Runtime.Models;
public record InvokeWorkflowDefinitionRequest(string DefinitionId, VersionOptions VersionOptions, IDictionary<string, object>? Input = default);
public record InvokeWorkflowDefinitionRequest(string DefinitionId, VersionOptions VersionOptions, IDictionary<string, object>? Input = default, string? CorrelationId = default);

View file

@ -2,4 +2,4 @@ using Elsa.Models;
namespace Elsa.Runtime.Models;
public record InvokeWorkflowInstanceRequest(string InstanceId, Bookmark? Bookmark = default, IDictionary<string, object>? Input = default);
public record InvokeWorkflowInstanceRequest(string InstanceId, Bookmark? Bookmark = default, IDictionary<string, object>? Input = default, string? CorrelationId = default);

View file

@ -4,7 +4,7 @@ namespace Elsa.Runtime.Models;
public static class Stimulus
{
public static StandardStimulus Standard(string activityTypeName, string? hash = default, IDictionary<string, object>? input = default) => new(activityTypeName, hash, input);
public static StandardStimulus Standard(string activityTypeName, string? hash, object input) => new(activityTypeName, hash, input.ToDictionary());
public static StandardStimulus Standard(string activityTypeName, object input) => new(activityTypeName, default, input.ToDictionary());
public static StandardStimulus Standard(string activityTypeName, string? hash = default, IDictionary<string, object>? input = default, string? correlationId = default) => new(activityTypeName, hash, input, correlationId);
public static StandardStimulus Standard(string activityTypeName, string? hash, object input, string? correlationId = default) => new(activityTypeName, hash, input.ToDictionary(), correlationId);
public static StandardStimulus Standard(string activityTypeName, object input, string? correlationId = default) => new(activityTypeName, default, input.ToDictionary(), correlationId);
}

View file

@ -24,27 +24,27 @@ public class WorkflowService : IWorkflowService
_stimulusInterpreter = stimulusInterpreter;
}
public async Task<InvokeWorkflowResult> ExecuteWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary<string, object>? input = default, CancellationToken cancellationToken = default)
public async Task<InvokeWorkflowResult> ExecuteWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary<string, object>? input = default, string? correlationId = default, CancellationToken cancellationToken = default)
{
var executeRequest = new InvokeWorkflowDefinitionRequest(definitionId, versionOptions, input);
var executeRequest = new InvokeWorkflowDefinitionRequest(definitionId, versionOptions, input, correlationId);
return await _workflowInvoker.InvokeAsync(executeRequest, cancellationToken);
}
public async Task<InvokeWorkflowResult> ExecuteWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary<string, object>? input = default, CancellationToken cancellationToken = default)
public async Task<InvokeWorkflowResult> ExecuteWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary<string, object>? input = default, string? correlationId = default, CancellationToken cancellationToken = default)
{
var request = new InvokeWorkflowInstanceRequest(instanceId, bookmark, input);
var request = new InvokeWorkflowInstanceRequest(instanceId, bookmark, input, correlationId);
return await _workflowInvoker.InvokeAsync(request, cancellationToken);
}
public async Task<DispatchWorkflowDefinitionResponse> DispatchWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary<string, object>? input = default, CancellationToken cancellationToken = default)
public async Task<DispatchWorkflowDefinitionResponse> DispatchWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary<string, object>? input = default, string? correlationId = default, CancellationToken cancellationToken = default)
{
var executeRequest = new DispatchWorkflowDefinitionRequest(definitionId, versionOptions, input);
var executeRequest = new DispatchWorkflowDefinitionRequest(definitionId, versionOptions, input, correlationId);
return await _workflowDispatcher.DispatchAsync(executeRequest, cancellationToken);
}
public async Task<DispatchWorkflowInstanceResponse> DispatchWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary<string, object>? input = default, CancellationToken cancellationToken = default)
public async Task<DispatchWorkflowInstanceResponse> DispatchWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary<string, object>? input = default, string? correlationId = default, CancellationToken cancellationToken = default)
{
var request = new DispatchWorkflowInstanceRequest(instanceId, bookmark, input);
var request = new DispatchWorkflowInstanceRequest(instanceId, bookmark, input, correlationId);
return await _workflowDispatcher.DispatchAsync(request, cancellationToken);
}

View file

@ -14,6 +14,6 @@ public class ResumeWorkflowsStimulusHandler : StimulusHandler<StandardStimulus>
protected override async ValueTask<IEnumerable<IWorkflowInstruction>> GetInstructionsAsync(StandardStimulus stimulus, CancellationToken cancellationToken = default)
{
var workflowBookmarks = (await _mediator.RequestAsync(new FindWorkflowBookmarks(stimulus.ActivityTypeName, stimulus.Hash), cancellationToken)).ToList();
return workflowBookmarks.Select(x => new ResumeWorkflowInstruction(x, stimulus.Input));
return workflowBookmarks.Select(x => new ResumeWorkflowInstruction(x, stimulus.Input, stimulus.CorrelationId));
}
}

View file

@ -14,6 +14,6 @@ public class TriggerWorkflowsStimulusHandler : StimulusHandler<StandardStimulus>
protected override async ValueTask<IEnumerable<IWorkflowInstruction>> GetInstructionsAsync(StandardStimulus stimulus, CancellationToken cancellationToken = default)
{
var workflowTriggers = (await _mediator.RequestAsync(new FindWorkflowTriggersByName(stimulus.ActivityTypeName, stimulus.Hash), cancellationToken)).ToList();
return workflowTriggers.Select(x => new TriggerWorkflowInstruction(x, stimulus.Input));
return workflowTriggers.Select(x => new TriggerWorkflowInstruction(x, stimulus.Input, stimulus.CorrelationId));
}
}

View file

@ -5,4 +5,4 @@ namespace Elsa.Runtime.Stimuli;
/// <summary>
/// Represents a simple stimulus that targets a specific activity type and optionally a corresponding hash to use as a bookmark lookup.
/// </summary>
public record StandardStimulus(string ActivityTypeName, string? Hash = default, IDictionary<string, object>? Input = default) : IStimulus;
public record StandardStimulus(string ActivityTypeName, string? Hash = default, IDictionary<string, object>? Input = default, string? CorrelationId = default) : IStimulus;

View file

@ -25,7 +25,7 @@ class Program
// Configure activity execution pipeline.
.ConfigureDefaultActivityExecutionPipeline(pipeline => pipeline
.UseLogging()
.UseActivityDrivers()
.UseDefaultActivityInvoker()
)
// Configure workflow engine execution pipeline.
@ -49,7 +49,7 @@ class Program
var workflow13 = new Func<IActivity>(BlockingParallelForEachWorkflow.Create);
var workflow14 = new Func<IActivity>(FlowchartWorkflow.Create);
var workflowFactory = workflow3;
var workflowFactory = workflow1;
var workflowGraph = workflowFactory();
var workflow = Workflow.FromActivity(workflowGraph);

View file

@ -5,5 +5,5 @@ namespace Elsa.Samples.Console1.Workflows;
public static class HelloWorldWorkflow
{
public static IActivity Create() => new WriteLine("Hello World!");
public static IActivity Create() => new WriteLine("Hello World!") { Id = "WriteLine1" };
}