Implement HTTP correlation ID selectors (#4349)
This commit is contained in:
parent
46c9a76259
commit
1e7f954311
|
|
@ -1,8 +1,11 @@
|
|||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Features.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a feature.
|
||||
/// </summary>
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.Members)]
|
||||
public interface IFeature
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
using Elsa.Http.Contracts;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Http.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a base class for implementing <see cref="IHttpCorrelationIdSelector"/>.
|
||||
/// </summary>
|
||||
public abstract class HttpCorrelationIdSelectorBase : IHttpCorrelationIdSelector
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public virtual double Priority => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to return the correlation ID for the specified HTTP context, or <c>null</c> if no correlation ID could be found.
|
||||
/// </summary>
|
||||
protected virtual ValueTask<string?> GetCorrelationIdAsync(HttpContext httpContext, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var correlationId = GetCorrelationId(httpContext);
|
||||
return new(correlationId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to return the correlation ID for the specified HTTP context, or <c>null</c> if no correlation ID could be found.
|
||||
/// </summary>
|
||||
protected virtual string? GetCorrelationId(HttpContext httpContext) => null;
|
||||
|
||||
ValueTask<string?> IHttpCorrelationIdSelector.GetCorrelationIdAsync(HttpContext httpContext, CancellationToken cancellationToken) => GetCorrelationIdAsync(httpContext, cancellationToken);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Elsa.Http.Contracts;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Http.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a base class for implementing <see cref="IHttpCorrelationIdSelector"/>.
|
||||
/// </summary>
|
||||
public abstract class HttpWorkflowInstanceIdSelectorBase : IHttpWorkflowInstanceIdSelector
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public virtual double Priority => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to return the workflow instance ID for the specified HTTP context, or <c>null</c> if no workflow instance ID could be found.
|
||||
/// </summary>
|
||||
protected virtual ValueTask<string?> GetWorkflowInstanceIdAsync(HttpContext httpContext, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var workflowInstanceId = GetWorkflowInstanceId(httpContext);
|
||||
return new(workflowInstanceId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to return the workflow instance ID for the specified HTTP context, or <c>null</c> if no workflow instance ID could be found.
|
||||
/// </summary>
|
||||
protected virtual string? GetWorkflowInstanceId(HttpContext httpContext) => null;
|
||||
|
||||
ValueTask<string?> IHttpWorkflowInstanceIdSelector.GetWorkflowInstanceIdAsync(HttpContext httpContext, CancellationToken cancellationToken) => GetWorkflowInstanceIdAsync(httpContext, cancellationToken);
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Http.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a way to select the correlation ID for a request.
|
||||
/// </summary>
|
||||
public interface IHttpCorrelationIdSelector
|
||||
{
|
||||
/// <summary>
|
||||
/// The priority of this selector. The selector with the highest priority will be used.
|
||||
/// </summary>
|
||||
double Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the correlation ID for the specified HTTP context, or <c>null</c> if no correlation ID could be found.
|
||||
/// </summary>
|
||||
ValueTask<string?> GetCorrelationIdAsync(HttpContext httpContext, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Http.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a way to select the workflow instance ID for a request.
|
||||
/// </summary>
|
||||
public interface IHttpWorkflowInstanceIdSelector
|
||||
{
|
||||
/// <summary>
|
||||
/// The priority of this selector. The selector with the highest priority will be used.
|
||||
/// </summary>
|
||||
double Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the workflow instance ID for the specified HTTP context, or <c>null</c> if no workflow instance ID could be found.
|
||||
/// </summary>
|
||||
ValueTask<string?> GetWorkflowInstanceIdAsync(HttpContext httpContext, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -29,4 +29,6 @@
|
|||
<ProjectReference Include="..\Elsa.JavaScript\Elsa.JavaScript.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
using Elsa.Http.Contracts;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="IServiceCollection"/> interface.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a <see cref="IHttpCorrelationIdSelector"/> implementation to the service collection.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddHttpCorrelationIdSelector<T>(this IServiceCollection services) where T : class, IHttpCorrelationIdSelector
|
||||
{
|
||||
services.AddSingleton<IHttpCorrelationIdSelector, T>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="IHttpCorrelationIdSelector"/> implementation to the service collection.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddHttpCorrelationIdSelector(this IServiceCollection services, Func<IServiceProvider, IHttpCorrelationIdSelector> factory)
|
||||
{
|
||||
services.AddSingleton(factory);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|||
/// </summary>
|
||||
public Action<IHttpClientBuilder> HttpClientBuilder { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// A list of <see cref="IHttpCorrelationIdSelector"/> types to register with the service collection.
|
||||
/// </summary>
|
||||
public ICollection<Type> HttpCorrelationIdSelectorTypes { get; } = new List<Type>
|
||||
{
|
||||
typeof(HeaderHttpCorrelationIdSelector),
|
||||
typeof(QueryStringHttpCorrelationIdSelector)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A list of <see cref="IHttpWorkflowInstanceIdSelector"/> types to register with the service collection.
|
||||
/// </summary>
|
||||
public ICollection<Type> HttpWorkflowInstanceIdSelectorTypes { get; } = new List<Type>
|
||||
{
|
||||
typeof(HeaderHttpWorkflowInstanceIdSelector),
|
||||
typeof(QueryStringHttpWorkflowInstanceIdSelector)
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
|
|
@ -78,7 +97,7 @@ public class HttpFeature : FeatureBase
|
|||
|
||||
management.AddActivitiesFrom<HttpFeature>();
|
||||
});
|
||||
|
||||
|
||||
Services.AddRequestHandler<ValidateWorkflowRequestHandler, ValidateWorkflowRequest, ValidateWorkflowResponse>();
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,8 @@ public class WorkflowsMiddleware
|
|||
private readonly IWorkflowRuntime _workflowRuntime;
|
||||
private readonly IRouteMatcher _routeMatcher;
|
||||
private readonly IRouteTable _routeTable;
|
||||
private readonly IEnumerable<IHttpCorrelationIdSelector> _correlationIdSelectors;
|
||||
private readonly IEnumerable<IHttpWorkflowInstanceIdSelector> _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<IHttpCorrelationIdSelector> correlationIdSelectors,
|
||||
IEnumerable<IHttpWorkflowInstanceIdSelector> workflowInstanceIdSelectors)
|
||||
{
|
||||
_next = next;
|
||||
_workflowRuntime = workflowRuntime;
|
||||
|
|
@ -66,6 +70,8 @@ public class WorkflowsMiddleware
|
|||
_serializer = serializer;
|
||||
_routeMatcher = routeMatcher;
|
||||
_routeTable = routeTable;
|
||||
_correlationIdSelectors = correlationIdSelectors;
|
||||
_workflowInstanceIdSelectors = workflowInstanceIdSelectors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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<string?> 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<string?> 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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
using Elsa.Http.Abstractions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Http.Selectors;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the correlation ID from the <c>X-Correlation-ID</c> header, if any.
|
||||
/// </summary>
|
||||
public class HeaderHttpCorrelationIdSelector : HttpCorrelationIdSelectorBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override string? GetCorrelationId(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Request.Headers["X-Correlation-ID"].FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Elsa.Http.Abstractions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Http.Selectors;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the workflow instance ID from the <c>X-Workflow-Instance-ID</c> header, if any.
|
||||
/// </summary>
|
||||
public class HeaderHttpWorkflowInstanceIdSelector : HttpWorkflowInstanceIdSelectorBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override string? GetWorkflowInstanceId(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Request.Headers["X-Workflow-Instance-ID"].FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Elsa.Http.Abstractions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Http.Selectors;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the correlation ID from the <c>correlationId</c> query string parameter.
|
||||
/// </summary>
|
||||
public class QueryStringHttpCorrelationIdSelector : HttpCorrelationIdSelectorBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override string? GetCorrelationId(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Request.Query["correlationId"].FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Elsa.Http.Abstractions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Http.Selectors;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the workflow instance ID from the <c>X-Workflow-Instance-ID</c> header, if any.
|
||||
/// </summary>
|
||||
public class QueryStringHttpWorkflowInstanceIdSelector : HttpWorkflowInstanceIdSelectorBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override string? GetWorkflowInstanceId(HttpContext httpContext)
|
||||
{
|
||||
return httpContext.Request.Query["workflowInstanceId"].FirstOrDefault();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue