diff --git a/src/modules/Elsa.Http/Activities/FlowSendHttpRequest.cs b/src/modules/Elsa.Http/Activities/FlowSendHttpRequest.cs index cae3cbacf..f46f0e4d2 100644 --- a/src/modules/Elsa.Http/Activities/FlowSendHttpRequest.cs +++ b/src/modules/Elsa.Http/Activities/FlowSendHttpRequest.cs @@ -12,7 +12,8 @@ namespace Elsa.Http; /// /// Send an HTTP request. /// -[Activity("Elsa", "HTTP", "Send an HTTP request.", DisplayName = "HTTP Request (flow)", Kind = ActivityKind.Action)] +[Activity("Elsa", "HTTP", "Send an HTTP request. This activity yis deprecated in favor of the SendHttpRequestTask activity", DisplayName = "HTTP Request (flow) [Deprecated] ", Kind = ActivityKind.Task)] +[Obsolete("Use SendHttpRequestTask instead.")] public class FlowSendHttpRequest : SendHttpRequestBase, IActivityPropertyDefaultValueProvider { /// diff --git a/src/modules/Elsa.Http/Activities/SendHttpRequest.cs b/src/modules/Elsa.Http/Activities/SendHttpRequest.cs index 0fd76e72c..b45e5558f 100644 --- a/src/modules/Elsa.Http/Activities/SendHttpRequest.cs +++ b/src/modules/Elsa.Http/Activities/SendHttpRequest.cs @@ -10,7 +10,7 @@ namespace Elsa.Http; /// /// Send an HTTP request. /// -[Activity("Elsa", "HTTP", "Send an HTTP request.", DisplayName = "HTTP Request", Kind = ActivityKind.Action)] +[Activity("Elsa", "HTTP", "Send an HTTP request.", DisplayName = "HTTP Request", Kind = ActivityKind.Task)] public class SendHttpRequest : SendHttpRequestBase { /// diff --git a/src/modules/Elsa.Http/Activities/SendHttpRequestTask.cs b/src/modules/Elsa.Http/Activities/SendHttpRequestTask.cs index 3c2c114ba..911ca0039 100644 --- a/src/modules/Elsa.Http/Activities/SendHttpRequestTask.cs +++ b/src/modules/Elsa.Http/Activities/SendHttpRequestTask.cs @@ -15,7 +15,7 @@ namespace Elsa.Http; /// /// Sends HTTP requests from a background task. /// -[Activity("Elsa", "HTTP", "Send an HTTP request from a background task.", DisplayName = "HTTP Request Task", Kind = ActivityKind.Job)] +[Activity("Elsa", "HTTP", "Send an HTTP request from a background task.", DisplayName = "HTTP Request Task", Kind = ActivityKind.Task)] public class SendHttpRequestTask : Activity { /// @@ -115,23 +115,23 @@ public class SendHttpRequestTask : Activity context.SetResult(response.StatusCode); context.Set(ParsedContent, parsedContent); - HandleResponse(context, response); + await HandleResponseAsync(context, response); } catch (HttpRequestException e) { context.AddExecutionLogEntry("Error", e.Message, payload: new { StackTrace = e.StackTrace }); context.JournalData.Add("Error", e.Message); - HandleRequestException(context, e); + await HandleRequestExceptionAsync(context, e); } catch (TaskCanceledException e) { context.AddExecutionLogEntry("Error", e.Message, payload: new { StackTrace = e.StackTrace }); context.JournalData.Add("Cancelled", true); - HandleTaskCanceledException(context, e); + await HandleTaskCanceledExceptionAsync(context, e); } } - private void HandleResponse(ActivityExecutionContext context, HttpResponseMessage response) + private async Task HandleResponseAsync(ActivityExecutionContext context, HttpResponseMessage response) { var expectedStatusCodes = ExpectedStatusCodes.GetOrDefault(context) ?? new List(0); var statusCode = (int)response.StatusCode; @@ -144,17 +144,17 @@ public class SendHttpRequestTask : Activity outcomes.Add("Done"); context.JournalData["StatusCode"] = statusCode; - context.SetBackgroundOutcomes(outcomes); + await context.CompleteActivityWithOutcomesAsync(outcomes.ToArray()); } - private void HandleRequestException(ActivityExecutionContext context, HttpRequestException exception) + private async Task HandleRequestExceptionAsync(ActivityExecutionContext context, HttpRequestException exception) { - context.SetBackgroundOutcomes(new[] { "Failed to connect" }); + await context.CompleteActivityWithOutcomesAsync("Failed to connect"); } - private void HandleTaskCanceledException(ActivityExecutionContext context, TaskCanceledException exception) + private async Task HandleTaskCanceledExceptionAsync(ActivityExecutionContext context, TaskCanceledException exception) { - context.SetBackgroundOutcomes(new[] { "Timeout" }); + await context.CompleteActivityWithOutcomesAsync("Timeout"); } private async Task ParseContentAsync(ActivityExecutionContext context, HttpContent httpContent) diff --git a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs index d587de713..ccb7d4ba2 100644 --- a/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs +++ b/src/modules/Elsa.WorkflowContexts/Middleware/WorkflowContextActivityExecutionMiddleware.cs @@ -36,7 +36,7 @@ public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMidd } // Check if this is a background execution. - var isBackgroundExecution = context.TransientProperties.GetValueOrDefault(BackgroundActivityInvokerMiddleware.IsBackgroundExecution); + var isBackgroundExecution = context.GetIsBackgroundExecution(); // Is the activity configured to load the context? foreach (var providerType in providerTypes) diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs index 2fd7df1b5..1aa60ee4a 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs @@ -230,6 +230,12 @@ public class ActivityExecutionContext : IExecutionContext /// The options used to schedule the activity. public async ValueTask ScheduleActivityAsync(ActivityNode? activityNode, ActivityExecutionContext? owner = default, ScheduleWorkOptions? options = default) { + if (this.GetIsBackgroundExecution()) + { + // TODO: Capture the information in a serializable format and store it in the workflow execution context Properties dictionary. + // The information should be stored in a way that allows the workflow execution context to resume the activity execution context. + } + var completionCallback = options?.CompletionCallback; owner ??= this; diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs index 773597845..937934592 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs @@ -456,6 +456,16 @@ public static class ActivityExecutionContextExtensions /// public static async ValueTask CompleteActivityAsync(this ActivityExecutionContext context, object? result = default) { + var outcomes = result as Outcomes; + + // If the activity is executing in the background, simply capture the result and return. + if (context.GetIsBackgroundExecution()) + { + if (outcomes != null) + context.SetBackgroundOutcomes(outcomes.Names); + return; + } + // If the activity is not running, do nothing. if (context.Status != ActivityStatus.Running) return; @@ -470,7 +480,7 @@ public static class ActivityExecutionContextExtensions context.Status = ActivityStatus.Completed; // Record the outcomes, if any. - if (result is Outcomes outcomes) + if (outcomes != null) context.JournalData["Outcomes"] = outcomes.Names; // Record the output, if any. diff --git a/src/modules/Elsa.Workflows.Core/Extensions/BackgroundActivityExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/BackgroundActivityExecutionContextExtensions.cs index c4cc0de38..72d84e745 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/BackgroundActivityExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/BackgroundActivityExecutionContextExtensions.cs @@ -5,6 +5,27 @@ namespace Elsa.Workflows; /// public static class BackgroundActivityExecutionContextExtensions { + /// + /// A key into the activity execution context's transient properties that indicates whether the current activity is being executed in the background. + /// + public static readonly object IsBackgroundExecution = new(); + + /// + /// Configures the activity execution context to execute the current activity in the background. + /// + public static void SetIsBackgroundExecution(this ActivityExecutionContext activityExecutionContext, bool value = true) + { + activityExecutionContext.TransientProperties[IsBackgroundExecution] = value; + } + + /// + /// Gets a value indicating whether the current activity is being executed in the background. + /// + public static bool GetIsBackgroundExecution(this ActivityExecutionContext activityExecutionContext) + { + return activityExecutionContext.TransientProperties.ContainsKey(IsBackgroundExecution); + } + /// /// Sets the background outcomes. /// diff --git a/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs b/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs index c10cf90d1..6f440c8f5 100644 --- a/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs +++ b/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs @@ -14,11 +14,6 @@ namespace Elsa.Workflows.Runtime.Middleware.Activities; /// public class BackgroundActivityInvokerMiddleware : DefaultActivityInvokerMiddleware { - /// - /// A key into the activity execution context's transient properties that indicates whether the current activity is being executed in the background. - /// - public static readonly object IsBackgroundExecution = new(); - internal static string GetBackgroundActivityOutputKey(string activityNodeId) => $"__BackgroundActivityOutput:{activityNodeId}"; internal static string GetBackgroundActivityOutcomesKey(string activityNodeId) => $"__BackgroundActivityOutcomes:{activityNodeId}"; internal static string GetBackgroundActivityJournalDataKey(string activityNodeId) => $"__BackgroundActivityJournalData:{activityNodeId}"; @@ -66,7 +61,7 @@ public class BackgroundActivityInvokerMiddleware : DefaultActivityInvokerMiddlew && (kind is ActivityKind.Job || (kind == ActivityKind.Task && activity.GetRunAsynchronously())); } - private static bool GetIsBackgroundExecution(ActivityExecutionContext context) => context.TransientProperties.ContainsKey(IsBackgroundExecution); + private static bool GetIsBackgroundExecution(ActivityExecutionContext context) => context.TransientProperties.ContainsKey(BackgroundActivityExecutionContextExtensions.IsBackgroundExecution); /// /// Schedules the current activity for execution in the background. diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultBackgroundActivityInvoker.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultBackgroundActivityInvoker.cs index 379a10e24..90b7557a2 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultBackgroundActivityInvoker.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultBackgroundActivityInvoker.cs @@ -77,7 +77,7 @@ public class DefaultBackgroundActivityInvoker : IBackgroundActivityInvoker await _variablePersistenceManager.LoadVariablesAsync(workflowExecutionContext); // Mark the activity as being invoked from a background worker. - activityExecutionContext.TransientProperties[BackgroundActivityInvokerMiddleware.IsBackgroundExecution] = true; + activityExecutionContext.SetIsBackgroundExecution(); // Invoke the activity. await _activityInvoker.InvokeAsync(activityExecutionContext);