Improve trigger API and implement HTTP correlation

This commit is contained in:
Sipke Schoorstra 2020-10-30 21:55:27 +01:00
parent b51aa6d7a6
commit 6577b0cd38
29 changed files with 356 additions and 34 deletions

View file

@ -106,6 +106,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.WhileLoopWorke
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.IfElseConsole", "src\samples\Elsa.Samples.IfElseConsole\Elsa.Samples.IfElseConsole.csproj", "{E508C1B6-916F-40F4-8731-58279B8F45B2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.CorrelationHttp", "src\samples\Elsa.Samples.CorrelationHttp\Elsa.Samples.CorrelationHttp.csproj", "{565E2F7D-1F5F-45EF-B130-BC09CB3465B1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -249,6 +251,10 @@ Global
{E508C1B6-916F-40F4-8731-58279B8F45B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E508C1B6-916F-40F4-8731-58279B8F45B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E508C1B6-916F-40F4-8731-58279B8F45B2}.Release|Any CPU.Build.0 = Release|Any CPU
{565E2F7D-1F5F-45EF-B130-BC09CB3465B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{565E2F7D-1F5F-45EF-B130-BC09CB3465B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{565E2F7D-1F5F-45EF-B130-BC09CB3465B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{565E2F7D-1F5F-45EF-B130-BC09CB3465B1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -298,6 +304,7 @@ Global
{6B83CCE1-D523-4E3F-84EB-8D79AAF6F7C2} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A}
{85A70E7C-A4AC-49BA-942B-635F974DE255} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A}
{E508C1B6-916F-40F4-8731-58279B8F45B2} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A}
{565E2F7D-1F5F-45EF-B130-BC09CB3465B1} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158}

View file

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -38,5 +39,23 @@ namespace Elsa.Activities.Http.Extensions
var absoluteUrl = $"{request.Scheme}://{request.Host}{relativePath}";
return new Uri(absoluteUrl, UriKind.Absolute);
}
public static bool TryGetCorrelationId(this HttpRequest request, out string? correlationId)
{
if (request.Query.ContainsKey("correlation"))
{
correlationId = request.Query["correlation"];
return true;
}
if (request.Headers.ContainsKey("X-Correlation-Id"))
{
correlationId = request.Headers["X-Correlation-Id"].ToString();
return true;
}
correlationId = null;
return false;
}
}
}

View file

@ -1,9 +1,14 @@
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Elsa.Activities.Http.Extensions;
using Elsa.Activities.Http.Triggers;
using Elsa.Extensions;
using Elsa.Services;
using Elsa.Triggers;
using Microsoft.AspNetCore.Http;
using Open.Linq.AsyncExtensions;
namespace Elsa.Activities.Http.Middleware
{
@ -16,22 +21,49 @@ namespace Elsa.Activities.Http.Middleware
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext, IWorkflowSelector workflowSelector, IWorkflowRunner workflowRunner)
public async Task InvokeAsync(HttpContext httpContext, IWorkflowSelector workflowSelector, IWorkflowRunner workflowRunner, IWorkflowInstanceManager workflowInstanceManager)
{
var path = httpContext.Request.Path;
var method = httpContext.Request.Method;
var cancellationToken = httpContext.RequestAborted;
httpContext.Request.TryGetCorrelationId(out var correlationId);
var results = await workflowSelector.SelectWorkflowsAsync<ReceiveHttpRequestTrigger>(
x => x.Path == path && x.Method == null || x.Method == method,
cancellationToken);
x => (x.Path == path) && (x.Method == null || x.Method == method) && (x.CorrelationId == null || x.CorrelationId == correlationId),
cancellationToken)
.ToList();
var result = results.FirstOrDefault();
if (result == null)
if (!results.Any())
{
await _next(httpContext);
return;
}
if (results.Count > 1)
{
httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await httpContext.Response.WriteAsync("Request matches multiple workflows.", cancellationToken);
return;
}
var result = results.Single();
if (result.WorkflowInstanceId == null)
{
await workflowRunner.RunWorkflowAsync(result.WorkflowBlueprint, result.ActivityId, cancellationToken: cancellationToken);
}
else
await workflowRunner.RunWorkflowAsync(result.WorkflowBlueprint, result.WorkflowInstance, result.ActivityId, cancellationToken: cancellationToken);
{
var workflowInstance = await workflowInstanceManager.GetByIdAsync(result.WorkflowInstanceId, cancellationToken);
if (workflowInstance == null)
{
httpContext.Response.StatusCode = 404;
return;
}
await workflowRunner.RunWorkflowAsync(result.WorkflowBlueprint, workflowInstance, result.ActivityId, cancellationToken: cancellationToken);
}
}
}
}

View file

@ -9,6 +9,7 @@ namespace Elsa.Activities.Http.Triggers
{
public PathString Path { get; set; }
public string? Method { get; set; }
public string? CorrelationId { get; set; }
}
public class ReceiveHttpRequestTriggerProvider : TriggerProvider<ReceiveHttpRequestTrigger, ReceiveHttpRequest>
@ -17,7 +18,8 @@ namespace Elsa.Activities.Http.Triggers
new ReceiveHttpRequestTrigger
{
Path = await context.Activity.GetPropertyValueAsync(x => x.Path, cancellationToken),
Method = await context.Activity.GetPropertyValueAsync(x => x.Method, cancellationToken)
Method = await context.Activity.GetPropertyValueAsync(x => x.Method, cancellationToken),
CorrelationId = context.ActivityExecutionContext.WorkflowExecutionContext.CorrelationId
};
}
}

View file

@ -0,0 +1,15 @@
using Elsa.Models;
using MediatR;
namespace Elsa.Events
{
public class WorkflowInstanceDeleted : INotification
{
public WorkflowInstanceDeleted(WorkflowInstance workflowInstance)
{
WorkflowInstance = workflowInstance;
}
public WorkflowInstance WorkflowInstance { get; }
}
}

View file

@ -0,0 +1,15 @@
using Elsa.Models;
using MediatR;
namespace Elsa.Events
{
public class WorkflowInstanceSaved : INotification
{
public WorkflowInstanceSaved(WorkflowInstance workflowInstance)
{
WorkflowInstance = workflowInstance;
}
public WorkflowInstance WorkflowInstance { get; }
}
}

View file

@ -22,7 +22,19 @@ namespace Elsa.Models
[JsonExtensionData] public JObject Data { get; set; }
public JToken? Get(string name) => Has(name) ? Data[name] : default;
public T Get<T>(string name) => Has(name) ? Data.Value<T>(name) : default!;
public T Get<T>(string name)
{
if (!Has(name))
return default!;
var value = Get(name);
if (value == null)
return default!;
return value.ToObject<T>()!;
}
public Variables Set(string name, JToken value)
{

View file

@ -40,6 +40,7 @@ namespace Elsa.Services.Models
public void SetVariable(string name, object? value) => WorkflowExecutionContext.SetVariable(name, value);
public object? GetVariable(string name) => WorkflowExecutionContext.GetVariable(name);
public T GetVariable<T>(string name) => WorkflowExecutionContext.GetVariable<T>(name);
public T GetVariable<T>() => GetVariable<T>(typeof(T).Name);
public T GetService<T>() => WorkflowExecutionContext.ServiceProvider.GetService<T>();
public async ValueTask SetActivityPropertiesAsync(

View file

@ -56,7 +56,13 @@ namespace Elsa.Services.Models
public void PostScheduleActivity(ScheduledActivity activity) => WorkflowInstance.PostScheduledActivities.Push(activity);
public ScheduledActivity PopScheduledActivity() => WorkflowInstance.ScheduledActivities.Pop();
public ScheduledActivity PeekScheduledActivity() => WorkflowInstance.ScheduledActivities.Peek();
public string? CorrelationId { get; set; }
public string? CorrelationId
{
get => WorkflowInstance.CorrelationId;
set => WorkflowInstance.CorrelationId = value;
}
public bool DeleteCompletedInstances { get; set; }
public ICollection<ExecutionLogEntry> ExecutionLog => WorkflowInstance.ExecutionLog;
public WorkflowStatus Status => WorkflowInstance.Status;

View file

@ -1,12 +1,11 @@
using Elsa.Models;
using Elsa.Services.Models;
using Elsa.Services.Models;
namespace Elsa.Triggers
{
public class TriggerDescriptor
{
public IWorkflowBlueprint WorkflowBlueprint { get; set; } = default!;
public WorkflowInstance WorkflowInstance { get; set; } = default!;
public string? WorkflowInstanceId { get; set; }
public string ActivityId { get; set; } = default!;
public string ActivityType { get; set; } = default!;
public ITrigger Trigger { get; set; } = default!;

View file

@ -5,16 +5,16 @@ namespace Elsa.Triggers
{
public class WorkflowSelectorResult
{
public WorkflowSelectorResult(IWorkflowBlueprint workflowBlueprint, WorkflowInstance workflowInstance, string activityId, ITrigger trigger)
public WorkflowSelectorResult(IWorkflowBlueprint workflowBlueprint, string? workflowInstanceId, string activityId, ITrigger trigger)
{
WorkflowBlueprint = workflowBlueprint;
WorkflowInstance = workflowInstance;
WorkflowInstanceId = workflowInstanceId;
ActivityId = activityId;
Trigger = trigger;
}
public IWorkflowBlueprint WorkflowBlueprint { get; }
public WorkflowInstance WorkflowInstance { get; }
public string? WorkflowInstanceId { get; }
public string ActivityId { get; }
public ITrigger Trigger { get; }
}

View file

@ -18,6 +18,6 @@ namespace Elsa.Activities.Primitives
[ActivityProperty(Hint = "An expression that evaluates to the value to store as the correlation ID.")]
public string Value { get; set; } = default!;
protected override IActivityExecutionResult OnExecute() => new CorrelateResult(Value);
protected override IActivityExecutionResult OnExecute() => Combine(Done(Value), new CorrelateResult(Value));
}
}

View file

@ -0,0 +1,19 @@
using System;
using System.Threading.Tasks;
using Elsa.Activities.Primitives;
using Elsa.Builders;
using Elsa.Services.Models;
// ReSharper disable once CheckNamespace
namespace Elsa.Activities.ControlFlow
{
public static class CorrelateBuilderExtensions
{
public static IOutcomeBuilder Correlate(this IBuilder builder, Action<ISetupActivity<Correlate>> setup) => builder.Then(setup).When(OutcomeNames.Done);
public static IOutcomeBuilder Correlate(this IBuilder builder, Func<ActivityExecutionContext, ValueTask<string>> value) => builder.Correlate(activity => activity.Set(x => x.Value, value));
public static IOutcomeBuilder Correlate(this IBuilder builder, Func<ActivityExecutionContext, string> value) => builder.Correlate(activity => activity.Set(x => x.Value, value));
public static IOutcomeBuilder Correlate(this IBuilder builder, Func<ValueTask<string>> value) => builder.Correlate(activity => activity.Set(x => x.Value, value));
public static IOutcomeBuilder Correlate(this IBuilder builder, Func<string> value) => builder.Correlate(activity => activity.Set(x => x.Value, value));
public static IOutcomeBuilder Correlate(this IBuilder builder, string value) => builder.Correlate(activity => activity.Set(x => x.Value, value));
}
}

View file

@ -10,8 +10,7 @@ namespace Elsa.Activities.Primitives
DisplayName = "Set Variable",
Description = "Set variable on the workflow.",
Category = "Primitives",
RuntimeDescription =
"x => !!x.state.variableName ? `<strong>${x.state.variableName}</strong> = <strong>${x.state.valueExpression.expression}</strong><br/>${x.state.valueExpression.syntax}` : x.definition.description",
RuntimeDescription = "x => !!x.state.variableName ? `<strong>${x.state.variableName}</strong> = <strong>${x.state.valueExpression.expression}</strong><br/>${x.state.valueExpression.syntax}` : x.definition.description",
Outcomes = new[] { OutcomeNames.Done }
)]
public class SetVariable : Activity

View file

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Elsa.Activities.Primitives;
using Elsa.Builders;
using Elsa.Services.Models;
@ -10,12 +11,24 @@ namespace Elsa.Activities.ControlFlow
{
public static IOutcomeBuilder SetVariable(this IBuilder builder, Action<ISetupActivity<SetVariable>> setup) => builder.Then(setup).When(OutcomeNames.Done);
public static IOutcomeBuilder SetVariable(this IBuilder builder, string name, Func<ActivityExecutionContext, ValueTask<object?>> value) =>
builder.SetVariable(
activity => activity
.Set(x => x.VariableName, name)
.Set(x => x.Value, value));
public static IOutcomeBuilder SetVariable(this IBuilder builder, string name, Func<ActivityExecutionContext, object?> value) =>
builder.SetVariable(
activity => activity
.Set(x => x.VariableName, name)
.Set(x => x.Value, value));
public static IOutcomeBuilder SetVariable(this IBuilder builder, string name, Func<ValueTask<object?>> value) =>
builder.SetVariable(
activity => activity
.Set(x => x.VariableName, name)
.Set(x => x.Value, value));
public static IOutcomeBuilder SetVariable(this IBuilder builder, string name, Func<object?> value) =>
builder.SetVariable(
activity => activity
@ -27,5 +40,11 @@ namespace Elsa.Activities.ControlFlow
activity => activity
.Set(x => x.VariableName, name)
.Set(x => x.Value, value));
public static IOutcomeBuilder SetVariable<T>(this IBuilder builder, Func<ActivityExecutionContext, ValueTask<T>> value) => builder.SetVariable(typeof(T).Name, async context => await value(context));
public static IOutcomeBuilder SetVariable<T>(this IBuilder builder, Func<ActivityExecutionContext, T> value) => builder.SetVariable(typeof(T).Name, context => value(context));
public static IOutcomeBuilder SetVariable<T>(this IBuilder builder, Func<ValueTask<T>> value) => builder.SetVariable(typeof(T).Name, async () => await value());
public static IOutcomeBuilder SetVariable<T>(this IBuilder builder, Func<T> value) => builder.SetVariable(typeof(T).Name, () => value());
public static IOutcomeBuilder SetVariable<T>(this IBuilder builder, T value) => builder.SetVariable(typeof(T).Name, value);
}
}

View file

@ -7,7 +7,6 @@ namespace Elsa.ActivityResults
public CorrelateResult(string correlationId) => CorrelationId = correlationId;
public string CorrelationId { get; }
protected override void Execute(ActivityExecutionContext activityExecutionContext) =>
activityExecutionContext.WorkflowExecutionContext.CorrelationId = CorrelationId;
protected override void Execute(ActivityExecutionContext activityExecutionContext) => activityExecutionContext.WorkflowExecutionContext.CorrelationId = CorrelationId;
}
}

View file

@ -47,9 +47,9 @@ namespace Elsa.Builders
return this;
}
public IActivityBuilder WithId(string id)
public IActivityBuilder WithId(string? id)
{
ActivityId = id;
ActivityId = id!;
return this;
}

View file

@ -1,15 +1,29 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Events;
using Elsa.Models;
using Elsa.Services;
using Elsa.Triggers;
using MediatR;
namespace Elsa.Handlers
{
public class UpdateTriggers : INotificationHandler<WorkflowExecuted>
public class UpdateTriggers : INotificationHandler<WorkflowInstanceSaved>
{
private readonly IWorkflowSelector _workflowSelector;
public UpdateTriggers(IWorkflowSelector workflowSelector) => _workflowSelector = workflowSelector;
public async Task Handle(WorkflowExecuted notification, CancellationToken cancellationToken) => await _workflowSelector.UpdateTriggersAsync(notification.WorkflowExecutionContext.WorkflowBlueprint, cancellationToken);
private readonly IWorkflowRegistry _workflowRegistry;
public UpdateTriggers(IWorkflowSelector workflowSelector, IWorkflowRegistry workflowRegistry)
{
_workflowSelector = workflowSelector;
_workflowRegistry = workflowRegistry;
}
public async Task Handle(WorkflowInstanceSaved notification, CancellationToken cancellationToken)
{
var workflowInstance = notification.WorkflowInstance;
var workflowBlueprint = await _workflowRegistry.GetWorkflowAsync(workflowInstance.WorkflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken);
await _workflowSelector.UpdateTriggersAsync(workflowBlueprint!, cancellationToken);
}
}
}

View file

@ -3,7 +3,9 @@ using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Data;
using Elsa.Events;
using Elsa.Models;
using MediatR;
using YesSql;
using YesSql.Indexes;
@ -12,22 +14,26 @@ namespace Elsa.Services
public class WorkflowInstanceManager : IWorkflowInstanceManager
{
private readonly ISession _session;
private readonly IMediator _mediator;
public WorkflowInstanceManager(ISession session)
public WorkflowInstanceManager(ISession session, IMediator mediator)
{
_session = session;
_mediator = mediator;
}
public async ValueTask SaveAsync(WorkflowInstance workflowInstance, CancellationToken cancellationToken = default)
{
_session.Save(workflowInstance, CollectionNames.WorkflowInstances);
await _session.CommitAsync();
await _mediator.Publish(new WorkflowInstanceSaved(workflowInstance), cancellationToken);
}
public async ValueTask DeleteAsync(WorkflowInstance workflowInstance, CancellationToken cancellationToken = default)
{
_session.Delete(workflowInstance, CollectionNames.WorkflowInstances);
await _session.CommitAsync();
await _mediator.Publish(new WorkflowInstanceDeleted(workflowInstance), cancellationToken);
}
public IQuery<WorkflowInstance> Query() => _session.Query<WorkflowInstance>(CollectionNames.WorkflowInstances);

View file

@ -211,12 +211,13 @@ namespace Elsa.Services
private async ValueTask RunAsync(WorkflowExecutionContext workflowExecutionContext, ActivityOperation activityOperation, CancellationToken cancellationToken = default)
{
using var scope = _serviceProvider.CreateScope();
var serviceProvider = scope.ServiceProvider;
while (workflowExecutionContext.HasScheduledActivities)
{
var scheduledActivity = workflowExecutionContext.PopScheduledActivity();
var currentActivityId = scheduledActivity.ActivityId;
var activityBlueprint = workflowExecutionContext.WorkflowBlueprint.GetActivity(currentActivityId)!;
var activityExecutionContext = new ActivityExecutionContext(workflowExecutionContext, scope.ServiceProvider, activityBlueprint, scheduledActivity.Input);
var activityExecutionContext = new ActivityExecutionContext(workflowExecutionContext, serviceProvider, activityBlueprint, scheduledActivity.Input);
var activity = await activityBlueprint.CreateActivityAsync(activityExecutionContext, cancellationToken);
var result = await activityOperation(activityExecutionContext, activity, cancellationToken);
await _mediator.Publish(new ActivityExecuting(activityExecutionContext), cancellationToken);

View file

@ -95,8 +95,8 @@ namespace Elsa.Services
foreach (var result in results)
{
if (result.WorkflowInstance.Id > 0)
await ScheduleWorkflowInstanceAsync(result.WorkflowInstance.WorkflowInstanceId, result.ActivityId, input, cancellationToken);
if (result.WorkflowInstanceId != null)
await ScheduleWorkflowInstanceAsync(result.WorkflowInstanceId, result.ActivityId, input, cancellationToken);
else
await ScheduleWorkflowAsync(result.WorkflowBlueprint, result.WorkflowBlueprint.GetActivity(result.ActivityId)!, input, correlationId, cancellationToken);

View file

@ -74,7 +74,7 @@ namespace Elsa.Triggers
where descriptor.Trigger == trigger
select (entry.Key, descriptor);
foreach (var (key, descriptor) in query.ToList())
foreach (var (key, descriptor) in query.ToList())
_descriptors[key].Remove(descriptor);
_memoryCache.Set(CacheKey, _descriptors);
@ -94,10 +94,10 @@ namespace Elsa.Triggers
return from descriptor in descriptors
let workflow = descriptor.WorkflowBlueprint
where workflow.IsPublished && workflow.IsEnabled
let workflowInstance = descriptor.WorkflowInstance
let workflowInstanceId = descriptor.WorkflowInstanceId
let trigger = descriptor.Trigger
where trigger.GetType() == triggerType && evaluate(trigger)
select new WorkflowSelectorResult(workflow, workflowInstance, descriptor.ActivityId, trigger);
select new WorkflowSelectorResult(workflow, workflowInstanceId, descriptor.ActivityId, trigger);
}
private async Task<IDictionary<string, ICollection<TriggerDescriptor>>> BuildDescriptorsAsync(CancellationToken cancellationToken)
@ -171,7 +171,7 @@ namespace Elsa.Triggers
ActivityId = blockingActivity.Id,
ActivityType = blockingActivity.Type,
WorkflowBlueprint = workflowBlueprint,
WorkflowInstance = workflowInstance,
WorkflowInstanceId = workflowInstance.Id == 0 ? default : workflowInstance.WorkflowInstanceId,
Trigger = trigger,
};

View file

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\core\Elsa\Elsa.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,8 @@
namespace Elsa.Samples.CorrelationHttp.Models
{
public class Registration
{
public string Name { get; set; }
public string Email { get; set; }
}
}

View file

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace Elsa.Samples.CorrelationHttp
{
public class Program
{
public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
}

View file

@ -0,0 +1,12 @@
{
"profiles": {
"Elsa.Samples.LongRunningHttp": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:8201"
}
}
}

View file

@ -0,0 +1,29 @@
using System.Data;
using Elsa.Samples.CorrelationHttp.Workflows;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using YesSql.Provider.Sqlite;
namespace Elsa.Samples.CorrelationHttp
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddElsa(option => option.UsePersistence(db => db.UseSqLite("Data Source=elsa.db;Cache=Shared", IsolationLevel.ReadUncommitted)))
.AddHttpActivities()
.AddConsoleActivities()
.AddWorkflow<RegistrationWorkflow>();
}
public void Configure(IApplicationBuilder app)
{
// Add HTTP activities middleware.
app.UseHttpActivities();
// Show welcome page if no routes matched any endpoints.
app.UseWelcomePage();
}
}
}

View file

@ -0,0 +1,55 @@
using System.Net;
using Elsa.Activities.ControlFlow;
using Elsa.Activities.Http;
using Elsa.Activities.Http.Models;
using Elsa.Builders;
using Elsa.Samples.CorrelationHttp.Models;
using Microsoft.AspNetCore.Http;
namespace Elsa.Samples.CorrelationHttp.Workflows
{
/// <summary>
/// Triggered when POST HTTP requests are made to /register and continued when a subsequent GET HTTP request is made to /confirm to confirm the registration (see workflows.http).
/// </summary>
public class RegistrationWorkflow : IWorkflow
{
public void Build(IWorkflowBuilder workflow)
{
workflow
// Configure a Receive HTTP Request trigger that executes on incoming HTTP POST requests.
.ReceiveHttpRequest(activity => activity.WithPath("/register").WithMethod(HttpMethods.Post).WithTargetType<Registration>())
// Store the registration as a workflow variable for easier access.
.SetVariable(context => (Registration)((HttpRequestModel)(context.Input))?.Body)
// Correlate the workflow by email address.
.Correlate(context => context.GetVariable<Registration>().Email)
// Write an HTTP response with a hyperlink to continue the workflow (notice the presence of the "correlation" query string parameter).
.WriteHttpResponse(activity => activity
.WithStatusCode(HttpStatusCode.OK)
.WithContentType("text/html")
.WithContent(context =>
{
var registration = context.GetVariable<Registration>();
return $"Welcome onboard, {registration.Name}! Please <a href=\"http://localhost:8201/confirm?correlation={registration.Email}\">confirm your registration</a>";
}))
// Configure another Receive HTTP Request trigger that executes on incoming HTTP GET requests.
// This will cause the workflow to become suspended and executed once the request comes in.
.ReceiveHttpRequest(activity => activity.WithPath("/confirm"))
// Write an HTTP response with a thank-you note.
// Notice that the correct workflow instance is resumed base on the incoming correlation ID.
.WriteHttpResponse(activity => activity
.WithStatusCode(HttpStatusCode.OK)
.WithContentType("text/html")
.WithContent(
context =>
{
var registration = context.GetVariable<Registration>();
return $"Thanks for confirming your registration, {registration.Name}!";
}));
}
}
}

View file

@ -0,0 +1,28 @@
# Register John.
POST http://localhost:8201/register
Content-Type: application/json
{
"name": "John von Neumann",
"email": "john@gmail.com"
}
###
# Register Julia.
POST http://localhost:8201/register
Content-Type: application/json
{
"name": "Julia Berger",
"email": "julia@gmail.com"
}
###
# Confirm John's registration
GET http://localhost:8201/confirm?correlation=john@gmail.com
Content-Type: application/json
###
# Confirm Julia's registration
GET http://localhost:8201/confirm?correlation=julia@gmail.com
Content-Type: application/json