diff --git a/Samples.sln b/Samples.sln index 6854d488d..fc4741eeb 100644 --- a/Samples.sln +++ b/Samples.sln @@ -162,6 +162,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.WhileLoopWorke EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.CustomAttributesChildWorker", "src\samples\worker\Elsa.Samples.CustomAttributesChildWorker\Elsa.Samples.CustomAttributesChildWorker.csproj", "{AEA00027-F280-43A4-AC60-E2AF3014CF34}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.CustomActivityTypeProviders", "src\samples\aspnet\Elsa.Samples.CustomActivityTypeProviders\Elsa.Samples.CustomActivityTypeProviders.csproj", "{E0090EF3-C4A7-4262-9300-1D395BE25DD0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -393,6 +395,10 @@ Global {AEA00027-F280-43A4-AC60-E2AF3014CF34}.Debug|Any CPU.Build.0 = Debug|Any CPU {AEA00027-F280-43A4-AC60-E2AF3014CF34}.Release|Any CPU.ActiveCfg = Release|Any CPU {AEA00027-F280-43A4-AC60-E2AF3014CF34}.Release|Any CPU.Build.0 = Release|Any CPU + {E0090EF3-C4A7-4262-9300-1D395BE25DD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0090EF3-C4A7-4262-9300-1D395BE25DD0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0090EF3-C4A7-4262-9300-1D395BE25DD0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0090EF3-C4A7-4262-9300-1D395BE25DD0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -470,6 +476,7 @@ Global {FD2D1BD0-1229-4DCF-BE70-6BFD396DD489} = {E42743A0-FBDD-4150-9D53-6000496D9B87} {EA3832C4-8079-4E84-AF8C-12744E4EFD44} = {E42743A0-FBDD-4150-9D53-6000496D9B87} {AEA00027-F280-43A4-AC60-E2AF3014CF34} = {E42743A0-FBDD-4150-9D53-6000496D9B87} + {E0090EF3-C4A7-4262-9300-1D395BE25DD0} = {22E75696-6FE9-436A-9097-EE21C603F818} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158} diff --git a/Samples.sln.DotSettings b/Samples.sln.DotSettings index 08cde52e6..0c28b1630 100644 --- a/Samples.sln.DotSettings +++ b/Samples.sln.DotSettings @@ -9,6 +9,8 @@ True True True + True + True True True True diff --git a/src/core/Elsa.Abstractions/ActivityProviders/ActivityType.cs b/src/core/Elsa.Abstractions/ActivityProviders/ActivityType.cs index 3283b06aa..3761df5af 100644 --- a/src/core/Elsa.Abstractions/ActivityProviders/ActivityType.cs +++ b/src/core/Elsa.Abstractions/ActivityProviders/ActivityType.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Elsa.ActivityResults; using Elsa.Services.Models; @@ -23,6 +24,11 @@ namespace Elsa.ActivityProviders /// public string? Description { get; set; } + /// + /// Anything you want to store with this activity type. + /// + public IDictionary Annotations { get; set; } = new Dictionary(); + /// /// Returns a value of whether the specified activity can execute. /// diff --git a/src/core/Elsa.Abstractions/Extensions/ActivityActivatorExtensions.cs b/src/core/Elsa.Abstractions/Extensions/ActivityActivatorExtensions.cs new file mode 100644 index 000000000..bbb86e3c1 --- /dev/null +++ b/src/core/Elsa.Abstractions/Extensions/ActivityActivatorExtensions.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Elsa.Services; +using Elsa.Services.Models; + +namespace Elsa +{ + public static class ActivityActivatorExtensions + { + public static async Task ActivateActivityAsync(this IActivityActivator activator, ActivityExecutionContext context) where T : IActivity => (T) await activator.ActivateActivityAsync(context, typeof(T)); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/IActivityActivator.cs b/src/core/Elsa.Abstractions/Services/IActivityActivator.cs index 03c122700..d4d384600 100644 --- a/src/core/Elsa.Abstractions/Services/IActivityActivator.cs +++ b/src/core/Elsa.Abstractions/Services/IActivityActivator.cs @@ -1,21 +1,11 @@ -// using System; -// using System.Collections.Generic; -// using System.Threading; -// using System.Threading.Tasks; -// using Elsa.Models; -// using Elsa.Services.Models; -// using Microsoft.Extensions.DependencyInjection; -// -// namespace Elsa.Services -// { -// public interface IActivityActivator -// { -// Task ActivateActivity(IServiceScope serviceScope, string activityTypeName, Action? setup = default, CancellationToken cancellationToken = default); -// IActivity ActivateActivity(IServiceScope serviceScope, IActivityBlueprint activityBlueprint); -// T ActivateActivity(IServiceScope serviceScope, Action? configure = default) where T : class, IActivity; -// IActivity ActivateActivity(IServiceScope serviceScope, Type type); -// IActivity ActivateActivity(IServiceScope serviceScope, ActivityDefinition activityDefinition); -// IEnumerable GetActivityTypes(); -// Type? GetActivityType(string activityTypeName); -// } -// } \ No newline at end of file +using System; +using System.Threading.Tasks; +using Elsa.Services.Models; + +namespace Elsa.Services +{ + public interface IActivityActivator + { + Task ActivateActivityAsync(ActivityExecutionContext context, Type type); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/IWorkflowInterruptor.cs b/src/core/Elsa.Abstractions/Services/IWorkflowInterruptor.cs new file mode 100644 index 000000000..a5db5aa1d --- /dev/null +++ b/src/core/Elsa.Abstractions/Services/IWorkflowInterruptor.cs @@ -0,0 +1,18 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Models; +using Elsa.Services.Models; + +namespace Elsa.Services +{ + /// + /// Causes a suspended workflow's trigger to be interrupted + /// + public interface IWorkflowInterruptor + { + Task InterruptActivityAsync(WorkflowInstance workflowInstance, string activityId, object? input = default, CancellationToken cancellationToken = default); + Task InterruptActivityAsync(IWorkflowBlueprint workflowBlueprint, WorkflowInstance workflowInstance, string activityId, object? input = default, CancellationToken cancellationToken = default); + Task InterruptActivityTypeAsync(IWorkflowBlueprint workflowBlueprint, WorkflowInstance workflowInstance, string activityType, object? input = default, CancellationToken cancellationToken = default); + Task InterruptActivityTypeAsync(WorkflowInstance workflowInstance, string activityType, object? input = default, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/ActivityTypeProviders/TypeBasedActivityProvider.cs b/src/core/Elsa.Core/ActivityTypeProviders/TypeBasedActivityProvider.cs index 987c33736..8ce39be0f 100644 --- a/src/core/Elsa.Core/ActivityTypeProviders/TypeBasedActivityProvider.cs +++ b/src/core/Elsa.Core/ActivityTypeProviders/TypeBasedActivityProvider.cs @@ -15,11 +15,13 @@ namespace Elsa.ActivityTypeProviders public class TypeBasedActivityProvider : IActivityTypeProvider { private readonly IActivityDescriber _activityDescriber; + private readonly IActivityActivator _activityActivator; private readonly Lazy> _lazyActivityTypeLookup; - public TypeBasedActivityProvider(IServiceProvider serviceProvider, IActivityDescriber activityDescriber) + public TypeBasedActivityProvider(IServiceProvider serviceProvider, IActivityDescriber activityDescriber, IActivityActivator activityActivator) { _activityDescriber = activityDescriber; + _activityActivator = activityActivator; _lazyActivityTypeLookup = new Lazy>( () => @@ -63,13 +65,6 @@ namespace Elsa.ActivityTypeProviders private IEnumerable GetActivityTypes() => ActivityTypeLookup.Values.ToList(); - private static async Task ActivateActivity(ActivityExecutionContext context, Type type) - { - var activity = (IActivity) ActivatorUtilities.GetServiceOrCreateInstance(context.ServiceScope.ServiceProvider, type); - activity.Data = context.ActivityInstance.Data; - activity.Id = context.ActivityInstance.Id; - await context.WorkflowExecutionContext.WorkflowBlueprint.ActivityPropertyProviders.SetActivityPropertiesAsync(activity, context, context.CancellationToken); - return activity; - } + private Task ActivateActivity(ActivityExecutionContext context, Type type) => _activityActivator.ActivateActivityAsync(context, type); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index ee344e4af..d1c4d1cf2 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -14,6 +14,7 @@ using Elsa.Converters; using Elsa.Data.Extensions; using Elsa.Expressions; using Elsa.Extensions; +using Elsa.HostedServices; using Elsa.Indexes; using Elsa.Mapping; using Elsa.Metadata; @@ -25,6 +26,7 @@ using Elsa.Triggers; using Elsa.WorkflowProviders; using MediatR; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; using NodaTime; using Rebus.ServiceProvider; @@ -54,27 +56,27 @@ namespace Microsoft.Extensions.DependencyInjection return services; } - public static IServiceCollection AddActivity(this IServiceCollection services) - where T : class, IActivity - { - return services + public static IServiceCollection AddActivity(this IServiceCollection services) where T : class, IActivity => + services .AddTransient() .AddTransient(sp => sp.GetRequiredService()); - } public static IServiceCollection AddWorkflow(this IServiceCollection services) where T : class, IWorkflow { - return services - .AddSingleton() - .AddSingleton(sp => sp.GetRequiredService()); + services.TryAddSingleton(); + services.TryAddSingleton(sp => sp.GetRequiredService()); + return services; } - public static IServiceCollection AddWorkflow(this IServiceCollection services, IWorkflow workflow) - { - return services + public static IServiceCollection AddWorkflow(this IServiceCollection services, IWorkflow workflow) => + services .AddSingleton(workflow.GetType(), workflow) .AddTransient(sp => workflow); - } + + public static IServiceCollection StartWorkflow(this IServiceCollection services) where T : class, IWorkflow => + services + .AddWorkflow() + .AddHostedService>(); private static IServiceCollection AddMediatR(this ElsaOptions options) => options.Services.AddMediatR(mediatr => mediatr.AsScoped(), typeof(IActivity)); @@ -96,7 +98,9 @@ namespace Microsoft.Extensions.DependencyInjection .AddScoped() .AddScoped() .AddSingleton() + .AddSingleton() .AddScoped() + .AddScoped() .AddSingleton() .AddSingleton() .AddSingleton() @@ -110,7 +114,6 @@ namespace Microsoft.Extensions.DependencyInjection .AddIndexProvider() .AddIndexProvider() .AddStartupRunner() - //.AddSingleton() .AddSingleton() .AddSingleton() .AddWorkflowProvider() diff --git a/src/core/Elsa.Core/Extensions/WorkflowDefinitionManagerExtensions.cs b/src/core/Elsa.Core/Extensions/WorkflowDefinitionManagerExtensions.cs index 54ce96ecd..8b17af40d 100644 --- a/src/core/Elsa.Core/Extensions/WorkflowDefinitionManagerExtensions.cs +++ b/src/core/Elsa.Core/Extensions/WorkflowDefinitionManagerExtensions.cs @@ -36,5 +36,13 @@ namespace Elsa.Extensions manager .Query(x => x.WorkflowStatus == workflowStatus) .ListAsync(); + + public static Task> ListByBlockingActivityTypeAsync( + this IWorkflowInstanceManager manager, + string activityType, + CancellationToken cancellationToken = default) => + manager + .Query(x => x.ActivityType == activityType) + .ListAsync(); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/HostedServices/StartWorkflow.cs b/src/core/Elsa.Core/HostedServices/StartWorkflow.cs new file mode 100644 index 000000000..651b15bda --- /dev/null +++ b/src/core/Elsa.Core/HostedServices/StartWorkflow.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Builders; +using Elsa.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Elsa.HostedServices +{ + /// + /// A hosted service that starts a workflow of the specified type. + /// + public class StartWorkflow : IHostedService where T : IWorkflow + { + private readonly IServiceProvider _serviceProvider; + public StartWorkflow(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + + public async Task StartAsync(CancellationToken cancellationToken) + { + using var scope = _serviceProvider.CreateScope(); + var workflowRunner = scope.ServiceProvider.GetRequiredService(); + await workflowRunner.RunWorkflowAsync(cancellationToken: cancellationToken); + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Indexes/WorkflowInstanceIndex.cs b/src/core/Elsa.Core/Indexes/WorkflowInstanceIndex.cs index 6705bc4d6..0a227fff4 100644 --- a/src/core/Elsa.Core/Indexes/WorkflowInstanceIndex.cs +++ b/src/core/Elsa.Core/Indexes/WorkflowInstanceIndex.cs @@ -46,17 +46,23 @@ namespace Elsa.Indexes context.For() .Map( - workflowInstance => workflowInstance.BlockingActivities - .Select( - activity => new WorkflowInstanceBlockingActivitiesIndex - { - ActivityId = activity.ActivityId, - ActivityType = activity.ActivityType, - CorrelationId = workflowInstance.CorrelationId, - TenantId = workflowInstance.TenantId, - WorkflowStatus = workflowInstance.Status, - CreatedAt = workflowInstance.CreatedAt.ToDateTimeOffset() - })); + workflowInstance => + { + if (workflowInstance.Status != WorkflowStatus.Suspended || !workflowInstance.BlockingActivities.Any()) + return default; + + return workflowInstance.BlockingActivities + .Select( + activity => new WorkflowInstanceBlockingActivitiesIndex + { + ActivityId = activity.ActivityId, + ActivityType = activity.ActivityType, + CorrelationId = workflowInstance.CorrelationId, + TenantId = workflowInstance.TenantId, + WorkflowStatus = workflowInstance.Status, + CreatedAt = workflowInstance.CreatedAt.ToDateTimeOffset() + }); + }); } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/ActivityActivator.cs b/src/core/Elsa.Core/Services/ActivityActivator.cs index affb924cc..6a4554d3e 100644 --- a/src/core/Elsa.Core/Services/ActivityActivator.cs +++ b/src/core/Elsa.Core/Services/ActivityActivator.cs @@ -1,71 +1,19 @@ -// using System; -// using System.Collections.Generic; -// using System.Linq; -// using System.Threading; -// using System.Threading.Tasks; -// using Elsa.ActivityProviders; -// using Elsa.Models; -// using Elsa.Services.Models; -// using Microsoft.Extensions.DependencyInjection; -// -// namespace Elsa.Services -// { -// public class ActivityActivator : IActivityActivator -// { -// private readonly IActivityTypeService _activityTypeService; -// -// public ActivityActivator(IActivityTypeService activityTypeService) -// { -// _activityTypeService = activityTypeService; -// } -// -// public async Task ActivateActivity(IServiceScope serviceScope, string activityTypeName, Action? setup, CancellationToken cancellationToken) -// { -// var activityType = await _activityTypeService.GetActivityTypeAsync(activityTypeName, cancellationToken); -// var activity = activityType. -// -// setup?.Invoke(activity); -// return activity; -// } -// -// public T ActivateActivity(IServiceScope serviceScope, Action? setup = null) where T : class, IActivity -// { -// var activity = ActivatorUtilities.GetServiceOrCreateInstance(serviceScope.ServiceProvider); -// setup?.Invoke(activity); -// return activity; -// } -// -// public IActivity ActivateActivity(IServiceScope serviceScope, Type type) => (IActivity)ActivatorUtilities.GetServiceOrCreateInstance(serviceScope.ServiceProvider, type); -// -// public IActivity ActivateActivity(IServiceScope serviceScope, IActivityBlueprint activityBlueprint) -// { -// return ActivateActivity( -// serviceScope, -// activityBlueprint.Type, -// activity => -// { -// activity.Id = activityBlueprint.Id; -// activity.Name = activityBlueprint.Name; -// activity.PersistWorkflow = activityBlueprint.PersistWorkflow; -// activity.SaveWorkflowContext = activityBlueprint.SaveWorkflowContext; -// activity.LoadWorkflowContext = activityBlueprint.LoadWorkflowContext; -// }); -// } -// -// public IActivity ActivateActivity(IServiceScope serviceScope, ActivityDefinition activityDefinition) -// { -// var activity = ActivateActivity(serviceScope, activityDefinition.Type); -// activity.Description = activityDefinition.Description; -// activity.Id = activityDefinition.ActivityId; -// activity.Name = activityDefinition.Name; -// activity.DisplayName = activityDefinition.DisplayName; -// activity.PersistWorkflow = activityDefinition.PersistWorkflow; -// activity.LoadWorkflowContext = activityDefinition.LoadWorkflowContext; -// activity.SaveWorkflowContext = activityDefinition.SaveWorkflowContext; -// return activity; -// } -// -// public IEnumerable GetActivityTypes() => ActivityTypeLookup.Values.ToList(); -// public Type? GetActivityType(string activityTypeName) => ActivityTypeLookup.ContainsKey(activityTypeName) ? ActivityTypeLookup[activityTypeName] : default; -// } -// } \ No newline at end of file +using System; +using System.Threading.Tasks; +using Elsa.Services.Models; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Services +{ + public class ActivityActivator : IActivityActivator + { + public async Task ActivateActivityAsync(ActivityExecutionContext context, Type type) + { + var activity = (IActivity) ActivatorUtilities.GetServiceOrCreateInstance(context.ServiceScope.ServiceProvider, type); + activity.Data = context.ActivityInstance.Data; + activity.Id = context.ActivityInstance.Id; + await context.WorkflowExecutionContext.WorkflowBlueprint.ActivityPropertyProviders.SetActivityPropertiesAsync(activity, context, context.CancellationToken); + return activity; + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/WorkflowInterruptor.cs b/src/core/Elsa.Core/Services/WorkflowInterruptor.cs new file mode 100644 index 000000000..a051f390c --- /dev/null +++ b/src/core/Elsa.Core/Services/WorkflowInterruptor.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Exceptions; +using Elsa.Models; +using Elsa.Services.Models; + +namespace Elsa.Services +{ + public class WorkflowInterruptor : IWorkflowInterruptor + { + private readonly IWorkflowRunner _workflowRunner; + private readonly IWorkflowRegistry _workflowRegistry; + + public WorkflowInterruptor(IWorkflowRunner workflowRunner, IWorkflowRegistry workflowRegistry) + { + _workflowRunner = workflowRunner; + _workflowRegistry = workflowRegistry; + } + + public async Task InterruptActivityAsync(WorkflowInstance workflowInstance, string activityId, object? input, CancellationToken cancellationToken) + { + var workflowBlueprint = await GetWorkflowBlueprintAsync(workflowInstance, cancellationToken); + return await InterruptActivityAsync(workflowBlueprint!, workflowInstance, activityId, input, cancellationToken); + } + + public async Task InterruptActivityAsync(IWorkflowBlueprint workflowBlueprint, WorkflowInstance workflowInstance, string activityId, object? input, CancellationToken cancellationToken) + { + if (workflowInstance.Status != WorkflowStatus.Suspended) + throw new WorkflowException("Cannot interrupt workflows that are not in the Suspended state."); + + var blockingActivity = workflowInstance.BlockingActivities.SingleOrDefault(x => x.ActivityId == activityId); + + if (blockingActivity == null) + throw new WorkflowException($"No blocking activity with ID {activityId} found."); + + return await _workflowRunner.RunWorkflowAsync(workflowBlueprint, workflowInstance, activityId, input, cancellationToken); + } + + public async Task InterruptActivityTypeAsync(IWorkflowBlueprint workflowBlueprint, WorkflowInstance workflowInstance, string activityType, object? input, CancellationToken cancellationToken) + { + var blockingActivities = workflowInstance.BlockingActivities.Where(x => x.ActivityType == activityType).ToList(); + + foreach (var blockingActivity in blockingActivities) + workflowInstance = await InterruptActivityAsync(workflowBlueprint, workflowInstance, blockingActivity.ActivityId, input, cancellationToken); + + return workflowInstance; + } + + public async Task InterruptActivityTypeAsync(WorkflowInstance workflowInstance, string activityType, object? input, CancellationToken cancellationToken) + { + var workflowBlueprint = await GetWorkflowBlueprintAsync(workflowInstance, cancellationToken); + return await InterruptActivityTypeAsync(workflowBlueprint!, workflowInstance, activityType, input, cancellationToken); + } + + private async Task GetWorkflowBlueprintAsync(WorkflowInstance workflowInstance, CancellationToken cancellationToken) => + await _workflowRegistry.GetWorkflowAsync(workflowInstance.WorkflowDefinitionId, workflowInstance.TenantId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken); + } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Activities/Sleep.cs b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Activities/Sleep.cs new file mode 100644 index 000000000..0b11ddd6c --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Activities/Sleep.cs @@ -0,0 +1,14 @@ +using Elsa.Activities.Timers; +using Elsa.Activities.Timers.Services; +using Elsa.Services; +using NodaTime; + +namespace Elsa.Samples.CustomActivityTypeProviders.Activities +{ + public class Sleep : TimerEvent + { + public Sleep(IWorkflowInstanceManager workflowInstanceManager, IWorkflowScheduler workflowScheduler, IClock clock) : base(workflowInstanceManager, workflowScheduler, clock) + { + } + } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Elsa.Samples.CustomActivityTypeProviders.csproj b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Elsa.Samples.CustomActivityTypeProviders.csproj new file mode 100644 index 000000000..5e9d9a675 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Elsa.Samples.CustomActivityTypeProviders.csproj @@ -0,0 +1,12 @@ + + + + net5.0 + latest + + + + + + + diff --git a/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Endpoints/WakeUp.cs b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Endpoints/WakeUp.cs new file mode 100644 index 000000000..6b4223d45 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Endpoints/WakeUp.cs @@ -0,0 +1,37 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Extensions; +using Elsa.Samples.CustomActivityTypeProviders.Activities; +using Elsa.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Elsa.Samples.CustomActivityTypeProviders.Endpoints +{ + [ApiController] + [Route("wakeup")] + public class WakeUp : Controller + { + private readonly IWorkflowInterruptor _workflowInterruptor; + private readonly IWorkflowInstanceManager _workflowInstanceManager; + + public WakeUp(IWorkflowInterruptor workflowInterruptor, IWorkflowInstanceManager workflowInstanceManager) + { + _workflowInterruptor = workflowInterruptor; + _workflowInstanceManager = workflowInstanceManager; + } + + [HttpGet] + public async Task Handle(CancellationToken cancellationToken) + { + // Get all workflows blocked on the "Sleep" activity. + var suspendedWorkflows = (await _workflowInstanceManager.ListByBlockingActivityTypeAsync(nameof(Sleep), cancellationToken)).ToList(); + + // Interrupt each workflow by triggering the "Sleep" activity. + foreach (var workflowInstance in suspendedWorkflows) + await _workflowInterruptor.InterruptActivityTypeAsync(workflowInstance, nameof(Sleep), cancellationToken: cancellationToken); + + return Ok($"Interrupted {suspendedWorkflows.Count} workflows."); + } + } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Program.cs b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Program.cs new file mode 100644 index 000000000..6a51a0b7b --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Program.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Elsa.Samples.CustomActivityTypeProviders +{ + 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()); + } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Properties/launchSettings.json b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Properties/launchSettings.json new file mode 100644 index 000000000..065f277c1 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "Elsa.Samples.CustomActivityTypeProviders": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": false, + "applicationUrl": "https://localhost:6171", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Startup.cs b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Startup.cs new file mode 100644 index 000000000..05397a808 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Startup.cs @@ -0,0 +1,33 @@ +using Elsa.Samples.CustomActivityTypeProviders.Activities; +using Elsa.Samples.CustomActivityTypeProviders.Workflows; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Samples.CustomActivityTypeProviders +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + + services + .AddElsa() + .AddConsoleActivities() + .AddHttpActivities() + .AddTimerActivities() + .AddActivity() + .StartWorkflow(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseDeveloperExceptionPage(); + app.UseHttpActivities(); + app.UseRouting(); + app.UseEndpoints(endpoints => endpoints.MapControllers()); + app.UseWelcomePage(); + } + } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Workflows/InterruptableWorkflow.cs b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Workflows/InterruptableWorkflow.cs new file mode 100644 index 000000000..dcf20342b --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/Workflows/InterruptableWorkflow.cs @@ -0,0 +1,19 @@ +using Elsa.Activities.Console; +using Elsa.Builders; +using Elsa.Samples.CustomActivityTypeProviders.Activities; +using NodaTime; + +namespace Elsa.Samples.CustomActivityTypeProviders.Workflows +{ + public class InterruptableWorkflow : IWorkflow + { + public void Build(IWorkflowBuilder workflow) + { + workflow + .WriteLine("This workflow will sleep for 5 minutes before it continues.") + .WriteLine("Can't wait that long? Send me a message at https://localhost:6171/wakeup.") + .Then(sleep => sleep.Set(x => x.Timeout, Duration.FromMinutes(5))) + .WriteLine("Done."); + } + } +} \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/appsettings.json b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/appsettings.json new file mode 100644 index 000000000..d9d9a9bff --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/interrupt.http b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/interrupt.http new file mode 100644 index 000000000..1e6ebc03d --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.CustomActivityTypeProviders/interrupt.http @@ -0,0 +1 @@ +GET https://localhost:6171/wakeup \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.HelloWorldHttp/Startup.cs b/src/samples/aspnet/Elsa.Samples.HelloWorldHttp/Startup.cs index 38dd3705c..b4c080842 100644 --- a/src/samples/aspnet/Elsa.Samples.HelloWorldHttp/Startup.cs +++ b/src/samples/aspnet/Elsa.Samples.HelloWorldHttp/Startup.cs @@ -9,7 +9,7 @@ namespace Elsa.Samples.HelloWorldHttp public void ConfigureServices(IServiceCollection services) { services - .AddElsa(option => option.UsePersistence(db => db.UseSqLite("Data Source=elsa.db;Cache=Shared"))) + .AddElsa() .AddHttpActivities() .AddWorkflow(); }