From ce6509baff40b1d2333156cb95a93e7d3afff43a Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sat, 31 Oct 2020 22:19:58 +0100 Subject: [PATCH] Complete workflow context sample and fix NodaTime serialization --- .../ReceiveHttpRequestBuilderExtensions.cs | 3 ++ .../Extensions/HttpRequestExtensions.cs | 1 - .../Middleware/HttpRequestMiddleware.cs | 1 - .../Parsers/JsonHttpRequestBodyParser.cs | 11 ++++- .../Activities/CronEvent/CronEvent.cs | 2 - .../Extensions/ServiceCollectionExtensions.cs | 1 - .../Triggers/InstantEventTrigger.cs | 3 -- .../Builders/IActivityBuilder.cs | 1 + .../Extensions/ServiceCollectionExtensions.cs | 1 + .../Serialization/IContentSerializer.cs | 5 +- .../Models/WorkflowExecutionContext.cs | 1 - .../Services/WorkflowContextProvider.cs | 15 ++++++ .../Triggers/WorkflowSelectorResult.cs | 3 +- .../ControlFlow/Join/JoinBuilderExtensions.cs | 10 ---- .../ControlFlow/Join/JoinExtensions.cs | 14 ++++++ .../Elsa.Core/Builders/ActivityBuilder.cs | 15 ++++-- .../Extensions/WorkflowSchedulerExtensions.cs | 1 - .../Serialization/DefaultContentSerializer.cs | 15 +++--- .../Elsa.Core/Triggers/WorkflowSelector.cs | 7 ++- ...Elsa.Samples.ContextualWorkflowHttp.csproj | 1 + .../Indexes/DocumentIndex.cs | 22 +++++++++ .../Migrations.cs | 15 ++++++ .../Models/Document.cs | 7 +-- .../Properties/launchSettings.json | 2 +- .../Startup.cs | 5 ++ .../DocumentWorkflowContextProvider.cs | 37 ++++++++++++++ .../Workflows/DocumentApprovalWorkflow.cs | 49 +++++++++---------- .../workflows.http | 31 ++++++------ .../RecurringTaskWorkflow.cs | 1 - 29 files changed, 197 insertions(+), 83 deletions(-) create mode 100644 src/core/Elsa.Abstractions/Services/WorkflowContextProvider.cs delete mode 100644 src/core/Elsa.Core/Activities/ControlFlow/Join/JoinBuilderExtensions.cs create mode 100644 src/core/Elsa.Core/Activities/ControlFlow/Join/JoinExtensions.cs create mode 100644 src/samples/Elsa.Samples.ContextualWorkflowHttp/Indexes/DocumentIndex.cs create mode 100644 src/samples/Elsa.Samples.ContextualWorkflowHttp/Migrations.cs create mode 100644 src/samples/Elsa.Samples.ContextualWorkflowHttp/WorkflowContextProviders/DocumentWorkflowContextProvider.cs diff --git a/src/activities/Elsa.Activities.Http/Activities/ReceiveHttpRequest/ReceiveHttpRequestBuilderExtensions.cs b/src/activities/Elsa.Activities.Http/Activities/ReceiveHttpRequest/ReceiveHttpRequestBuilderExtensions.cs index 009634e89..aec076454 100644 --- a/src/activities/Elsa.Activities.Http/Activities/ReceiveHttpRequest/ReceiveHttpRequestBuilderExtensions.cs +++ b/src/activities/Elsa.Activities.Http/Activities/ReceiveHttpRequest/ReceiveHttpRequestBuilderExtensions.cs @@ -15,5 +15,8 @@ namespace Elsa.Activities.Http public static IActivityBuilder ReceiveHttpRequest(this IBuilder builder, Func> path) => builder.ReceiveHttpRequest(setup => setup.Set(x => x.Path, path)); public static IActivityBuilder ReceiveHttpRequest(this IBuilder builder, Func path) => builder.ReceiveHttpRequest(setup => setup.Set(x => x.Path, path)); public static IActivityBuilder ReceiveHttpRequest(this IBuilder builder, PathString path) => builder.ReceiveHttpRequest(setup => setup.Set(x => x.Path, path)); + public static IActivityBuilder ReceiveHttpPostRequest(this IBuilder builder, Func path) => builder.ReceiveHttpRequest(activity => activity.WithPath(path).WithMethod(HttpMethods.Post).WithTargetType()); + public static IActivityBuilder ReceiveHttpPostRequest(this IBuilder builder, Func path) => builder.ReceiveHttpRequest(activity => activity.WithPath(path).WithMethod(HttpMethods.Post).WithTargetType()); + public static IActivityBuilder ReceiveHttpPostRequest(this IBuilder builder, PathString path) => builder.ReceiveHttpRequest(activity => activity.WithPath(path).WithMethod(HttpMethods.Post).WithTargetType()); } } \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Extensions/HttpRequestExtensions.cs b/src/activities/Elsa.Activities.Http/Extensions/HttpRequestExtensions.cs index 9e41fb6f0..b30242807 100644 --- a/src/activities/Elsa.Activities.Http/Extensions/HttpRequestExtensions.cs +++ b/src/activities/Elsa.Activities.Http/Extensions/HttpRequestExtensions.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; diff --git a/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs b/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs index c8d7cffef..a67b74e50 100644 --- a/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs +++ b/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Net; using System.Threading.Tasks; diff --git a/src/activities/Elsa.Activities.Http/Parsers/JsonHttpRequestBodyParser.cs b/src/activities/Elsa.Activities.Http/Parsers/JsonHttpRequestBodyParser.cs index 2abac997a..3c0c4d4d6 100644 --- a/src/activities/Elsa.Activities.Http/Parsers/JsonHttpRequestBodyParser.cs +++ b/src/activities/Elsa.Activities.Http/Parsers/JsonHttpRequestBodyParser.cs @@ -4,13 +4,20 @@ using System.Threading; using System.Threading.Tasks; using Elsa.Activities.Http.Extensions; using Elsa.Activities.Http.Services; +using Elsa.Serialization; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; namespace Elsa.Activities.Http.Parsers { public class JsonHttpRequestBodyParser : IHttpRequestBodyParser { + private readonly IContentSerializer _serializer; + + public JsonHttpRequestBodyParser(IContentSerializer serializer) + { + _serializer = serializer; + } + public int Priority => 0; public string?[] SupportedContentTypes => new[] { "application/json", "text/json" }; @@ -18,7 +25,7 @@ namespace Elsa.Activities.Http.Parsers { var json = await request.ReadContentAsStringAsync(cancellationToken); targetType ??= typeof(ExpandoObject); - return JsonConvert.DeserializeObject(json, targetType)!; + return _serializer.Deserialize(json, targetType)!; } } } \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Timers/Activities/CronEvent/CronEvent.cs b/src/activities/Elsa.Activities.Timers/Activities/CronEvent/CronEvent.cs index 97d3a842c..5b3a7eb94 100644 --- a/src/activities/Elsa.Activities.Timers/Activities/CronEvent/CronEvent.cs +++ b/src/activities/Elsa.Activities.Timers/Activities/CronEvent/CronEvent.cs @@ -2,8 +2,6 @@ using Elsa.ActivityResults; using Elsa.Attributes; using Elsa.Services; using Elsa.Services.Models; -using NCrontab; -using NodaTime; // ReSharper disable once CheckNamespace namespace Elsa.Activities.Timers diff --git a/src/activities/Elsa.Activities.Timers/Extensions/ServiceCollectionExtensions.cs b/src/activities/Elsa.Activities.Timers/Extensions/ServiceCollectionExtensions.cs index 35bf7fd1d..5ea667242 100644 --- a/src/activities/Elsa.Activities.Timers/Extensions/ServiceCollectionExtensions.cs +++ b/src/activities/Elsa.Activities.Timers/Extensions/ServiceCollectionExtensions.cs @@ -3,7 +3,6 @@ using Elsa.Activities.Timers; using Elsa.Activities.Timers.HostedServices; using Elsa.Activities.Timers.Options; using Elsa.Activities.Timers.Triggers; -using Elsa.Triggers; // ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection diff --git a/src/activities/Elsa.Activities.Timers/Triggers/InstantEventTrigger.cs b/src/activities/Elsa.Activities.Timers/Triggers/InstantEventTrigger.cs index 278bb65b6..e28be22c5 100644 --- a/src/activities/Elsa.Activities.Timers/Triggers/InstantEventTrigger.cs +++ b/src/activities/Elsa.Activities.Timers/Triggers/InstantEventTrigger.cs @@ -1,12 +1,9 @@ using System.Threading; using System.Threading.Tasks; -using Elsa.Extensions; using Elsa.Indexes; -using Elsa.Models; using Elsa.Services; using Elsa.Triggers; using NodaTime; -using Open.Linq.AsyncExtensions; namespace Elsa.Activities.Timers.Triggers { diff --git a/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs b/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs index eedd1de49..ddfca040f 100644 --- a/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs +++ b/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs @@ -19,6 +19,7 @@ namespace Elsa.Builders IActivityBuilder Add(Action>? setup = default) where T : class, IActivity; IOutcomeBuilder When(string outcome); IActivityBuilder Then(IActivityBuilder targetActivity); + IConnectionBuilder Then(string activityName); IActivityBuilder WithId(string? id); IActivityBuilder WithName(string? name); Func> BuildActivityAsync(); diff --git a/src/core/Elsa.Abstractions/Extensions/ServiceCollectionExtensions.cs b/src/core/Elsa.Abstractions/Extensions/ServiceCollectionExtensions.cs index c48b91a58..ed43aa0c4 100644 --- a/src/core/Elsa.Abstractions/Extensions/ServiceCollectionExtensions.cs +++ b/src/core/Elsa.Abstractions/Extensions/ServiceCollectionExtensions.cs @@ -13,5 +13,6 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection AddDataMigration(this IServiceCollection services) where T : class, IDataMigration => services.AddScoped(); public static IServiceCollection AddWorkflowProvider(this IServiceCollection services) where T : class, IWorkflowProvider => services.AddTransient(); public static IServiceCollection AddTriggerProvider(this IServiceCollection services) where T : class, ITriggerProvider => services.AddTransient(); + public static IServiceCollection AddWorkflowContextProvider(this IServiceCollection services) where T : class, IWorkflowContextProvider => services.AddTransient(); } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Serialization/IContentSerializer.cs b/src/core/Elsa.Abstractions/Serialization/IContentSerializer.cs index 0f7e4aecd..6ec2b3904 100644 --- a/src/core/Elsa.Abstractions/Serialization/IContentSerializer.cs +++ b/src/core/Elsa.Abstractions/Serialization/IContentSerializer.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System; +using Newtonsoft.Json.Linq; namespace Elsa.Serialization { @@ -6,7 +7,9 @@ namespace Elsa.Serialization { string Serialize(T value); T Deserialize(JToken token); + object? Deserialize(JToken token, Type targetType); T Deserialize(string json); + object? Deserialize(string json, Type targetType); object GetSettings(); } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs index 4c9481285..9fa5ae367 100644 --- a/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs +++ b/src/core/Elsa.Abstractions/Services/Models/WorkflowExecutionContext.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using Elsa.Models; -using Elsa.Serialization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Newtonsoft.Json; diff --git a/src/core/Elsa.Abstractions/Services/WorkflowContextProvider.cs b/src/core/Elsa.Abstractions/Services/WorkflowContextProvider.cs new file mode 100644 index 000000000..25cd81e81 --- /dev/null +++ b/src/core/Elsa.Abstractions/Services/WorkflowContextProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Services.Models; + +namespace Elsa.Services +{ + public abstract class WorkflowContextProvider : IWorkflowContextProvider + { + public IEnumerable SupportedTypes => new[] { typeof(T) }; + public virtual ValueTask LoadContextAsync(LoadWorkflowContext context, CancellationToken cancellationToken = default) => new ValueTask(); + public virtual ValueTask SaveContextAsync(SaveWorkflowContext context, CancellationToken cancellationToken = default) => new ValueTask(); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Triggers/WorkflowSelectorResult.cs b/src/core/Elsa.Abstractions/Triggers/WorkflowSelectorResult.cs index eafb75957..354a72ce3 100644 --- a/src/core/Elsa.Abstractions/Triggers/WorkflowSelectorResult.cs +++ b/src/core/Elsa.Abstractions/Triggers/WorkflowSelectorResult.cs @@ -1,5 +1,4 @@ -using Elsa.Models; -using Elsa.Services.Models; +using Elsa.Services.Models; namespace Elsa.Triggers { diff --git a/src/core/Elsa.Core/Activities/ControlFlow/Join/JoinBuilderExtensions.cs b/src/core/Elsa.Core/Activities/ControlFlow/Join/JoinBuilderExtensions.cs deleted file mode 100644 index f0a6df44f..000000000 --- a/src/core/Elsa.Core/Activities/ControlFlow/Join/JoinBuilderExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ - - -// ReSharper disable once CheckNamespace -namespace Elsa.Activities.ControlFlow -{ - public static class JoinBuilderExtensions - { - - } -} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/Join/JoinExtensions.cs b/src/core/Elsa.Core/Activities/ControlFlow/Join/JoinExtensions.cs new file mode 100644 index 000000000..dcde1f03c --- /dev/null +++ b/src/core/Elsa.Core/Activities/ControlFlow/Join/JoinExtensions.cs @@ -0,0 +1,14 @@ +using System; +using Elsa.Builders; +using Elsa.Services.Models; + +// ReSharper disable once CheckNamespace +namespace Elsa.Activities.ControlFlow +{ + public static class JoinExtensions + { + public static ISetupActivity WithMode(this ISetupActivity activity, Func value) => activity.Set(x => x.Mode, value); + public static ISetupActivity WithMode(this ISetupActivity activity, Func value) => activity.Set(x => x.Mode, value); + public static ISetupActivity WithMode(this ISetupActivity activity, Join.JoinMode value) => activity.Set(x => x.Mode, value); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Builders/ActivityBuilder.cs b/src/core/Elsa.Core/Builders/ActivityBuilder.cs index 35a8a8233..ba65a3426 100644 --- a/src/core/Elsa.Core/Builders/ActivityBuilder.cs +++ b/src/core/Elsa.Core/Builders/ActivityBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Elsa.Services; @@ -29,17 +30,20 @@ namespace Elsa.Builders public IActivityBuilder Add( Action>? setup = default) - where T : class, IActivity => WorkflowBuilder.Add(setup); + where T : class, IActivity => + WorkflowBuilder.Add(setup); public IOutcomeBuilder When(string outcome) => new OutcomeBuilder(WorkflowBuilder, this, outcome); public IActivityBuilder Then( Action>? setup = null, Action? branch = null) - where T : class, IActivity => When(OutcomeNames.Done).Then(setup, branch); + where T : class, IActivity => + When(OutcomeNames.Done).Then(setup, branch); public IActivityBuilder Then(Action? branch = null) - where T : class, IActivity => When(OutcomeNames.Done).Then(branch); + where T : class, IActivity => + When(OutcomeNames.Done).Then(branch); public IActivityBuilder Then(IActivityBuilder targetActivity) { @@ -47,6 +51,11 @@ namespace Elsa.Builders return this; } + public IConnectionBuilder Then(string activityName) => + WorkflowBuilder.Connect( + () => this, + () => WorkflowBuilder.Activities.First(x => x.Name == activityName)); + public IActivityBuilder WithId(string? id) { ActivityId = id!; diff --git a/src/core/Elsa.Core/Extensions/WorkflowSchedulerExtensions.cs b/src/core/Elsa.Core/Extensions/WorkflowSchedulerExtensions.cs index 0b5168140..8b1366f98 100644 --- a/src/core/Elsa.Core/Extensions/WorkflowSchedulerExtensions.cs +++ b/src/core/Elsa.Core/Extensions/WorkflowSchedulerExtensions.cs @@ -1,6 +1,5 @@ using System.Threading; using System.Threading.Tasks; -using Elsa.Data; using Elsa.Services; // ReSharper disable once CheckNamespace diff --git a/src/core/Elsa.Core/Serialization/DefaultContentSerializer.cs b/src/core/Elsa.Core/Serialization/DefaultContentSerializer.cs index ff9ca4a53..1fad04ac6 100644 --- a/src/core/Elsa.Core/Serialization/DefaultContentSerializer.cs +++ b/src/core/Elsa.Core/Serialization/DefaultContentSerializer.cs @@ -1,3 +1,4 @@ +using System; using Elsa.Converters; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -12,19 +13,17 @@ namespace Elsa.Serialization public DefaultContentSerializer(JsonSerializer serializer) { Serializer = serializer; + SerializerSettings = CreateDefaultJsonSerializationSettings(); } + private JsonSerializerSettings SerializerSettings { get; } private JsonSerializer Serializer { get; } public string Serialize(T value) => JObject.FromObject(value!, Serializer).ToString(); public T Deserialize(JToken token) => token.ToObject(Serializer)!; - - public T Deserialize(string json) - { - var token = JObject.Parse(json); - return Deserialize(token); - } - - public object GetSettings() => CreateDefaultJsonSerializationSettings(); + public object? Deserialize(JToken token, Type targetType) => token.ToObject(targetType, Serializer); + public T Deserialize(string json) => JsonConvert.DeserializeObject(json, SerializerSettings)!; + public object? Deserialize(string json, Type targetType) => JsonConvert.DeserializeObject(json, targetType, SerializerSettings); + public object GetSettings() => SerializerSettings; public static void ConfigureDefaultJsonSerializationSettings(JsonSerializerSettings settings) { diff --git a/src/core/Elsa.Core/Triggers/WorkflowSelector.cs b/src/core/Elsa.Core/Triggers/WorkflowSelector.cs index 8a04fae67..954f74a82 100644 --- a/src/core/Elsa.Core/Triggers/WorkflowSelector.cs +++ b/src/core/Elsa.Core/Triggers/WorkflowSelector.cs @@ -18,6 +18,7 @@ namespace Elsa.Triggers private readonly IWorkflowRegistry _workflowRegistry; private readonly IWorkflowFactory _workflowFactory; private readonly IWorkflowInstanceManager _workflowInstanceManager; + private readonly IWorkflowContextManager _workflowContextManager; private readonly IEnumerable _triggerProviders; private readonly IMemoryCache _memoryCache; private readonly IServiceProvider _serviceProvider; @@ -27,6 +28,7 @@ namespace Elsa.Triggers IWorkflowRegistry workflowRegistry, IWorkflowFactory workflowFactory, IWorkflowInstanceManager workflowInstanceManager, + IWorkflowContextManager workflowContextManager, IEnumerable triggerProviders, IMemoryCache memoryCache, IServiceProvider serviceProvider) @@ -34,6 +36,7 @@ namespace Elsa.Triggers _workflowRegistry = workflowRegistry; _workflowFactory = workflowFactory; _workflowInstanceManager = workflowInstanceManager; + _workflowContextManager = workflowContextManager; _triggerProviders = triggerProviders; _memoryCache = memoryCache; _serviceProvider = serviceProvider; @@ -152,7 +155,9 @@ namespace Elsa.Triggers { var providers = _triggerProviders.ToList(); var descriptors = new List(); - var workflowExecutionContext = new WorkflowExecutionContext(_serviceProvider, workflowBlueprint, workflowInstance, default, default); + var loadWorkflowContext = new LoadWorkflowContext(workflowBlueprint, workflowInstance); + var workflowContext = await _workflowContextManager.LoadContext(loadWorkflowContext, cancellationToken); + var workflowExecutionContext = new WorkflowExecutionContext(_serviceProvider, workflowBlueprint, workflowInstance, default, workflowContext); foreach (var blockingActivity in blockingActivities) { diff --git a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Elsa.Samples.ContextualWorkflowHttp.csproj b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Elsa.Samples.ContextualWorkflowHttp.csproj index 876533012..d193bc781 100644 --- a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Elsa.Samples.ContextualWorkflowHttp.csproj +++ b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Elsa.Samples.ContextualWorkflowHttp.csproj @@ -2,6 +2,7 @@ netcoreapp3.1 + enable diff --git a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Indexes/DocumentIndex.cs b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Indexes/DocumentIndex.cs new file mode 100644 index 000000000..20a68902e --- /dev/null +++ b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Indexes/DocumentIndex.cs @@ -0,0 +1,22 @@ +using Elsa.Samples.ContextualWorkflowHttp.Models; +using YesSql.Indexes; + +namespace Elsa.Samples.ContextualWorkflowHttp.Indexes +{ + public class DocumentIndex : MapIndex + { + public string DocumentUid { get; set; } = default!; // DocumentId is a reserved column name by YesSql, so taking DocumentUid instead. + } + + public class DocumentIndexProvider : IndexProvider + { + public override void Describe(DescribeContext context) + { + context.For().Map( + x => new DocumentIndex + { + DocumentUid = x.DocumentId + }); + } + } +} \ No newline at end of file diff --git a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Migrations.cs b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Migrations.cs new file mode 100644 index 000000000..7eab87177 --- /dev/null +++ b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Migrations.cs @@ -0,0 +1,15 @@ +using Elsa.Data; +using Elsa.Samples.ContextualWorkflowHttp.Indexes; +using YesSql.Sql; + +namespace Elsa.Samples.ContextualWorkflowHttp +{ + public class Migrations : DataMigration + { + public int Create() + { + SchemaBuilder.CreateMapIndexTable(table => table.Column(nameof(DocumentIndex.DocumentUid))); + return 1; + } + } +} \ No newline at end of file diff --git a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Models/Document.cs b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Models/Document.cs index 63fd4b307..f982ebb3a 100644 --- a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Models/Document.cs +++ b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Models/Document.cs @@ -4,9 +4,10 @@ namespace Elsa.Samples.ContextualWorkflowHttp.Models { public class Document { - public string Id { get; set; } - public string Title { get; set; } - public string Body { get; set; } + public int Id { get; set; } + public string DocumentId { get; set; } = default!; + public string Title { get; set; } = default!; + public string Body { get; set; } = default!; public ICollection Comments { get; set; } = new List(); } } \ No newline at end of file diff --git a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Properties/launchSettings.json b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Properties/launchSettings.json index 8f09bf942..2ed9e5380 100644 --- a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Properties/launchSettings.json +++ b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Properties/launchSettings.json @@ -6,7 +6,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:8201" + "applicationUrl": "http://localhost:7301" } } } \ No newline at end of file diff --git a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Startup.cs b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Startup.cs index f86e60f02..b34463a56 100644 --- a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Startup.cs +++ b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Startup.cs @@ -1,4 +1,6 @@ using System.Data; +using Elsa.Samples.ContextualWorkflowHttp.Indexes; +using Elsa.Samples.ContextualWorkflowHttp.WorkflowContextProviders; using Elsa.Samples.ContextualWorkflowHttp.Workflows; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; @@ -14,6 +16,9 @@ namespace Elsa.Samples.ContextualWorkflowHttp .AddElsa(option => option.UsePersistence(db => db.UseSqLite("Data Source=elsa.db;Cache=Shared", IsolationLevel.ReadUncommitted))) .AddHttpActivities() .AddConsoleActivities() + .AddDataMigration() + .AddIndexProvider() + .AddWorkflowContextProvider() .AddWorkflow(); } diff --git a/src/samples/Elsa.Samples.ContextualWorkflowHttp/WorkflowContextProviders/DocumentWorkflowContextProvider.cs b/src/samples/Elsa.Samples.ContextualWorkflowHttp/WorkflowContextProviders/DocumentWorkflowContextProvider.cs new file mode 100644 index 000000000..614d40d7b --- /dev/null +++ b/src/samples/Elsa.Samples.ContextualWorkflowHttp/WorkflowContextProviders/DocumentWorkflowContextProvider.cs @@ -0,0 +1,37 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Samples.ContextualWorkflowHttp.Indexes; +using Elsa.Services; +using Elsa.Services.Models; +using YesSql; +using Document = Elsa.Samples.ContextualWorkflowHttp.Models.Document; +using IIdGenerator = Elsa.Services.IIdGenerator; + +namespace Elsa.Samples.ContextualWorkflowHttp.WorkflowContextProviders +{ + public class DocumentWorkflowContextProvider : WorkflowContextProvider + { + private readonly ISession _session; + private readonly IIdGenerator _idGenerator; + + public DocumentWorkflowContextProvider(ISession session, IIdGenerator idGenerator) + { + _session = session; + _idGenerator = idGenerator; + } + + public override async ValueTask LoadContextAsync(LoadWorkflowContext context, CancellationToken cancellationToken = default) => + await _session.Query(x => x.DocumentUid == context.ContextId).FirstOrDefaultAsync(); + + public override ValueTask SaveContextAsync(SaveWorkflowContext context, CancellationToken cancellationToken = default) + { + var document = (Document)context.Context; + + if (string.IsNullOrWhiteSpace(document.DocumentId)) + document.DocumentId = _idGenerator.Generate(); + + _session.Save(document); + return new ValueTask(document.DocumentId); + } + } +} \ No newline at end of file diff --git a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Workflows/DocumentApprovalWorkflow.cs b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Workflows/DocumentApprovalWorkflow.cs index 5aca3b43d..1fd07e354 100644 --- a/src/samples/Elsa.Samples.ContextualWorkflowHttp/Workflows/DocumentApprovalWorkflow.cs +++ b/src/samples/Elsa.Samples.ContextualWorkflowHttp/Workflows/DocumentApprovalWorkflow.cs @@ -1,49 +1,41 @@ using System; using System.Net; -using Elsa.Activities.Console; using Elsa.Activities.ControlFlow; using Elsa.Activities.Http; using Elsa.Activities.Http.Models; using Elsa.Builders; using Elsa.Samples.ContextualWorkflowHttp.Models; using Elsa.Services.Models; -using Microsoft.AspNetCore.Http; namespace Elsa.Samples.ContextualWorkflowHttp.Workflows { /// - /// Demonstrates loading & saving of the document-to-approve, which is the context (or subject) of the workflow. + /// Demonstrates saving & loading of the document-to-approve, which is the context (or subject) of the workflow. /// public class DocumentApprovalWorkflow : IWorkflow { public void Build(IWorkflowBuilder workflow) { + // Demonstrating that we can create activities and connect to them later on by using the activity builder reference. + var join = workflow.Add(x => x.WithMode(Join.JoinMode.WaitAny)); + join.Finish(); + workflow - .StartWith() - - // The subject type of this workflow. + // The workflow context type of this workflow. .WithContextType() // Accept HTTP requests to submit new documents. - .ReceiveHttpRequest(activity => activity.WithPath("/documents").WithMethod(HttpMethods.Post).WithTargetType()) + .ReceiveHttpPostRequest("/documents") - // Store the document as the workflow subject. It will be saved automatically when the workflow gets suspended. - .Then(context => context.WorkflowExecutionContext.WorkflowContext = context.Input) - - // Correlate the workflow by document ID. - .Correlate(context => ((Document)context.WorkflowExecutionContext.WorkflowContext)!.Id) + // Store the document as the workflow context. It will be saved automatically when the workflow gets suspended. + .Then(context => context.WorkflowExecutionContext.WorkflowContext = (Document)((HttpRequestModel)context.Input!).Body) // Write an HTTP response. .WriteHttpResponse( activity => activity .WithStatusCode(HttpStatusCode.OK) .WithContentType("text/html") - .WithContent( - context => - { - var document = (Document)context.WorkflowExecutionContext.WorkflowContext; - return $"Document received with ID {document!.Id}! Awaiting Approve or Reject response."; - })) + .WithContent(context => $"Document received with ID {GetDocumentId(context)}! Awaiting Approve or Reject response.")) // Fork execution into two branches: an Approve branch and a Reject branch. .Then( @@ -52,27 +44,30 @@ namespace Elsa.Samples.ContextualWorkflowHttp.Workflows { var approveBranch = fork .When("Approve") - .ReceiveHttpRequest(activity => activity.WithPath("/approve").WithMethod(HttpMethods.Post).WithTargetType()) + .ReceiveHttpPostRequest(context => $"/documents/{GetDocumentId(context)}/approve") .Then(StoreComment); var rejectBranch = fork .When("Reject") - .ReceiveHttpRequest(activity => activity.WithPath("/reject").WithMethod(HttpMethods.Post).WithTargetType()) + .ReceiveHttpPostRequest(context => $"/documents/{GetDocumentId(context)}/reject") .Then(StoreComment); - WriteResponse(approveBranch, document => $"Thanks for approving document {document!.Id}!").Then("Join"); - WriteResponse(rejectBranch, document => $"Thanks for rejecting document {document!.Id}!"); + WriteResponse(approveBranch, document => $"Thanks for approving document {document!.DocumentId}!").Then(join); + WriteResponse(rejectBranch, document => $"Thanks for rejecting document {document!.DocumentId}!").Then(join); }); } - private void StoreComment(ActivityExecutionContext context) + private static Document GetDocument(ActivityExecutionContext context) => (Document)context.WorkflowExecutionContext.WorkflowContext!; + private static string GetDocumentId(ActivityExecutionContext context) => GetDocument(context).DocumentId; + + private static void StoreComment(ActivityExecutionContext context) { - var document = (Document)context.WorkflowExecutionContext.WorkflowContext; + var document = (Document)context.WorkflowExecutionContext.WorkflowContext!; var comment = (Comment)((HttpRequestModel)context.Input)!.Body; - document!.Comments.Add(comment); + document.Comments.Add(comment); } - private IActivityBuilder WriteResponse(IActivityBuilder builder, Func html) => + private static IActivityBuilder WriteResponse(IBuilder builder, Func html) => builder.WriteHttpResponse( activity => activity .WithStatusCode(HttpStatusCode.OK) @@ -80,7 +75,7 @@ namespace Elsa.Samples.ContextualWorkflowHttp.Workflows .WithContent( context => { - var document = (Document)context.WorkflowExecutionContext.WorkflowContext; + var document = GetDocument(context); return html(document); })); } diff --git a/src/samples/Elsa.Samples.ContextualWorkflowHttp/workflows.http b/src/samples/Elsa.Samples.ContextualWorkflowHttp/workflows.http index 67f4517ca..534a36047 100644 --- a/src/samples/Elsa.Samples.ContextualWorkflowHttp/workflows.http +++ b/src/samples/Elsa.Samples.ContextualWorkflowHttp/workflows.http @@ -1,28 +1,31 @@ -# Register John. -POST http://localhost:8201/register +# Create document. +POST http://localhost:7301/documents Content-Type: application/json { - "name": "John von Neumann", - "email": "john@gmail.com" + "documentId": "document-1", + "title": "Document 1", + "body": "john@gmail.com" } ### -# Register Julia. -POST http://localhost:8201/register +# Approve document. +POST http://localhost:7301/documents/document-1/approve Content-Type: application/json { -"name": "Julia Berger", -"email": "julia@gmail.com" + "author": "Jason", + "text": "Great job!", + "timeStamp": "2020-10-31T20:56:49Z" } ### -# Confirm John's registration -GET http://localhost:8201/confirm?correlation=john@gmail.com +# Reject document. +POST http://localhost:7301/documents/document-1/reject Content-Type: application/json -### -# Confirm Julia's registration -GET http://localhost:8201/confirm?correlation=julia@gmail.com -Content-Type: application/json \ No newline at end of file +{ + "author": "Laura", + "text": "Nice try.", + "timeStamp": "2020-11-01T20:20:20Z" +} diff --git a/src/samples/Elsa.Samples.Timers/RecurringTaskWorkflow.cs b/src/samples/Elsa.Samples.Timers/RecurringTaskWorkflow.cs index 4f1a58579..b8c782150 100644 --- a/src/samples/Elsa.Samples.Timers/RecurringTaskWorkflow.cs +++ b/src/samples/Elsa.Samples.Timers/RecurringTaskWorkflow.cs @@ -1,4 +1,3 @@ -using System; using Elsa.Activities.Console; using Elsa.Activities.Timers; using Elsa.Builders;