Fix object disposed bug (#116)

* Refactor buggy SignalRequestHandler

For some reason, the following code is problematic:

await DecryptToken()
                .BindAsync(GetWorkflowInstanceAsync)
                .BindAsync(CheckIfExecutingAsync)
                .MapAsync(ResumeWorkflowAsync);

Basically, any lambda in the BindAsync and MapAsync could execute even after the containing method returned

* Implement better message handler registration without adding multiple IMediator services

* Abstracted Mediator service registration methods.
* Add Liquid support for JObject.
This commit is contained in:
Sipke Schoorstra 2019-10-31 21:31:18 +01:00 committed by GitHub
parent 48777d8eb3
commit 57f116c239
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 687 additions and 658 deletions

View file

@ -27,7 +27,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="LanguageExt.Core" Version="3.3.29" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />

View file

@ -1,12 +1,12 @@
using System;
using Elsa.Activities.Http.Activities;
using Elsa.Activities.Http.Formatters;
using Elsa.Activities.Http.Liquid;
using Elsa.Activities.Http.Options;
using Elsa.Activities.Http.RequestHandlers.Handlers;
using Elsa.Activities.Http.Services;
using Elsa.Scripting;
using Elsa.Scripting.JavaScript;
using MediatR;
using Elsa.Extensions;
using Elsa.Scripting.Liquid.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
@ -38,11 +38,12 @@ namespace Elsa.Activities.Http.Extensions
.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddSingleton<IAbsoluteUrlProvider, DefaultAbsoluteUrlProvider>()
.AddHttpContextAccessor()
.AddMediatR(typeof(HttpActivitiesServiceCollectionExtensions))
.AddNotificationHandlers(typeof(HttpActivitiesServiceCollectionExtensions))
.AddDataProtection();
services.AddLiquidFilter<SignalUrlFilter>("signal_url");
return services
.AddScoped(sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext)
.AddRequestHandler<TriggerRequestHandler>()
.AddRequestHandler<SignalRequestHandler>();
}

View file

@ -0,0 +1,46 @@
using System;
using System.Threading.Tasks;
using Elsa.Activities.Http.Models;
using Elsa.Activities.Http.Services;
using Elsa.Scripting.Liquid.Services;
using Elsa.Services.Models;
using Fluid;
using Fluid.Values;
namespace Elsa.Activities.Http.Liquid
{
public class SignalUrlFilter : ILiquidFilter
{
private readonly ITokenService tokenService;
private readonly IAbsoluteUrlProvider absoluteUrlProvider;
public SignalUrlFilter(ITokenService tokenService, IAbsoluteUrlProvider absoluteUrlProvider)
{
this.tokenService = tokenService;
this.absoluteUrlProvider = absoluteUrlProvider;
}
public ValueTask<FluidValue> ProcessAsync(FluidValue input, FilterArguments arguments, TemplateContext context)
{
var workflowContextValue = context.GetValue("WorkflowExecutionContext");
if (workflowContextValue.IsNil())
throw new ArgumentException("WorkflowExecutionContext missing while invoking 'signal_url'");
var workflowContext = (WorkflowExecutionContext)workflowContextValue.ToObjectValue();
var signalName = input.ToStringValue();
var url = GenerateUrl(signalName, workflowContext);
return new ValueTask<FluidValue>(new StringValue(url));
}
private string GenerateUrl(string signal, WorkflowExecutionContext workflowExecutionContext)
{
var workflowInstanceId = workflowExecutionContext.Workflow.Id;
var payload = new Signal(signal, workflowInstanceId);
var token = tokenService.CreateToken(payload);
var url = $"/workflows/signal?token={token}";
return absoluteUrlProvider.ToAbsoluteUrl(url).ToString();
}
}
}

View file

@ -3,12 +3,11 @@ using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Http.Models;
using Elsa.Activities.Http.RequestHandlers.Results;
using Elsa.Activities.Http.Services;
using Elsa.Models;
using Elsa.Persistence;
using Elsa.Services;
using LanguageExt;
using LanguageExt.Common;
using Microsoft.AspNetCore.Http;
namespace Elsa.Activities.Http.RequestHandlers.Handlers
@ -42,58 +41,39 @@ namespace Elsa.Activities.Http.RequestHandlers.Handlers
public async Task<IRequestHandlerResult> HandleRequestAsync()
{
await DecryptToken()
.BindAsync(GetWorkflowInstanceAsync)
.BindAsync(CheckIfExecutingAsync)
.MapAsync(ResumeWorkflowAsync);
var signal = DecryptToken();
return default;
if (signal == null)
return new NotFoundResult();
var workflowInstance = await GetWorkflowInstanceAsync(signal);
if (workflowInstance == null)
return new NotFoundResult();
if (!CheckIfExecuting(workflowInstance))
return new BadRequestResult($"Cannot signal a workflow with status other than {WorkflowStatus.Executing}. Actual workflow status: {workflowInstance.Status}.");
await ResumeWorkflowAsync(workflowInstance, signal);
return new AcceptedResult();
}
private Either<Error, Signal> DecryptToken()
private Signal DecryptToken()
{
var token = httpContext.Request.Query["token"];
if (tokenService.TryDecryptToken(token, out Signal signal))
{
return signal;
}
httpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return Error.New("Invalid token");
return tokenService.TryDecryptToken(token, out Signal signal) ? signal : default;
}
private async Task<Either<Error, (WorkflowInstance, Signal)>> GetWorkflowInstanceAsync(Signal signal)
private async Task<WorkflowInstance> GetWorkflowInstanceAsync(Signal signal) =>
await workflowInstanceStore.GetByIdAsync(signal.WorkflowInstanceId, cancellationToken);
private bool CheckIfExecuting(WorkflowInstance workflowInstance) =>
workflowInstance.Status == WorkflowStatus.Executing;
private async Task ResumeWorkflowAsync(WorkflowInstance workflowInstance, Signal signal)
{
var workflowInstance =
await workflowInstanceStore.GetByIdAsync(signal.WorkflowInstanceId, cancellationToken);
if (workflowInstance != null)
return (workflowInstance, signal);
httpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return Error.New("Workflow not found");
}
private async Task<Either<Error, (WorkflowInstance, Signal)>> CheckIfExecutingAsync(
(WorkflowInstance, Signal) tuple)
{
var (workflowInstance, signal) = tuple;
if (workflowInstance.Status == WorkflowStatus.Executing)
return (workflowInstance, signal);
httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await httpContext.Response.WriteAsync(
$"Cannot signal a workflow with status other than {WorkflowStatus.Executing}. Actual workflow status: {workflowInstance.Status}.",
cancellationToken);
return Error.New("Cannot resume workflow that is not executing.");
}
private async Task ResumeWorkflowAsync((WorkflowInstance, Signal) tuple)
{
var (workflowInstance, signal) = tuple;
var input = new Variables
{
["Signal"] = signal.Name
@ -107,11 +87,6 @@ namespace Elsa.Activities.Http.RequestHandlers.Handlers
var workflow = workflowFactory.CreateWorkflow(workflowDefinition, input, workflowInstance);
var blockingSignalActivities = workflow.BlockingActivities.ToList();
await workflowInvoker.ResumeAsync(workflow, blockingSignalActivities, cancellationToken);
if (!httpContext.Response.HasStarted)
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Accepted;
}
}
}
}

View file

@ -24,16 +24,16 @@ namespace Elsa.Activities.Http.RequestHandlers.Handlers
private readonly CancellationToken cancellationToken;
public TriggerRequestHandler(
HttpContext httpContext,
IHttpContextAccessor httpContext,
IWorkflowInvoker workflowInvoker,
IWorkflowRegistry registry,
IWorkflowInstanceStore workflowInstanceStore)
{
this.httpContext = httpContext;
this.httpContext = httpContext.HttpContext;
this.workflowInvoker = workflowInvoker;
this.registry = registry;
this.workflowInstanceStore = workflowInstanceStore;
cancellationToken = httpContext.RequestAborted;
cancellationToken = httpContext.HttpContext.RequestAborted;
}
public async Task<IRequestHandlerResult> HandleRequestAsync()

View file

@ -7,10 +7,24 @@ namespace Elsa.Activities.Http.RequestHandlers.Results
{
public class BadRequestResult : IRequestHandlerResult
{
public Task ExecuteResultAsync(HttpContext httpContext, RequestDelegate next)
public BadRequestResult()
{
}
public BadRequestResult(string message)
{
Message = message;
}
public string Message { get; }
public async Task ExecuteResultAsync(HttpContext httpContext, RequestDelegate next)
{
httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Task.CompletedTask;
if(!string.IsNullOrWhiteSpace(Message))
await httpContext.Response.WriteAsync(Message, httpContext.RequestAborted);
}
}
}

View file

@ -5,7 +5,6 @@ using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Models;
using Elsa.Results;
using Elsa.Scripting.JavaScript;
using Elsa.Scripting.JavaScript.Services;
using Elsa.Services;
using Elsa.Services.Models;

View file

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Workflows.Activities;
using Elsa.Models;
using Elsa.Services;
using Newtonsoft.Json.Linq;
namespace Elsa.Activities.Workflows.Extensions
{
public static class WorkflowInvokerExtensions
{
public static async Task TriggerSignalAsync(
this IWorkflowInvoker workflowInvoker,
string signalName,
Variables input = default,
Func<JObject, bool> activityStatePredicate = null,
string correlationId = default,
CancellationToken cancellationToken = default)
{
var combinedInput = new Variables(
new Dictionary<string, object>
{
["Signal"] = signalName
});
if (input != null)
combinedInput.AddVariables(input);
await workflowInvoker.TriggerAsync(nameof(Signaled), combinedInput, correlationId, activityStatePredicate, cancellationToken);
}
}
}

View file

@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Activities.Workflows.Extensions
{
public static class WorkflowActivityServiceCollectionExtensions
public static class WorkflowsServiceCollectionExtensions
{
public static IServiceCollection AddWorkflowActivities(this IServiceCollection services)
{

View file

@ -1,16 +1,20 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Expressions;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Extensions
{
public static class WorkflowExpressionEvaluatorExtensions
{
public static async Task<T> EvaluateAsync<T>(this IWorkflowExpressionEvaluator evaluator, IWorkflowExpression<T> expression, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
{
return (T)await evaluator.EvaluateAsync(expression, typeof(T), workflowExecutionContext, cancellationToken);
}
}
using System.Threading;
using System.Threading.Tasks;
using Elsa.Expressions;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Extensions
{
public static class WorkflowExpressionEvaluatorExtensions
{
public static async Task<T> EvaluateAsync<T>(
this IWorkflowExpressionEvaluator evaluator,
IWorkflowExpression<T> expression,
WorkflowExecutionContext workflowExecutionContext,
CancellationToken cancellationToken = default)
{
return (T)await evaluator.EvaluateAsync(expression, typeof(T), workflowExecutionContext, cancellationToken);
}
}
}

View file

@ -10,7 +10,7 @@ namespace Elsa.Models
{
}
public Variables(Variables other) : this((IEnumerable<KeyValuePair<string, object>>) other)
public Variables(Variables other) : this((IEnumerable<KeyValuePair<string, object>>)other)
{
}
@ -29,7 +29,21 @@ namespace Elsa.Models
public T GetVariable<T>(string name)
{
return ContainsKey(name) ? (T) this[name] : default(T);
return ContainsKey(name) ? (T)this[name] : default(T);
}
public void AddVariable(string name, object value)
{
this[name] = value;
}
public void AddVariables(Variables variables) =>
AddVariables((IEnumerable<KeyValuePair<string, object>>)variables);
public void AddVariables(IEnumerable<KeyValuePair<string, object>> variables)
{
foreach (var variable in variables)
AddVariable(variable.Key, variable.Value);
}
public bool HasVariable(string name, object value)

View file

@ -1,7 +0,0 @@
namespace Elsa.Services
{
internal interface IScopedWorkflowInvoker : IWorkflowInvoker
{
}
}

View file

@ -1,7 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Models;
using Microsoft.Extensions.DependencyInjection;
using NodaTime;
namespace Elsa.Services.Models
@ -20,6 +25,7 @@ namespace Elsa.Services.Models
IsFirstPass = true;
scheduledActivities = new Stack<IActivity>();
scheduledHaltingActivities = new Stack<IActivity>();
ExpressionEvaluator = serviceProvider.GetRequiredService<IWorkflowExpressionEvaluator>();
}
public Workflow Workflow { get; }
@ -53,6 +59,7 @@ namespace Elsa.Services.Models
public IActivity PopScheduledActivity() => CurrentActivity = scheduledActivities.Pop();
public void ScheduleHaltingActivity(IActivity activity) => scheduledHaltingActivities.Push(activity);
public IActivity PopScheduledHaltingActivity() => scheduledHaltingActivities.Pop();
public IWorkflowExpressionEvaluator ExpressionEvaluator { get; }
public void SetVariable(string name, object value)
{
@ -75,6 +82,9 @@ namespace Elsa.Services.Models
return scope.GetVariable(name);
}
public Task<T> EvaluateAsync<T>(IWorkflowExpression<T> expression, CancellationToken cancellationToken) =>
ExpressionEvaluator.EvaluateAsync(expression, this, cancellationToken);
public void SetLastResult(object value) => CurrentScope.LastResult = value;
public void Start()

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Elsa;
using Elsa.Activities;
using Elsa.AutoMapper.Extensions;
@ -30,8 +29,8 @@ namespace Microsoft.Extensions.DependencyInjection
Action<ElsaBuilder> configure = null)
{
var configuration = new ElsaBuilder(services);
configuration.UseWorkflowsCore();
configuration.UseMediatR();
configuration.AddWorkflowsCore();
configuration.AddMediatR();
configure?.Invoke(configuration);
EnsurePersistence(configuration);
EnsureCaching(configuration);
@ -53,51 +52,14 @@ namespace Microsoft.Extensions.DependencyInjection
.AddTransient<IActivity>(sp => sp.GetRequiredService<T>());
}
/// <summary>
/// Registers the specified service only if none already exists for the specified provider type.
/// </summary>
public static IServiceCollection TryAddProvider<TService, TProvider>(
this IServiceCollection services,
ServiceLifetime lifetime)
private static IServiceCollection AddMediatR(this ElsaBuilder configuration)
{
return services.TryAddProvider(typeof(TService), typeof(TProvider), lifetime);
return configuration.Services.AddMediatR(
mediatr => mediatr.AsSingleton(),
typeof(ElsaServiceCollectionExtensions));
}
/// <summary>
/// Registers the specified service only if none already exists for the specified provider type.
/// </summary>
public static IServiceCollection TryAddProvider(
this IServiceCollection services,
Type serviceType,
Type providerType,
ServiceLifetime lifetime)
{
var descriptor = services.FirstOrDefault(
x => x.ServiceType == serviceType && x.ImplementationType == providerType
);
if (descriptor == null)
{
descriptor = new ServiceDescriptor(serviceType, providerType, lifetime);
services.Add(descriptor);
}
return services;
}
public static IServiceCollection Replace<TService, TImplementation>(
this IServiceCollection services,
ServiceLifetime lifetime)
{
return services.Replace(new ServiceDescriptor(typeof(TService), typeof(TImplementation), lifetime));
}
private static IServiceCollection UseMediatR(this ElsaBuilder configuration)
{
return configuration.Services.AddMediatR(typeof(ElsaServiceCollectionExtensions));
}
private static ElsaBuilder UseWorkflowsCore(this ElsaBuilder configuration)
private static ElsaBuilder AddWorkflowsCore(this ElsaBuilder configuration)
{
var services = configuration.Services;
services.TryAddSingleton<IClock>(SystemClock.Instance);
@ -112,14 +74,13 @@ namespace Microsoft.Extensions.DependencyInjection
.TryAddProvider<ITokenFormatter, YamlTokenFormatter>(ServiceLifetime.Singleton)
.TryAddProvider<ITokenFormatter, XmlTokenFormatter>(ServiceLifetime.Singleton)
.TryAddProvider<IExpressionEvaluator, LiteralEvaluator>(ServiceLifetime.Singleton)
.AddScoped<IWorkflowFactory, WorkflowFactory>()
.AddSingleton<IActivityInvoker, ActivityInvoker>()
.AddTransient<IWorkflowFactory, WorkflowFactory>()
.AddScoped<IActivityInvoker, ActivityInvoker>()
.AddScoped<IWorkflowExpressionEvaluator, WorkflowExpressionEvaluator>()
.AddSingleton<IWorkflowSerializerProvider, WorkflowSerializerProvider>()
.AddSingleton<IWorkflowRegistry, WorkflowRegistry>()
.AddTransient<IWorkflowRegistry, WorkflowRegistry>()
.AddScoped<IWorkflowEventHandler, PersistenceWorkflowEventHandler>()
.AddSingleton<IWorkflowInvoker, WorkflowInvoker>()
.AddScoped<IScopedWorkflowInvoker, ScopedWorkflowInvoker>()
.AddScoped<IWorkflowInvoker, WorkflowInvoker>()
.AddScoped<IActivityResolver, ActivityResolver>()
.AddScoped<IWorkflowEventHandler, ActivityLoggingWorkflowEventHandler>()
.AddTransient<IWorkflowProvider, StoreWorkflowProvider>()
@ -151,12 +112,6 @@ namespace Microsoft.Extensions.DependencyInjection
configuration.Services.AddMemoryCache();
}
private static bool HasService<T>(this IServiceCollection services) =>
services.Any(x => x.ServiceType == typeof(T));
private static bool HasService<T>(this ElsaBuilder configuration) =>
configuration.Services.HasService<T>();
private static IServiceCollection AddPrimitiveActivities(this IServiceCollection services)
{
return services

View file

@ -0,0 +1,26 @@
using System;
using System.Linq;
using System.Reflection;
using MediatR;
using MediatR.Registration;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Extensions
{
public static class MessageHandlerServiceCollectionExtensions
{
public static IServiceCollection AddNotificationHandler<T, THandler>(this IServiceCollection services)
where T : INotification
where THandler : INotificationHandler<T>
{
return services.AddTransient(typeof(INotificationHandler<T>), typeof(THandler));
}
public static IServiceCollection AddNotificationHandlers(this IServiceCollection services, params Type[] markerTypes)
{
var assemblies = markerTypes.Select(x => x.GetTypeInfo().Assembly);
ServiceRegistrar.AddMediatRClasses(services, assemblies);
return services;
}
}
}

View file

@ -0,0 +1,56 @@
using System;
using System.Linq;
using Elsa;
using Microsoft.Extensions.DependencyInjection.Extensions;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registers the specified service only if none already exists for the specified provider type.
/// </summary>
public static IServiceCollection TryAddProvider<TService, TProvider>(
this IServiceCollection services,
ServiceLifetime lifetime)
{
return services.TryAddProvider(typeof(TService), typeof(TProvider), lifetime);
}
/// <summary>
/// Registers the specified service only if none already exists for the specified provider type.
/// </summary>
public static IServiceCollection TryAddProvider(
this IServiceCollection services,
Type serviceType,
Type providerType,
ServiceLifetime lifetime)
{
var descriptor = services.FirstOrDefault(
x => x.ServiceType == serviceType && x.ImplementationType == providerType
);
if (descriptor == null)
{
descriptor = new ServiceDescriptor(serviceType, providerType, lifetime);
services.Add(descriptor);
}
return services;
}
public static IServiceCollection Replace<TService, TImplementation>(
this IServiceCollection services,
ServiceLifetime lifetime)
{
return services.Replace(new ServiceDescriptor(typeof(TService), typeof(TImplementation), lifetime));
}
public static bool HasService<T>(this IServiceCollection services) =>
services.Any(x => x.ServiceType == typeof(T));
public static bool HasService<T>(this ElsaBuilder configuration) =>
configuration.Services.HasService<T>();
}
}

View file

@ -14,11 +14,11 @@ namespace Elsa.Metadata
public string Type { get; set; }
public string DisplayName { get; set; }
public string? Description { get; set; }
public string? RuntimeDescription { get; set; }
public string Description { get; set; }
public string RuntimeDescription { get; set; }
public string Category { get; set; }
public string? Icon { get; set; }
public object? Outcomes { get; set; }
public string Icon { get; set; }
public object Outcomes { get; set; }
public ActivityPropertyDescriptor[] Properties { get; set; }
}
}

View file

@ -2,7 +2,7 @@ namespace Elsa.Metadata
{
public class ActivityPropertyDescriptor
{
public ActivityPropertyDescriptor(string name, string type, string label, string? hint = null, object? options = null)
public ActivityPropertyDescriptor(string name, string type, string label, string hint = null, object options = null)
{
Name = name;
Type = type;
@ -14,7 +14,7 @@ namespace Elsa.Metadata
public string Name { get; }
public string Type { get; }
public string Label { get; }
public string? Hint { get; }
public object? Options { get; }
public string Hint { get; }
public object Options { get; }
}
}

View file

@ -1,457 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Extensions;
using Elsa.Models;
using Elsa.Persistence;
using Elsa.Results;
using Elsa.Services.Extensions;
using Elsa.Services.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Elsa.Services
{
internal class ScopedWorkflowInvoker : IScopedWorkflowInvoker
{
private readonly IActivityInvoker activityInvoker;
private readonly IWorkflowFactory workflowFactory;
private readonly IWorkflowRegistry workflowRegistry;
private readonly IWorkflowInstanceStore workflowInstanceStore;
private readonly IEnumerable<IWorkflowEventHandler> workflowEventHandlers;
private readonly IClock clock;
private readonly IServiceProvider serviceProvider;
private readonly ILogger logger;
public ScopedWorkflowInvoker(
IActivityInvoker activityInvoker,
IWorkflowFactory workflowFactory,
IWorkflowRegistry workflowRegistry,
IWorkflowInstanceStore workflowInstanceStore,
IEnumerable<IWorkflowEventHandler> workflowEventHandlers,
IClock clock,
IServiceProvider serviceProvider,
ILogger<WorkflowInvoker> logger)
{
this.activityInvoker = activityInvoker;
this.workflowFactory = workflowFactory;
this.workflowRegistry = workflowRegistry;
this.workflowInstanceStore = workflowInstanceStore;
this.workflowEventHandlers = workflowEventHandlers;
this.clock = clock;
this.serviceProvider = serviceProvider;
this.logger = logger;
}
public Task<WorkflowExecutionContext> StartAsync(
Workflow workflow,
IEnumerable<IActivity> startActivities = default,
CancellationToken cancellationToken = default)
{
return ExecuteAsync(workflow, false, startActivities, cancellationToken);
}
public Task<WorkflowExecutionContext> StartAsync(
WorkflowDefinitionVersion workflowDefinition,
Variables input = default,
IEnumerable<string> startActivityIds = default,
string correlationId = default,
CancellationToken cancellationToken = default)
{
var workflow = workflowFactory.CreateWorkflow(workflowDefinition, input, correlationId: correlationId);
var startActivities = workflow.Activities.Find(startActivityIds);
return ExecuteAsync(workflow, false, startActivities, cancellationToken);
}
public Task<WorkflowExecutionContext> StartAsync<T>(
Variables input = default,
IEnumerable<string> startActivityIds = default,
string correlationId = default,
CancellationToken cancellationToken = default) where T : IWorkflow, new()
{
var workflow = workflowFactory.CreateWorkflow<T>(input, correlationId: correlationId);
var startActivities = workflow.Activities.Find(startActivityIds);
return ExecuteAsync(workflow, false, startActivities, cancellationToken);
}
public Task<WorkflowExecutionContext> ResumeAsync(
Workflow workflow,
IEnumerable<IActivity> startActivities = default,
CancellationToken cancellationToken = default)
{
return ExecuteAsync(workflow, true, startActivities, cancellationToken);
}
public Task<WorkflowExecutionContext> ResumeAsync<T>(
WorkflowInstance workflowInstance,
Variables input = null,
IEnumerable<string> startActivityIds = default,
CancellationToken cancellationToken = default) where T : IWorkflow, new()
{
var workflow = workflowFactory.CreateWorkflow<T>(input, workflowInstance);
var startActivities = workflow.Activities.Find(startActivityIds);
return ExecuteAsync(workflow, true, startActivities, cancellationToken);
}
public async Task<WorkflowExecutionContext> ResumeAsync(
WorkflowInstance workflowInstance,
Variables input = null,
IEnumerable<string> startActivityIds = default,
CancellationToken cancellationToken = default)
{
var definition = await workflowRegistry.GetWorkflowDefinitionAsync(
workflowInstance.DefinitionId,
VersionOptions.SpecificVersion(workflowInstance.Version),
cancellationToken);
var workflow = workflowFactory.CreateWorkflow(definition, input, workflowInstance);
return await ExecuteAsync(workflow, true, startActivityIds, cancellationToken);
}
public async Task<IEnumerable<WorkflowExecutionContext>> TriggerAsync(
string activityType,
Variables input = default,
string correlationId = default,
Func<JObject, bool> activityStatePredicate = default,
CancellationToken cancellationToken = default)
{
var startedExecutionContexts = await StartManyAsync(
activityType,
input,
correlationId,
activityStatePredicate,
cancellationToken
);
var resumedExecutionContexts = await ResumeManyAsync(
activityType,
input,
correlationId,
activityStatePredicate,
cancellationToken
);
return startedExecutionContexts.Concat(resumedExecutionContexts);
}
private async Task<IEnumerable<WorkflowExecutionContext>> ResumeManyAsync(
string activityType,
Variables input = default,
string correlationId = default,
Func<JObject, bool> activityStatePredicate = default,
CancellationToken cancellationToken = default)
{
var workflowInstances = await workflowInstanceStore
.ListByBlockingActivityAsync(activityType, correlationId, cancellationToken)
.ToListAsync();
if (activityStatePredicate != null)
workflowInstances = workflowInstances.Where(x => activityStatePredicate(x.Item2.State)).ToList();
return await ResumeManyAsync(
workflowInstances,
input,
cancellationToken
);
}
private async Task<IEnumerable<WorkflowExecutionContext>> StartManyAsync(
string activityType,
Variables input = default,
string correlationId = default,
Func<JObject, bool> activityStatePredicate = default,
CancellationToken cancellationToken = default)
{
var workflowDefinitions = await workflowRegistry.ListByStartActivityAsync(activityType, cancellationToken);
if (activityStatePredicate != null)
workflowDefinitions = workflowDefinitions.Where(x => activityStatePredicate(x.Item2.State));
workflowDefinitions = await FilterRunningSingletonsAsync(
workflowDefinitions,
cancellationToken
);
return await StartManyAsync(workflowDefinitions, input, correlationId, cancellationToken);
}
private Task<WorkflowExecutionContext> ExecuteAsync(
Workflow workflow,
bool resume,
IEnumerable<string> startActivityIds = default,
CancellationToken cancellationToken = default)
{
var startActivities = startActivityIds != null
? workflow.Activities.Find(startActivityIds)
: Enumerable.Empty<IActivity>();
return ExecuteAsync(workflow, resume, startActivities, cancellationToken);
}
private async Task<WorkflowExecutionContext> ExecuteAsync(
Workflow workflow,
bool resume,
IEnumerable<IActivity> startActivities = default,
CancellationToken cancellationToken = default)
{
var workflowExecutionContext = await CreateWorkflowExecutionContextAsync(
workflow,
startActivities,
cancellationToken
);
var start = !resume;
while (workflowExecutionContext.HasScheduledActivities)
{
var currentActivity = workflowExecutionContext.PopScheduledActivity();
var result = start
? await ExecuteActivityAsync(workflowExecutionContext, currentActivity, cancellationToken)
: await ResumeActivityAsync(workflowExecutionContext, currentActivity, cancellationToken);
if (result == null)
break;
await result.ExecuteAsync(this, workflowExecutionContext, cancellationToken);
workflowExecutionContext.IsFirstPass = false;
start = true;
}
await FinalizeWorkflowExecutionAsync(workflowExecutionContext, cancellationToken);
return workflowExecutionContext;
}
private async Task<IEnumerable<WorkflowExecutionContext>> StartManyAsync(
IEnumerable<(WorkflowDefinitionVersion, ActivityDefinition)> workflowDefinitions,
Variables input,
string correlationId,
CancellationToken cancellationToken1)
{
var executionContexts = new List<WorkflowExecutionContext>();
foreach (var (workflowDefinition, activityDefinition) in workflowDefinitions)
{
var startActivityIds = workflowDefinition.Activities
.Where(x => x.Id == activityDefinition.Id)
.Select(x => x.Id);
var workflow = workflowFactory.CreateWorkflow(workflowDefinition, input, correlationId: correlationId);
var executionContext = await ExecuteAsync(
workflow,
false,
startActivityIds,
cancellationToken1
);
executionContexts.Add(executionContext);
}
return executionContexts;
}
private async Task<IEnumerable<WorkflowExecutionContext>> ResumeManyAsync(
IEnumerable<(WorkflowInstance, ActivityInstance)> workflowInstances,
Variables input,
CancellationToken cancellationToken)
{
var executionContexts = new List<WorkflowExecutionContext>();
var workflowInstanceGroups = workflowInstances.GroupBy(x => x.Item1);
foreach (var workflowInstanceGroup in workflowInstanceGroups)
{
var workflowInstance = workflowInstanceGroup.Key;
var workflowDefinition = await workflowRegistry.GetWorkflowDefinitionAsync(
workflowInstance.DefinitionId,
VersionOptions.SpecificVersion(workflowInstance.Version),
cancellationToken
);
var workflow = workflowFactory.CreateWorkflow(workflowDefinition, input, workflowInstance);
foreach (var activity in workflowInstanceGroup)
{
var executionContext = await ExecuteAsync(
workflow,
true,
new[] { activity.Item2.Id },
cancellationToken
);
executionContexts.Add(executionContext);
}
}
return executionContexts;
}
private async Task FinalizeWorkflowExecutionAsync(
WorkflowExecutionContext workflowExecutionContext,
CancellationToken cancellationToken)
{
if (!workflowExecutionContext.Workflow.BlockingActivities.Any() &&
workflowExecutionContext.Workflow.IsExecuting())
{
workflowExecutionContext.Finish();
}
else
{
// Notify event handlers that halting activities are about to be executed.
await workflowEventHandlers.InvokeAsync(
async x => await x.InvokingHaltedActivitiesAsync(workflowExecutionContext, cancellationToken),
logger
);
// Invoke Halted event on activity drivers that halted the workflow.
while (workflowExecutionContext.HasScheduledHaltingActivities)
{
var currentActivity = workflowExecutionContext.PopScheduledHaltingActivity();
var result = await ExecuteActivityHaltedAsync(
workflowExecutionContext,
currentActivity,
cancellationToken
);
await result.ExecuteAsync(this, workflowExecutionContext, cancellationToken);
}
}
// Notify event handlers that workflow execution has ended.
await workflowEventHandlers.InvokeAsync(
async x => await x.WorkflowInvokedAsync(workflowExecutionContext, cancellationToken),
logger
);
}
private async Task<ActivityExecutionResult> ExecuteActivityAsync(
WorkflowExecutionContext workflowContext,
IActivity activity,
CancellationToken cancellationToken)
{
return await InvokeActivityAsync(
workflowContext,
activity,
() => activityInvoker.ExecuteAsync(workflowContext, activity, cancellationToken),
cancellationToken
);
}
private async Task<ActivityExecutionResult> ResumeActivityAsync(
WorkflowExecutionContext workflowContext,
IActivity activity,
CancellationToken cancellationToken)
{
return await InvokeActivityAsync(
workflowContext,
activity,
() => activityInvoker.ResumeAsync(workflowContext, activity, cancellationToken),
cancellationToken
);
}
private async Task<ActivityExecutionResult> InvokeActivityAsync(
WorkflowExecutionContext workflowContext,
IActivity activity,
Func<Task<ActivityExecutionResult>> executeAction,
CancellationToken cancellationToken)
{
try
{
if (cancellationToken.IsCancellationRequested)
{
workflowContext.Workflow.Status = WorkflowStatus.Aborted;
workflowContext.Workflow.FinishedAt = clock.GetCurrentInstant();
return null;
}
return await executeAction();
}
catch (Exception ex)
{
FaultWorkflow(workflowContext, activity, ex);
}
return null;
}
private async Task<ActivityExecutionResult> ExecuteActivityHaltedAsync(
WorkflowExecutionContext workflowContext,
IActivity activity,
CancellationToken cancellationToken)
{
return await InvokeActivityAsync(
workflowContext,
activity,
() => activityInvoker.HaltedAsync(workflowContext, activity, cancellationToken),
cancellationToken
);
}
private void FaultWorkflow(WorkflowExecutionContext workflowContext, IActivity activity, Exception ex)
{
logger.LogError(
ex,
"An unhandled error occurred while executing an activity. Putting the workflow in the faulted state."
);
workflowContext.Fault(activity, ex);
}
private async Task<WorkflowExecutionContext> CreateWorkflowExecutionContextAsync(
Workflow workflow,
IEnumerable<IActivity> startActivities,
CancellationToken cancellationToken)
{
var workflowExecutionContext = new WorkflowExecutionContext(workflow, clock, serviceProvider);
var startActivityList = startActivities?.ToList() ?? workflow.GetStartActivities().Take(1).ToList();
foreach (var startActivity in startActivityList)
{
if (await startActivity.CanExecuteAsync(workflowExecutionContext, cancellationToken))
workflowExecutionContext.ScheduleActivity(startActivity);
}
if (workflowExecutionContext.HasScheduledActivities)
{
workflow.BlockingActivities.RemoveWhere(startActivityList.Contains);
if (workflowExecutionContext.Workflow.Status == WorkflowStatus.Idle)
workflowExecutionContext.Start();
}
return workflowExecutionContext;
}
private async Task<IEnumerable<(WorkflowDefinitionVersion, ActivityDefinition)>> FilterRunningSingletonsAsync(
IEnumerable<(WorkflowDefinitionVersion, ActivityDefinition)> workflowDefinitions,
CancellationToken cancellationToken)
{
var definitions = workflowDefinitions.ToList();
var transients = definitions.Where(x => !x.Item1.IsSingleton).ToList();
var singletons = definitions.Where(x => x.Item1.IsSingleton).ToList();
var result = transients.ToList();
foreach (var definition in singletons)
{
var instances = await workflowInstanceStore.ListByStatusAsync(
definition.Item1.DefinitionId,
WorkflowStatus.Executing,
cancellationToken
);
if (!instances.Any())
{
result.Add(definition);
}
}
return result;
}
}
}

View file

@ -1,21 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Extensions;
using Elsa.Models;
using Elsa.Persistence;
using Elsa.Results;
using Elsa.Services.Extensions;
using Elsa.Services.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Elsa.Services
{
public class WorkflowInvoker : IWorkflowInvoker
internal class WorkflowInvoker : IWorkflowInvoker
{
private readonly IActivityInvoker activityInvoker;
private readonly IWorkflowFactory workflowFactory;
private readonly IWorkflowRegistry workflowRegistry;
private readonly IWorkflowInstanceStore workflowInstanceStore;
private readonly IEnumerable<IWorkflowEventHandler> workflowEventHandlers;
private readonly IClock clock;
private readonly IServiceProvider serviceProvider;
private readonly ILogger logger;
public WorkflowInvoker(IServiceProvider serviceProvider)
public WorkflowInvoker(
IActivityInvoker activityInvoker,
IWorkflowFactory workflowFactory,
IWorkflowRegistry workflowRegistry,
IWorkflowInstanceStore workflowInstanceStore,
IEnumerable<IWorkflowEventHandler> workflowEventHandlers,
IClock clock,
IServiceProvider serviceProvider,
ILogger<WorkflowInvoker> logger)
{
this.activityInvoker = activityInvoker;
this.workflowFactory = workflowFactory;
this.workflowRegistry = workflowRegistry;
this.workflowInstanceStore = workflowInstanceStore;
this.workflowEventHandlers = workflowEventHandlers;
this.clock = clock;
this.serviceProvider = serviceProvider;
this.logger = logger;
}
public Task<WorkflowExecutionContext> StartAsync(
@ -23,16 +51,7 @@ namespace Elsa.Services
IEnumerable<IActivity> startActivities = default,
CancellationToken cancellationToken = default)
{
return Invoke(x => x.StartAsync(workflow, startActivities, cancellationToken));
}
public Task<WorkflowExecutionContext> StartAsync<T>(
Variables input = default,
IEnumerable<string> startActivityIds = default,
string correlationId = default,
CancellationToken cancellationToken = default) where T : IWorkflow, new()
{
return Invoke(x => x.StartAsync<T>(input, startActivityIds, correlationId, cancellationToken));
return ExecuteAsync(workflow, false, startActivities, cancellationToken);
}
public Task<WorkflowExecutionContext> StartAsync(
@ -42,9 +61,22 @@ namespace Elsa.Services
string correlationId = default,
CancellationToken cancellationToken = default)
{
return Invoke(
x => x.StartAsync(workflowDefinition, input, startActivityIds, correlationId, cancellationToken)
);
var workflow = workflowFactory.CreateWorkflow(workflowDefinition, input, correlationId: correlationId);
var startActivities = workflow.Activities.Find(startActivityIds);
return ExecuteAsync(workflow, false, startActivities, cancellationToken);
}
public Task<WorkflowExecutionContext> StartAsync<T>(
Variables input = default,
IEnumerable<string> startActivityIds = default,
string correlationId = default,
CancellationToken cancellationToken = default) where T : IWorkflow, new()
{
var workflow = workflowFactory.CreateWorkflow<T>(input, correlationId: correlationId);
var startActivities = workflow.Activities.Find(startActivityIds);
return ExecuteAsync(workflow, false, startActivities, cancellationToken);
}
public Task<WorkflowExecutionContext> ResumeAsync(
@ -52,45 +84,374 @@ namespace Elsa.Services
IEnumerable<IActivity> startActivities = default,
CancellationToken cancellationToken = default)
{
return Invoke(x => x.ResumeAsync(workflow, startActivities, cancellationToken));
return ExecuteAsync(workflow, true, startActivities, cancellationToken);
}
public Task<WorkflowExecutionContext> ResumeAsync<T>(
WorkflowInstance workflowInstance,
Variables input = default,
Variables input = null,
IEnumerable<string> startActivityIds = default,
CancellationToken cancellationToken = default)
where T : IWorkflow, new()
CancellationToken cancellationToken = default) where T : IWorkflow, new()
{
return Invoke(x => x.ResumeAsync<T>(workflowInstance, input, startActivityIds, cancellationToken));
var workflow = workflowFactory.CreateWorkflow<T>(input, workflowInstance);
var startActivities = workflow.Activities.Find(startActivityIds);
return ExecuteAsync(workflow, true, startActivities, cancellationToken);
}
public Task<WorkflowExecutionContext> ResumeAsync(
public async Task<WorkflowExecutionContext> ResumeAsync(
WorkflowInstance workflowInstance,
Variables input = default,
Variables input = null,
IEnumerable<string> startActivityIds = default,
CancellationToken cancellationToken = default)
{
return Invoke(x => x.ResumeAsync(workflowInstance, input, startActivityIds, cancellationToken));
var definition = await workflowRegistry.GetWorkflowDefinitionAsync(
workflowInstance.DefinitionId,
VersionOptions.SpecificVersion(workflowInstance.Version),
cancellationToken);
var workflow = workflowFactory.CreateWorkflow(definition, input, workflowInstance);
return await ExecuteAsync(workflow, true, startActivityIds, cancellationToken);
}
public Task<IEnumerable<WorkflowExecutionContext>> TriggerAsync(
public async Task<IEnumerable<WorkflowExecutionContext>> TriggerAsync(
string activityType,
Variables input = default,
string correlationId = default,
Func<JObject, bool> activityStatePredicate = default,
CancellationToken cancellationToken = default)
{
return Invoke(x => x.TriggerAsync(activityType, input, correlationId, activityStatePredicate, cancellationToken));
var startedExecutionContexts = await StartManyAsync(
activityType,
input,
correlationId,
activityStatePredicate,
cancellationToken
);
var resumedExecutionContexts = await ResumeManyAsync(
activityType,
input,
correlationId,
activityStatePredicate,
cancellationToken
);
return startedExecutionContexts.Concat(resumedExecutionContexts);
}
private async Task<T> Invoke<T>(Func<IScopedWorkflowInvoker, Task<T>> action)
private async Task<IEnumerable<WorkflowExecutionContext>> ResumeManyAsync(
string activityType,
Variables input = default,
string correlationId = default,
Func<JObject, bool> activityStatePredicate = default,
CancellationToken cancellationToken = default)
{
using (var scope = serviceProvider.CreateScope())
var workflowInstances = await workflowInstanceStore
.ListByBlockingActivityAsync(activityType, correlationId, cancellationToken)
.ToListAsync();
if (activityStatePredicate != null)
workflowInstances = workflowInstances.Where(x => activityStatePredicate(x.Item2.State)).ToList();
return await ResumeManyAsync(
workflowInstances,
input,
cancellationToken
);
}
private async Task<IEnumerable<WorkflowExecutionContext>> StartManyAsync(
string activityType,
Variables input = default,
string correlationId = default,
Func<JObject, bool> activityStatePredicate = default,
CancellationToken cancellationToken = default)
{
var workflowDefinitions = await workflowRegistry.ListByStartActivityAsync(activityType, cancellationToken);
if (activityStatePredicate != null)
workflowDefinitions = workflowDefinitions.Where(x => activityStatePredicate(x.Item2.State));
workflowDefinitions = await FilterRunningSingletonsAsync(
workflowDefinitions,
cancellationToken
);
return await StartManyAsync(workflowDefinitions, input, correlationId, cancellationToken);
}
private Task<WorkflowExecutionContext> ExecuteAsync(
Workflow workflow,
bool resume,
IEnumerable<string> startActivityIds = default,
CancellationToken cancellationToken = default)
{
var startActivities = startActivityIds != null
? workflow.Activities.Find(startActivityIds)
: Enumerable.Empty<IActivity>();
return ExecuteAsync(workflow, resume, startActivities, cancellationToken);
}
private async Task<WorkflowExecutionContext> ExecuteAsync(
Workflow workflow,
bool resume,
IEnumerable<IActivity> startActivities = default,
CancellationToken cancellationToken = default)
{
var workflowExecutionContext = await CreateWorkflowExecutionContextAsync(
workflow,
startActivities,
cancellationToken
);
var start = !resume;
while (workflowExecutionContext.HasScheduledActivities)
{
var invoker = scope.ServiceProvider.GetRequiredService<IScopedWorkflowInvoker>();
return await action(invoker);
var currentActivity = workflowExecutionContext.PopScheduledActivity();
var result = start
? await ExecuteActivityAsync(workflowExecutionContext, currentActivity, cancellationToken)
: await ResumeActivityAsync(workflowExecutionContext, currentActivity, cancellationToken);
if (result == null)
break;
await result.ExecuteAsync(this, workflowExecutionContext, cancellationToken);
workflowExecutionContext.IsFirstPass = false;
start = true;
}
await FinalizeWorkflowExecutionAsync(workflowExecutionContext, cancellationToken);
return workflowExecutionContext;
}
private async Task<IEnumerable<WorkflowExecutionContext>> StartManyAsync(
IEnumerable<(WorkflowDefinitionVersion, ActivityDefinition)> workflowDefinitions,
Variables input,
string correlationId,
CancellationToken cancellationToken1)
{
var executionContexts = new List<WorkflowExecutionContext>();
foreach (var (workflowDefinition, activityDefinition) in workflowDefinitions)
{
var startActivityIds = workflowDefinition.Activities
.Where(x => x.Id == activityDefinition.Id)
.Select(x => x.Id);
var workflow = workflowFactory.CreateWorkflow(workflowDefinition, input, correlationId: correlationId);
var executionContext = await ExecuteAsync(
workflow,
false,
startActivityIds,
cancellationToken1
);
executionContexts.Add(executionContext);
}
return executionContexts;
}
private async Task<IEnumerable<WorkflowExecutionContext>> ResumeManyAsync(
IEnumerable<(WorkflowInstance, ActivityInstance)> workflowInstances,
Variables input,
CancellationToken cancellationToken)
{
var executionContexts = new List<WorkflowExecutionContext>();
var workflowInstanceGroups = workflowInstances.GroupBy(x => x.Item1);
foreach (var workflowInstanceGroup in workflowInstanceGroups)
{
var workflowInstance = workflowInstanceGroup.Key;
var workflowDefinition = await workflowRegistry.GetWorkflowDefinitionAsync(
workflowInstance.DefinitionId,
VersionOptions.SpecificVersion(workflowInstance.Version),
cancellationToken
);
var workflow = workflowFactory.CreateWorkflow(workflowDefinition, input, workflowInstance);
foreach (var activity in workflowInstanceGroup)
{
var executionContext = await ExecuteAsync(
workflow,
true,
new[] { activity.Item2.Id },
cancellationToken
);
executionContexts.Add(executionContext);
}
}
return executionContexts;
}
private async Task FinalizeWorkflowExecutionAsync(
WorkflowExecutionContext workflowExecutionContext,
CancellationToken cancellationToken)
{
if (!workflowExecutionContext.Workflow.BlockingActivities.Any() &&
workflowExecutionContext.Workflow.IsExecuting())
{
workflowExecutionContext.Finish();
}
else
{
// Notify event handlers that halting activities are about to be executed.
await workflowEventHandlers.InvokeAsync(
async x => await x.InvokingHaltedActivitiesAsync(workflowExecutionContext, cancellationToken),
logger
);
// Invoke Halted event on activity drivers that halted the workflow.
while (workflowExecutionContext.HasScheduledHaltingActivities)
{
var currentActivity = workflowExecutionContext.PopScheduledHaltingActivity();
var result = await ExecuteActivityHaltedAsync(
workflowExecutionContext,
currentActivity,
cancellationToken
);
await result.ExecuteAsync(this, workflowExecutionContext, cancellationToken);
}
}
// Notify event handlers that workflow execution has ended.
await workflowEventHandlers.InvokeAsync(
async x => await x.WorkflowInvokedAsync(workflowExecutionContext, cancellationToken),
logger
);
}
private async Task<ActivityExecutionResult> ExecuteActivityAsync(
WorkflowExecutionContext workflowContext,
IActivity activity,
CancellationToken cancellationToken)
{
return await InvokeActivityAsync(
workflowContext,
activity,
async () => await activityInvoker.ExecuteAsync(workflowContext, activity, cancellationToken),
cancellationToken
);
}
private async Task<ActivityExecutionResult> ResumeActivityAsync(
WorkflowExecutionContext workflowContext,
IActivity activity,
CancellationToken cancellationToken)
{
return await InvokeActivityAsync(
workflowContext,
activity,
async () => await activityInvoker.ResumeAsync(workflowContext, activity, cancellationToken),
cancellationToken
);
}
private async Task<ActivityExecutionResult> InvokeActivityAsync(
WorkflowExecutionContext workflowContext,
IActivity activity,
Func<Task<ActivityExecutionResult>> executeAction,
CancellationToken cancellationToken)
{
try
{
if (cancellationToken.IsCancellationRequested)
{
workflowContext.Workflow.Status = WorkflowStatus.Aborted;
workflowContext.Workflow.FinishedAt = clock.GetCurrentInstant();
return null;
}
return await executeAction();
}
catch (Exception ex)
{
FaultWorkflow(workflowContext, activity, ex);
}
return null;
}
private async Task<ActivityExecutionResult> ExecuteActivityHaltedAsync(
WorkflowExecutionContext workflowContext,
IActivity activity,
CancellationToken cancellationToken)
{
return await InvokeActivityAsync(
workflowContext,
activity,
async () => await activityInvoker.HaltedAsync(workflowContext, activity, cancellationToken),
cancellationToken
);
}
private void FaultWorkflow(WorkflowExecutionContext workflowContext, IActivity activity, Exception ex)
{
logger.LogError(
ex,
"An unhandled error occurred while executing an activity. Putting the workflow in the faulted state."
);
workflowContext.Fault(activity, ex);
}
private async Task<WorkflowExecutionContext> CreateWorkflowExecutionContextAsync(
Workflow workflow,
IEnumerable<IActivity> startActivities,
CancellationToken cancellationToken)
{
var workflowExecutionContext = new WorkflowExecutionContext(workflow, clock, serviceProvider);
var startActivityList = startActivities?.ToList() ?? workflow.GetStartActivities().Take(1).ToList();
foreach (var startActivity in startActivityList)
{
if (await startActivity.CanExecuteAsync(workflowExecutionContext, cancellationToken))
workflowExecutionContext.ScheduleActivity(startActivity);
}
if (workflowExecutionContext.HasScheduledActivities)
{
workflow.BlockingActivities.RemoveWhere(startActivityList.Contains);
if (workflowExecutionContext.Workflow.Status == WorkflowStatus.Idle)
workflowExecutionContext.Start();
}
return workflowExecutionContext;
}
private async Task<IEnumerable<(WorkflowDefinitionVersion, ActivityDefinition)>> FilterRunningSingletonsAsync(
IEnumerable<(WorkflowDefinitionVersion, ActivityDefinition)> workflowDefinitions,
CancellationToken cancellationToken)
{
var definitions = workflowDefinitions.ToList();
var transients = definitions.Where(x => !x.Item1.IsSingleton).ToList();
var singletons = definitions.Where(x => x.Item1.IsSingleton).ToList();
var result = transients.ToList();
foreach (var definition in singletons)
{
var instances = await workflowInstanceStore.ListByStatusAsync(
definition.Item1.DefinitionId,
WorkflowStatus.Executing,
cancellationToken
);
if (!instances.Any())
{
result.Add(definition);
}
}
return result;
}
}
}

View file

@ -19,13 +19,13 @@ namespace Elsa.Services
private readonly ISignal signal;
public WorkflowRegistry(
IServiceProvider serviceProvider,
IMemoryCache cache,
ISignal signal)
ISignal signal,
IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
this.cache = cache;
this.signal = signal;
this.serviceProvider = serviceProvider;
}
public async Task<IEnumerable<(WorkflowDefinitionVersion, ActivityDefinition)>> ListByStartActivityAsync(
@ -72,8 +72,7 @@ namespace Elsa.Services
});
}
private async Task<ICollection<WorkflowDefinitionVersion>> LoadWorkflowDefinitionsAsync(
CancellationToken cancellationToken)
private async Task<ICollection<WorkflowDefinitionVersion>> LoadWorkflowDefinitionsAsync(CancellationToken cancellationToken)
{
using var scope = serviceProvider.CreateScope();
var providers = scope.ServiceProvider.GetServices<IWorkflowProvider>();

View file

@ -3,9 +3,7 @@ using Elsa;
using Elsa.Activities.ControlFlow.Extensions;
using Elsa.Activities.UserTask.Extensions;
using Elsa.Activities.Workflows.Extensions;
using Elsa.Scripting.JavaScript;
using Elsa.Scripting.JavaScript.Extensions;
using Elsa.Scripting.Liquid;
using Elsa.Scripting.Liquid.Extensions;
// ReSharper disable once CheckNamespace

View file

@ -2,7 +2,6 @@ using System;
using Elsa.Dashboard.Options;
using Elsa.Metadata;
using Elsa.Services.Models;
using Elsa.WorkflowDesigner;
using Microsoft.Extensions.DependencyInjection;
using Scrutor;

View file

@ -1,7 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using Elsa.Metadata;
using Elsa.WorkflowDesigner.Models;
namespace Elsa.Dashboard.Options
{

View file

@ -25,7 +25,6 @@ namespace Elsa.Persistence.MongoDb.Extensions
RegisterEnumAsStringConvention();
BsonSerializer.RegisterSerializer(new JObjectSerializer());
BsonSerializer.RegisterSerializer(new WorkflowExecutionScopeSerializer());
//BsonSerializer.RegisterSerializer(new WorkflowInstanceSerializer());
elsaBuilder.Services
.AddSingleton(sp => CreateDbClient(configuration, connectionStringName))
@ -51,7 +50,7 @@ namespace Elsa.Persistence.MongoDb.Extensions
{
configuration.Services
.AddMongoDbCollection<WorkflowInstance>("WorkflowInstances")
.Replace<IWorkflowInstanceStore, MongoWorkflowInstanceStore>(ServiceLifetime.Scoped);
.AddScoped<IWorkflowInstanceStore, MongoWorkflowInstanceStore>();
return configuration;
}
@ -61,7 +60,7 @@ namespace Elsa.Persistence.MongoDb.Extensions
{
configuration.Services
.AddMongoDbCollection<WorkflowDefinitionVersion>("WorkflowDefinitions")
.Replace<IWorkflowDefinitionStore, MongoWorkflowDefinitionStore>(ServiceLifetime.Scoped);
.AddScoped<IWorkflowDefinitionStore, MongoWorkflowDefinitionStore>();
return configuration;
}

View file

@ -1,6 +1,6 @@
using Elsa.Extensions;
using Elsa.Scripting.JavaScript.Services;
using Elsa.Services;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Scripting.JavaScript.Extensions
@ -11,7 +11,7 @@ namespace Elsa.Scripting.JavaScript.Extensions
{
return services
.TryAddProvider<IExpressionEvaluator, JavaScriptExpressionEvaluator>(ServiceLifetime.Scoped)
.AddMediatR(typeof(JavaScriptServiceCollectionExtensions));
.AddNotificationHandlers(typeof(JavaScriptServiceCollectionExtensions));
}
}
}

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

View file

@ -1,8 +1,8 @@
using Elsa.Extensions;
using Elsa.Scripting.Liquid.Filters;
using Elsa.Scripting.Liquid.Options;
using Elsa.Scripting.Liquid.Services;
using Elsa.Services;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Scripting.Liquid.Extensions
@ -14,7 +14,7 @@ namespace Elsa.Scripting.Liquid.Extensions
return services
.TryAddProvider<IExpressionEvaluator, LiquidExpressionEvaluator>(ServiceLifetime.Scoped)
.AddMemoryCache()
.AddMediatR(typeof(LiquidServiceCollectionExtensions))
.AddNotificationHandlers(typeof(LiquidServiceCollectionExtensions))
.AddScoped<ILiquidTemplateManager, LiquidTemplateManager>()
.AddLiquidFilter<JsonFilter>("json");
}

View file

@ -9,6 +9,7 @@ using Elsa.Services.Models;
using Fluid;
using Fluid.Values;
using MediatR;
using Newtonsoft.Json.Linq;
namespace Elsa.Scripting.Liquid.Handlers
{
@ -17,11 +18,14 @@ namespace Elsa.Scripting.Liquid.Handlers
static CommonLiquidContextHandler()
{
FluidValue.SetTypeMapping<ExpandoObject>(x => new ObjectValue(x));
FluidValue.SetTypeMapping<JObject>(o => new ObjectValue(o));
FluidValue.SetTypeMapping<JValue>(o => FluidValue.Create(o.Value));
}
public Task Handle(EvaluatingLiquidExpression notification, CancellationToken cancellationToken)
{
var context = notification.TemplateContext;
context.MemberAccessStrategy.Register<LiquidPropertyAccessor, FluidValue>((x, name) => x.GetValueAsync(name));
context.MemberAccessStrategy.Register<WorkflowExecutionContext, LiquidPropertyAccessor>("Input", x => new LiquidPropertyAccessor(name => ToFluidValue(x.Workflow.Input, name)));
context.MemberAccessStrategy.Register<WorkflowExecutionContext, LiquidPropertyAccessor>("Output", x => new LiquidPropertyAccessor(name => ToFluidValue(x.Workflow.Output, name)));
@ -30,10 +34,11 @@ namespace Elsa.Scripting.Liquid.Handlers
context.MemberAccessStrategy.Register<LiquidObjectAccessor<IActivity>, LiquidObjectAccessor<object>>((x, activityName) => new LiquidObjectAccessor<object>(outputKey => GetActivityOutput(x, activityName, outputKey)));
context.MemberAccessStrategy.Register<LiquidObjectAccessor<object>, object>((x, name) => x.GetValueAsync(name));
context.MemberAccessStrategy.Register<ExpandoObject, object>((x, name) => ((IDictionary<string, object>)x)[name]);
context.MemberAccessStrategy.Register<JObject, object>((source, name) => source[name]);
return Task.CompletedTask;
}
private Task<FluidValue> ToFluidValue(IDictionary<string, object> dictionary, string key)
{
return Task.FromResult(!dictionary.ContainsKey(key) ? default : FluidValue.Create(dictionary[key]));

View file

@ -34,6 +34,7 @@ namespace Elsa.Scripting.Liquid.Services
private async Task<TemplateContext> CreateTemplateContextAsync(WorkflowExecutionContext workflowContext)
{
var context = new TemplateContext();
context.SetValue("WorkflowExecutionContext", workflowContext);
await mediator.Publish(new EvaluatingLiquidExpression(context, workflowContext));
context.Model = workflowContext;
return context;