diff --git a/src/common/Elsa.Features/Services/IFeature.cs b/src/common/Elsa.Features/Services/IFeature.cs
index 78123e7e5..71c6ed504 100644
--- a/src/common/Elsa.Features/Services/IFeature.cs
+++ b/src/common/Elsa.Features/Services/IFeature.cs
@@ -1,8 +1,11 @@
+using JetBrains.Annotations;
+
namespace Elsa.Features.Services;
///
/// Represents a feature.
///
+[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.Members)]
public interface IFeature
{
///
diff --git a/src/modules/Elsa.Http/Abstractions/HttpCorrelationIdSelectorBase.cs b/src/modules/Elsa.Http/Abstractions/HttpCorrelationIdSelectorBase.cs
new file mode 100644
index 000000000..7403aef5c
--- /dev/null
+++ b/src/modules/Elsa.Http/Abstractions/HttpCorrelationIdSelectorBase.cs
@@ -0,0 +1,29 @@
+using Elsa.Http.Contracts;
+using Microsoft.AspNetCore.Http;
+
+namespace Elsa.Http.Abstractions;
+
+///
+/// Provides a base class for implementing .
+///
+public abstract class HttpCorrelationIdSelectorBase : IHttpCorrelationIdSelector
+{
+ ///
+ public virtual double Priority => 0;
+
+ ///
+ /// Override this method to return the correlation ID for the specified HTTP context, or null if no correlation ID could be found.
+ ///
+ protected virtual ValueTask GetCorrelationIdAsync(HttpContext httpContext, CancellationToken cancellationToken = default)
+ {
+ var correlationId = GetCorrelationId(httpContext);
+ return new(correlationId);
+ }
+
+ ///
+ /// Override this method to return the correlation ID for the specified HTTP context, or null if no correlation ID could be found.
+ ///
+ protected virtual string? GetCorrelationId(HttpContext httpContext) => null;
+
+ ValueTask IHttpCorrelationIdSelector.GetCorrelationIdAsync(HttpContext httpContext, CancellationToken cancellationToken) => GetCorrelationIdAsync(httpContext, cancellationToken);
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Http/Abstractions/HttpWorkflowInstanceIdSelectorBase.cs b/src/modules/Elsa.Http/Abstractions/HttpWorkflowInstanceIdSelectorBase.cs
new file mode 100644
index 000000000..6d70522fa
--- /dev/null
+++ b/src/modules/Elsa.Http/Abstractions/HttpWorkflowInstanceIdSelectorBase.cs
@@ -0,0 +1,29 @@
+using Elsa.Http.Contracts;
+using Microsoft.AspNetCore.Http;
+
+namespace Elsa.Http.Abstractions;
+
+///
+/// Provides a base class for implementing .
+///
+public abstract class HttpWorkflowInstanceIdSelectorBase : IHttpWorkflowInstanceIdSelector
+{
+ ///
+ public virtual double Priority => 0;
+
+ ///
+ /// Override this method to return the workflow instance ID for the specified HTTP context, or null if no workflow instance ID could be found.
+ ///
+ protected virtual ValueTask GetWorkflowInstanceIdAsync(HttpContext httpContext, CancellationToken cancellationToken = default)
+ {
+ var workflowInstanceId = GetWorkflowInstanceId(httpContext);
+ return new(workflowInstanceId);
+ }
+
+ ///
+ /// Override this method to return the workflow instance ID for the specified HTTP context, or null if no workflow instance ID could be found.
+ ///
+ protected virtual string? GetWorkflowInstanceId(HttpContext httpContext) => null;
+
+ ValueTask IHttpWorkflowInstanceIdSelector.GetWorkflowInstanceIdAsync(HttpContext httpContext, CancellationToken cancellationToken) => GetWorkflowInstanceIdAsync(httpContext, cancellationToken);
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Http/Contracts/IHttpCorrelationIdSelector.cs b/src/modules/Elsa.Http/Contracts/IHttpCorrelationIdSelector.cs
new file mode 100644
index 000000000..d663acd68
--- /dev/null
+++ b/src/modules/Elsa.Http/Contracts/IHttpCorrelationIdSelector.cs
@@ -0,0 +1,19 @@
+using Microsoft.AspNetCore.Http;
+
+namespace Elsa.Http.Contracts;
+
+///
+/// Provides a way to select the correlation ID for a request.
+///
+public interface IHttpCorrelationIdSelector
+{
+ ///
+ /// The priority of this selector. The selector with the highest priority will be used.
+ ///
+ double Priority { get; }
+
+ ///
+ /// Returns the correlation ID for the specified HTTP context, or null if no correlation ID could be found.
+ ///
+ ValueTask GetCorrelationIdAsync(HttpContext httpContext, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Http/Contracts/IHttpWorkflowInstanceIdSelector.cs b/src/modules/Elsa.Http/Contracts/IHttpWorkflowInstanceIdSelector.cs
new file mode 100644
index 000000000..28be3880f
--- /dev/null
+++ b/src/modules/Elsa.Http/Contracts/IHttpWorkflowInstanceIdSelector.cs
@@ -0,0 +1,19 @@
+using Microsoft.AspNetCore.Http;
+
+namespace Elsa.Http.Contracts;
+
+///
+/// Provides a way to select the workflow instance ID for a request.
+///
+public interface IHttpWorkflowInstanceIdSelector
+{
+ ///
+ /// The priority of this selector. The selector with the highest priority will be used.
+ ///
+ double Priority { get; }
+
+ ///
+ /// Returns the workflow instance ID for the specified HTTP context, or null if no workflow instance ID could be found.
+ ///
+ ValueTask GetWorkflowInstanceIdAsync(HttpContext httpContext, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Http/Elsa.Http.csproj b/src/modules/Elsa.Http/Elsa.Http.csproj
index 1ceceea66..d0cbfa09e 100644
--- a/src/modules/Elsa.Http/Elsa.Http.csproj
+++ b/src/modules/Elsa.Http/Elsa.Http.csproj
@@ -29,4 +29,6 @@
+
+
diff --git a/src/modules/Elsa.Http/Extensions/ServiceCollectionExtensions.cs b/src/modules/Elsa.Http/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 000000000..3218b25f0
--- /dev/null
+++ b/src/modules/Elsa.Http/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,31 @@
+using Elsa.Http.Contracts;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+///
+/// Contains extension methods for the interface.
+///
+[PublicAPI]
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// Adds a implementation to the service collection.
+ ///
+ public static IServiceCollection AddHttpCorrelationIdSelector(this IServiceCollection services) where T : class, IHttpCorrelationIdSelector
+ {
+ services.AddSingleton();
+ return services;
+ }
+
+ ///
+ /// Adds a implementation to the service collection.
+ ///
+ public static IServiceCollection AddHttpCorrelationIdSelector(this IServiceCollection services, Func factory)
+ {
+ services.AddSingleton(factory);
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Http/Features/HttpFeature.cs b/src/modules/Elsa.Http/Features/HttpFeature.cs
index e374ccc65..758be5462 100644
--- a/src/modules/Elsa.Http/Features/HttpFeature.cs
+++ b/src/modules/Elsa.Http/Features/HttpFeature.cs
@@ -12,6 +12,7 @@ using Elsa.Http.Options;
using Elsa.Http.Parsers;
using Elsa.Http.PortResolvers;
using Elsa.Http.Providers;
+using Elsa.Http.Selectors;
using Elsa.Http.Services;
using Elsa.JavaScript.Features;
using Elsa.Liquid.Features;
@@ -62,6 +63,24 @@ public class HttpFeature : FeatureBase
///
public Action HttpClientBuilder { get; set; } = _ => { };
+ ///
+ /// A list of types to register with the service collection.
+ ///
+ public ICollection HttpCorrelationIdSelectorTypes { get; } = new List
+ {
+ typeof(HeaderHttpCorrelationIdSelector),
+ typeof(QueryStringHttpCorrelationIdSelector)
+ };
+
+ ///
+ /// A list of types to register with the service collection.
+ ///
+ public ICollection HttpWorkflowInstanceIdSelectorTypes { get; } = new List
+ {
+ typeof(HeaderHttpWorkflowInstanceIdSelector),
+ typeof(QueryStringHttpWorkflowInstanceIdSelector)
+ };
+
///
public override void Configure()
{
@@ -78,7 +97,7 @@ public class HttpFeature : FeatureBase
management.AddActivitiesFrom();
});
-
+
Services.AddRequestHandler();
}
@@ -141,5 +160,12 @@ public class HttpFeature : FeatureBase
// AuthenticationBasedHttpEndpointAuthorizationHandler requires Authorization services.
// We could consider creating a separate module for installing authorization services.
.AddAuthorization();
+
+ // Add selectors.
+ foreach (var httpCorrelationIdSelectorType in HttpCorrelationIdSelectorTypes)
+ Services.AddSingleton(typeof(IHttpCorrelationIdSelector), httpCorrelationIdSelectorType);
+
+ foreach (var httpWorkflowInstanceIdSelectorType in HttpWorkflowInstanceIdSelectorTypes)
+ Services.AddSingleton(typeof(IHttpWorkflowInstanceIdSelector), httpWorkflowInstanceIdSelectorType);
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Http/Middleware/WorkflowsMiddleware.cs b/src/modules/Elsa.Http/Middleware/WorkflowsMiddleware.cs
index 0b1302341..b0d04a92f 100644
--- a/src/modules/Elsa.Http/Middleware/WorkflowsMiddleware.cs
+++ b/src/modules/Elsa.Http/Middleware/WorkflowsMiddleware.cs
@@ -27,6 +27,8 @@ public class WorkflowsMiddleware
private readonly IWorkflowRuntime _workflowRuntime;
private readonly IRouteMatcher _routeMatcher;
private readonly IRouteTable _routeTable;
+ private readonly IEnumerable _correlationIdSelectors;
+ private readonly IEnumerable _workflowInstanceIdSelectors;
private readonly IHttpBookmarkProcessor _httpBookmarkProcessor;
private readonly IHttpEndpointWorkflowFaultHandler _httpEndpointWorkflowFaultHandler;
private readonly IHttpEndpointAuthorizationHandler _httpEndpointAuthorizationHandler;
@@ -52,7 +54,9 @@ public class WorkflowsMiddleware
IBookmarkHasher hasher,
IBookmarkPayloadSerializer serializer,
IRouteMatcher routeMatcher,
- IRouteTable routeTable)
+ IRouteTable routeTable,
+ IEnumerable correlationIdSelectors,
+ IEnumerable workflowInstanceIdSelectors)
{
_next = next;
_workflowRuntime = workflowRuntime;
@@ -66,6 +70,8 @@ public class WorkflowsMiddleware
_serializer = serializer;
_routeMatcher = routeMatcher;
_routeTable = routeTable;
+ _correlationIdSelectors = correlationIdSelectors;
+ _workflowInstanceIdSelectors = workflowInstanceIdSelectors;
}
///
@@ -97,14 +103,13 @@ public class WorkflowsMiddleware
[HttpEndpoint.RequestPathInputKey] = path
};
- // TODO: Get correlation ID from query string or header.
- var correlationId = default(string);
-
- var request = httpContext.Request;
- var method = request.Method!.ToLowerInvariant();
- var bookmarkPayload = new HttpEndpointBookmarkPayload(matchingPath, method);
- var triggerOptions = new TriggerWorkflowsRuntimeOptions(correlationId, default, default, input);
var cancellationToken = httpContext.RequestAborted;
+ var request = httpContext.Request;
+ var method = request.Method.ToLowerInvariant();
+ var correlationId = await GetCorrelationIdAsync(httpContext, httpContext.RequestAborted);
+ var workflowInstanceId = await GetWorkflowInstanceIdAsync(httpContext, httpContext.RequestAborted);
+ var bookmarkPayload = new HttpEndpointBookmarkPayload(matchingPath, method);
+ var triggerOptions = new TriggerWorkflowsRuntimeOptions(correlationId, workflowInstanceId, default, input);
var workflowsFilter = new WorkflowsFilter(_activityTypeName, bookmarkPayload, triggerOptions);
var workflowMatches = (await _workflowRuntime.FindWorkflowsAsync(workflowsFilter, cancellationToken)).ToList();
@@ -146,6 +151,36 @@ public class WorkflowsMiddleware
return routeTemplate;
}
+ private async Task GetCorrelationIdAsync(HttpContext httpContext, CancellationToken cancellationToken)
+ {
+ var correlationId = default(string);
+
+ foreach (var selector in _correlationIdSelectors.OrderByDescending(x => x.Priority))
+ {
+ correlationId = await selector.GetCorrelationIdAsync(httpContext, cancellationToken);
+
+ if (correlationId != null)
+ break;
+ }
+
+ return correlationId;
+ }
+
+ private async Task GetWorkflowInstanceIdAsync(HttpContext httpContext, CancellationToken cancellationToken)
+ {
+ var workflowInstanceId = default(string);
+
+ foreach (var selector in _workflowInstanceIdSelectors.OrderByDescending(x => x.Priority))
+ {
+ workflowInstanceId = await selector.GetWorkflowInstanceIdAsync(httpContext, cancellationToken);
+
+ if (workflowInstanceId != null)
+ break;
+ }
+
+ return workflowInstanceId;
+ }
+
private static async Task WriteResponseAsync(HttpContext httpContext, CancellationToken cancellationToken)
{
var response = httpContext.Response;
diff --git a/src/modules/Elsa.Http/Selectors/HeaderHttpCorrelationIdSelector.cs b/src/modules/Elsa.Http/Selectors/HeaderHttpCorrelationIdSelector.cs
new file mode 100644
index 000000000..0757f9ab3
--- /dev/null
+++ b/src/modules/Elsa.Http/Selectors/HeaderHttpCorrelationIdSelector.cs
@@ -0,0 +1,16 @@
+using Elsa.Http.Abstractions;
+using Microsoft.AspNetCore.Http;
+
+namespace Elsa.Http.Selectors;
+
+///
+/// Returns the correlation ID from the X-Correlation-ID header, if any.
+///
+public class HeaderHttpCorrelationIdSelector : HttpCorrelationIdSelectorBase
+{
+ ///
+ protected override string? GetCorrelationId(HttpContext httpContext)
+ {
+ return httpContext.Request.Headers["X-Correlation-ID"].FirstOrDefault();
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Http/Selectors/HeaderHttpWorkflowInstanceIdSelector.cs b/src/modules/Elsa.Http/Selectors/HeaderHttpWorkflowInstanceIdSelector.cs
new file mode 100644
index 000000000..7294d7081
--- /dev/null
+++ b/src/modules/Elsa.Http/Selectors/HeaderHttpWorkflowInstanceIdSelector.cs
@@ -0,0 +1,16 @@
+using Elsa.Http.Abstractions;
+using Microsoft.AspNetCore.Http;
+
+namespace Elsa.Http.Selectors;
+
+///
+/// Returns the workflow instance ID from the X-Workflow-Instance-ID header, if any.
+///
+public class HeaderHttpWorkflowInstanceIdSelector : HttpWorkflowInstanceIdSelectorBase
+{
+ ///
+ protected override string? GetWorkflowInstanceId(HttpContext httpContext)
+ {
+ return httpContext.Request.Headers["X-Workflow-Instance-ID"].FirstOrDefault();
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Http/Selectors/QueryStringHttpCorrelationIdSelector.cs b/src/modules/Elsa.Http/Selectors/QueryStringHttpCorrelationIdSelector.cs
new file mode 100644
index 000000000..8c632bbb4
--- /dev/null
+++ b/src/modules/Elsa.Http/Selectors/QueryStringHttpCorrelationIdSelector.cs
@@ -0,0 +1,16 @@
+using Elsa.Http.Abstractions;
+using Microsoft.AspNetCore.Http;
+
+namespace Elsa.Http.Selectors;
+
+///
+/// Returns the correlation ID from the correlationId query string parameter.
+///
+public class QueryStringHttpCorrelationIdSelector : HttpCorrelationIdSelectorBase
+{
+ ///
+ protected override string? GetCorrelationId(HttpContext httpContext)
+ {
+ return httpContext.Request.Query["correlationId"].FirstOrDefault();
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Http/Selectors/QueryStringHttpWorkflowInstanceIdSelector.cs b/src/modules/Elsa.Http/Selectors/QueryStringHttpWorkflowInstanceIdSelector.cs
new file mode 100644
index 000000000..32ade85cc
--- /dev/null
+++ b/src/modules/Elsa.Http/Selectors/QueryStringHttpWorkflowInstanceIdSelector.cs
@@ -0,0 +1,16 @@
+using Elsa.Http.Abstractions;
+using Microsoft.AspNetCore.Http;
+
+namespace Elsa.Http.Selectors;
+
+///
+/// Returns the workflow instance ID from the X-Workflow-Instance-ID header, if any.
+///
+public class QueryStringHttpWorkflowInstanceIdSelector : HttpWorkflowInstanceIdSelectorBase
+{
+ ///
+ protected override string? GetWorkflowInstanceId(HttpContext httpContext)
+ {
+ return httpContext.Request.Query["workflowInstanceId"].FirstOrDefault();
+ }
+}
\ No newline at end of file