Add Conductor project and SendCommand activity
This commit is contained in:
parent
0f8a0e9f3d
commit
589cab520b
14
Elsa.sln
14
Elsa.sln
|
|
@ -297,6 +297,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Providers.Redis", "src
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.Redis", "src\locking\Elsa.DistributedLocking.Redis\Elsa.DistributedLocking.Redis.csproj", "{D1644D32-AA51-4D10-942E-F2722DB963B4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Conductor", "src\activities\Elsa.Activities.Conductor\Elsa.Activities.Conductor.csproj", "{E62077E1-59F4-4937-B45B-59F86417BCF8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleClientApp.Web", "src\samples\server\SampleClientApp.Web\SampleClientApp.Web.csproj", "{7756E22A-AD59-46C1-A019-1D28D7217347}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -695,6 +699,14 @@ Global
|
|||
{D1644D32-AA51-4D10-942E-F2722DB963B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D1644D32-AA51-4D10-942E-F2722DB963B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D1644D32-AA51-4D10-942E-F2722DB963B4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E62077E1-59F4-4937-B45B-59F86417BCF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E62077E1-59F4-4937-B45B-59F86417BCF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E62077E1-59F4-4937-B45B-59F86417BCF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E62077E1-59F4-4937-B45B-59F86417BCF8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7756E22A-AD59-46C1-A019-1D28D7217347}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7756E22A-AD59-46C1-A019-1D28D7217347}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7756E22A-AD59-46C1-A019-1D28D7217347}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7756E22A-AD59-46C1-A019-1D28D7217347}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -830,6 +842,8 @@ Global
|
|||
{EC5D4AFD-3F7F-4B51-9C38-F18C60618731} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
|
||||
{9ACA06DA-9AFE-41A2-8109-EFED8B9B14A1} = {EC5D4AFD-3F7F-4B51-9C38-F18C60618731}
|
||||
{D1644D32-AA51-4D10-942E-F2722DB963B4} = {DBBD242E-4437-4BDB-919F-A70839BE75FA}
|
||||
{E62077E1-59F4-4937-B45B-59F86417BCF8} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
{7756E22A-AD59-46C1-A019-1D28D7217347} = {BBCFC623-2113-464D-8A1F-3CCDABF1241C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Design;
|
||||
using Elsa.Expressions;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Activities.Conductor
|
||||
{
|
||||
[Activity(
|
||||
Category = "Conductor",
|
||||
Description = "Sends a command to your application.",
|
||||
Outcomes = new[] { OutcomeNames.Done }
|
||||
)]
|
||||
public class SendCommand : Activity
|
||||
{
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
|
||||
public SendCommand(IEventPublisher eventPublisher)
|
||||
{
|
||||
_eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Command",
|
||||
Hint = "The command to send.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public string CommandName { get; set; } = default!;
|
||||
|
||||
[ActivityInput(
|
||||
UIHint = ActivityInputUIHints.MultiLine,
|
||||
Hint = "Optional data to send to your application.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid, SyntaxNames.Json })]
|
||||
public object? Payload { get; set; } = default!;
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await _eventPublisher.PublishAsync(new SendCommandModel(CommandName, Payload));
|
||||
return Done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Rebus.Handlers;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Consumers
|
||||
{
|
||||
public class SendCommandConsumer : IHandleMessages<SendCommandModel>
|
||||
{
|
||||
private readonly RemoteApplicationClient _remoteApplicationClient;
|
||||
public SendCommandConsumer(RemoteApplicationClient remoteApplicationClient) => _remoteApplicationClient = remoteApplicationClient;
|
||||
public async Task Handle(SendCommandModel message) => await _remoteApplicationClient.SendCommandAsync(message.Command, message.Payload);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\common.props" />
|
||||
<Import Project="..\..\..\configureawait.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Description>
|
||||
Elsa is a set of workflow libraries and tools that enable lean and mean workflowing capabilities in any .NET Core application.
|
||||
This package provides activities to implement conductor-style workflows where Elsa sends commands to your application, which responds asynchronously with events.
|
||||
</Description>
|
||||
<PackageTags>elsa, workflows, conductor</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using Elsa.Activities.Conductor.Consumers;
|
||||
using Elsa.Activities.Conductor.Models;
|
||||
using Elsa.Activities.Conductor.Options;
|
||||
using Elsa.Activities.Conductor.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Polly;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Extensions
|
||||
{
|
||||
public static class ElsaOptionsBuilderExtensions
|
||||
{
|
||||
public static ElsaOptionsBuilder AddConductorActivities(this ElsaOptionsBuilder elsa, Action<ConductorOptions>? configureOptions = default, Action<IHttpClientBuilder>? configureHttpClient = default)
|
||||
{
|
||||
var services = elsa.Services;
|
||||
|
||||
if (configureOptions != null)
|
||||
services.Configure(configureOptions);
|
||||
|
||||
var httpClientBuilder = services.AddHttpClient<RemoteApplicationClient>((sp, httpClient) =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptions<ConductorOptions>>().Value;
|
||||
httpClient.BaseAddress = options.ApplicationHookUrl;
|
||||
});
|
||||
|
||||
if (configureHttpClient == null)
|
||||
httpClientBuilder.AddTransientHttpErrorPolicy(x => x.WaitAndRetryAsync(10, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount))));
|
||||
else
|
||||
configureHttpClient(httpClientBuilder);
|
||||
|
||||
elsa.AddActivitiesFrom<SendCommand>();
|
||||
elsa.AddCompetingConsumer<SendCommandConsumer, SendCommandModel>();
|
||||
return elsa;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/activities/Elsa.Activities.Conductor/FodyWeavers.xml
Normal file
3
src/activities/Elsa.Activities.Conductor/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
17
src/activities/Elsa.Activities.Conductor/IsExternalInit.cs
Normal file
17
src/activities/Elsa.Activities.Conductor/IsExternalInit.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved to be used by the compiler for tracking metadata.
|
||||
/// This class should not be used by developers in source code.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class IsExternalInit
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Elsa.Activities.Conductor.Models
|
||||
{
|
||||
public record SendCommandModel(string Command, object? Payload);
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.JsonNet;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Options
|
||||
{
|
||||
public class ConductorOptions
|
||||
{
|
||||
public ConductorOptions()
|
||||
{
|
||||
SerializerSettings = new JsonSerializerSettings();
|
||||
SerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The URL to post commands to.
|
||||
/// </summary>
|
||||
public Uri ApplicationHookUrl { get; set; } = default!;
|
||||
|
||||
public JsonSerializerSettings SerializerSettings { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Conductor.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Elsa.Activities.Conductor.Services
|
||||
{
|
||||
public class RemoteApplicationClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ConductorOptions _options;
|
||||
|
||||
public RemoteApplicationClient(HttpClient httpClient, IOptions<ConductorOptions> options)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public async Task SendCommandAsync(string command, object? payload, CancellationToken cancellationToken = default)
|
||||
{
|
||||
payload ??= new object();
|
||||
|
||||
var model = new
|
||||
{
|
||||
Command = command,
|
||||
Payload = payload
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(model, _options.SerializerSettings);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
await _httpClient.PostAsync("", content, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,9 +64,10 @@ namespace Elsa.Activities.Workflows
|
|||
public string? ContextId { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
UIHint = ActivityInputUIHints.Json,
|
||||
UIHint = ActivityInputUIHints.MultiLine,
|
||||
Hint = "Optional custom attributes to associate with the workflow to run.",
|
||||
Category = PropertyCategories.Advanced)]
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid, SyntaxNames.Json })]
|
||||
public Variables? CustomAttributes { get; set; } = default!;
|
||||
|
||||
[ActivityInput(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Conductor\Elsa.Activities.Conductor.csproj" />
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Email\Elsa.Activities.Email.csproj" />
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Temporal.Hangfire\Elsa.Activities.Temporal.Hangfire.csproj" />
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Temporal.Quartz\Elsa.Activities.Temporal.Quartz.csproj" />
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Elsa.Activities.Conductor.Extensions;
|
||||
using Elsa.Activities.UserTask.Extensions;
|
||||
using Elsa.Persistence.EntityFramework.Core.Extensions;
|
||||
using Elsa.Persistence.EntityFramework.Sqlite;
|
||||
|
|
@ -16,8 +17,6 @@ namespace Elsa.Samples.Server.Host
|
|||
{
|
||||
Environment = environment;
|
||||
Configuration = configuration;
|
||||
|
||||
//BsonClassMap.RegisterClassMap<HttpRequestModel>(cm => { cm.AutoMap(); });
|
||||
}
|
||||
|
||||
private IWebHostEnvironment Environment { get; }
|
||||
|
|
@ -58,6 +57,7 @@ namespace Elsa.Samples.Server.Host
|
|||
//.AddHangfireTemporalActivities(hangfire => hangfire.UseSqlServerStorage(sqlServerConnectionString), (_, hangfireServer) => hangfireServer.SchedulePollingInterval = TimeSpan.FromSeconds(5))
|
||||
.AddJavaScriptActivities()
|
||||
.AddUserTaskActivities()
|
||||
.AddConductorActivities(options => elsaSection.GetSection("Conductor").Bind(options))
|
||||
.AddActivitiesFrom<Startup>()
|
||||
.AddWorkflowsFrom<Startup>()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@
|
|||
"Host": "localhost",
|
||||
"Port": "2525",
|
||||
"DefaultSender": "noreply@acme.com"
|
||||
},
|
||||
"Conductor": {
|
||||
"ApplicationHookUrl": "https://localhost:16001/elsa-hook"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace SampleClientApp.Web.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("elsa-hook")]
|
||||
public class ElsaHookController : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult Post(ElsaCommand model)
|
||||
{
|
||||
Console.WriteLine("Received command {0} with payload {1}", model.Command, model.Payload);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public record ElsaCommand(string Command, JsonElement Payload);
|
||||
}
|
||||
17
src/samples/server/SampleClientApp.Web/Program.cs
Normal file
17
src/samples/server/SampleClientApp.Web/Program.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace SampleClientApp.Web
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "https://localhost:16001",
|
||||
"sslPort": 16001
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"SampleClientApp.Web": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": "true",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:16001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
35
src/samples/server/SampleClientApp.Web/Startup.cs
Normal file
35
src/samples/server/SampleClientApp.Web/Startup.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace SampleClientApp.Web
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseRouting();
|
||||
app.UseAuthorization();
|
||||
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/samples/server/SampleClientApp.Web/appsettings.json
Normal file
10
src/samples/server/SampleClientApp.Web/appsettings.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Loading…
Reference in a new issue