Added MassTransit support (#46)

This commit is contained in:
Sipke Schoorstra 2019-07-07 14:20:42 +02:00 committed by GitHub
parent 877674619d
commit 7cab475321
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
88 changed files with 860 additions and 163 deletions

View file

@ -27,7 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Activities", "Activities",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Email", "src\activities\Elsa.Activities.Email\Elsa.Activities.Email.csproj", "{D20FCB88-9DCA-49EA-9CC2-5B94DD935D30}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Cron", "src\activities\Elsa.Activities.Cron\Elsa.Activities.Cron.csproj", "{E4B71DC4-3E73-49C3-9B4E-CA13909F222D}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Timers", "src\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj", "{E4B71DC4-3E73-49C3-9B4E-CA13909F222D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample01", "samples\Sample01\Sample01.csproj", "{92750B10-8DAF-4E4F-8999-33304034EDF4}"
EndProject
@ -45,6 +45,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{2269
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample07", "samples\Sample07\Sample07.csproj", "{CC1012DE-511E-41A1-8714-3F1DFCA70BC6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.MassTransit", "src\activities\Elsa.Activities.MassTransit\Elsa.Activities.MassTransit.csproj", "{C4939482-9447-47D1-B6AF-E9F8C6323841}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample08", "samples\Sample08\Sample08.csproj", "{0E58E5FA-BF2F-44CA-9AC8-E8B33CF6AAAA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -103,6 +107,14 @@ Global
{CC1012DE-511E-41A1-8714-3F1DFCA70BC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC1012DE-511E-41A1-8714-3F1DFCA70BC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC1012DE-511E-41A1-8714-3F1DFCA70BC6}.Release|Any CPU.Build.0 = Release|Any CPU
{C4939482-9447-47D1-B6AF-E9F8C6323841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4939482-9447-47D1-B6AF-E9F8C6323841}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4939482-9447-47D1-B6AF-E9F8C6323841}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4939482-9447-47D1-B6AF-E9F8C6323841}.Release|Any CPU.Build.0 = Release|Any CPU
{0E58E5FA-BF2F-44CA-9AC8-E8B33CF6AAAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E58E5FA-BF2F-44CA-9AC8-E8B33CF6AAAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E58E5FA-BF2F-44CA-9AC8-E8B33CF6AAAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E58E5FA-BF2F-44CA-9AC8-E8B33CF6AAAA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -123,6 +135,8 @@ Global
{F48E32ED-4E85-4D03-8166-7E63E7AB1D6F} = {2269E8BE-894F-46BF-A767-1E0FD046F681}
{484E698C-6CC1-4D30-879C-F333AD0D44DD} = {2269E8BE-894F-46BF-A767-1E0FD046F681}
{CC1012DE-511E-41A1-8714-3F1DFCA70BC6} = {2269E8BE-894F-46BF-A767-1E0FD046F681}
{C4939482-9447-47D1-B6AF-E9F8C6323841} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
{0E58E5FA-BF2F-44CA-9AC8-E8B33CF6AAAA} = {2269E8BE-894F-46BF-A767-1E0FD046F681}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158}

View file

@ -1,9 +1,7 @@
using System;
using System.Threading.Tasks;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Services;
using Elsa.Services.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Sample01.Activities;

View file

@ -4,9 +4,7 @@ using Elsa.Activities.Console.Activities;
using Elsa.Activities.Console.Extensions;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Expressions;
using Elsa.Services;
using Elsa.Services.Extensions;
using Microsoft.Extensions.DependencyInjection;
namespace Sample02

View file

@ -5,9 +5,7 @@ using Elsa.Activities.Console.Extensions;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Models;
using Elsa.Serialization.Models;
using Elsa.Services;
using Elsa.Services.Models;
using Microsoft.Extensions.DependencyInjection;
namespace Sample03

View file

@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;

View file

@ -3,7 +3,6 @@ using System.Threading.Tasks;
using Elsa.Activities.Console.Extensions;
using Elsa.Core.Extensions;
using Elsa.Services;
using Elsa.Services.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Sample04.Activities;

View file

@ -1,11 +1,10 @@
using System;
using System.Threading.Tasks;
using Elsa.Activities.Console.Extensions;
using Elsa.Activities.Cron.Extensions;
using Elsa.Activities.Timers.Extensions;
using Elsa.Core.Extensions;
using Elsa.Core.Persistence.Extensions;
using Elsa.Services;
using Elsa.Services.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@ -46,7 +45,7 @@ namespace Sample05
services
.AddWorkflows()
.AddConsoleActivities()
.AddCronActivities(options => options.Configure(x => x.SweepInterval = Period.FromSeconds(10)))
.AddTimerActivities(options => options.Configure(x => x.SweepInterval = Period.FromSeconds(10)))
.AddMemoryWorkflowDefinitionStore()
.AddMemoryWorkflowInstanceStore();
}

View file

@ -1,5 +1,5 @@
using Elsa.Activities.Console.Activities;
using Elsa.Activities.Cron.Activities;
using Elsa.Activities.Timers.Activities;
using Elsa.Core.Expressions;
using Elsa.Services;
using Elsa.Services.Models;
@ -13,7 +13,7 @@ namespace Sample05
builder
.WithId("RecurringWorkflow")
.AsSingleton()
.StartWith<CronTrigger>(x => x.CronExpression = new PlainTextExpression("* * * * *"))
.StartWith<CronEvent>(x => x.CronExpression = new PlainTextExpression("* * * * *"))
.Then<WriteLine>(x => x.TextExpression = new JavaScriptExpression<string>("`Trigger received. The time is: ${new Date().toISOString()}`"));
}
}

View file

@ -7,7 +7,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Console\Elsa.Activities.Console.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Cron\Elsa.Activities.Cron.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj" />
<ProjectReference Include="..\..\src\core\Elsa.Core\Elsa.Core.csproj" />
</ItemGroup>

View file

@ -10,9 +10,9 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Cron\Elsa.Activities.Cron.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Email\Elsa.Activities.Email.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Http\Elsa.Activities.Http.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj" />
<ProjectReference Include="..\..\src\core\Elsa.Core\Elsa.Core.csproj" />
</ItemGroup>

View file

@ -6,7 +6,6 @@ using Elsa.Activities.Email.Activities;
using Elsa.Activities.Http.Activities;
using Elsa.Core.Activities.Primitives;
using Elsa.Core.Expressions;
using Elsa.Core.Services;
using Elsa.Services;
using Elsa.Services.Models;
@ -29,7 +28,7 @@ namespace Sample07
activity =>
{
activity.VariableName = "document";
activity.ValueExpression = new JavaScriptExpression<object>("lastResult().FormattedContent");
activity.ValueExpression = new JavaScriptExpression<object>("lastResult().ParsedContent");
}
)
.Then<SendEmail>(

View file

@ -1,12 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Sample07
{

View file

@ -10,9 +10,9 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Cron\Elsa.Activities.Cron.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Email\Elsa.Activities.Email.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Http\Elsa.Activities.Http.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj" />
<ProjectReference Include="..\..\src\core\Elsa.Core\Elsa.Core.csproj" />
</ItemGroup>

View file

@ -0,0 +1,9 @@
using Sample08.Models;
namespace Sample08.Messages
{
public class CreateOrder
{
public Order Order { get; set; }
}
}

View file

@ -0,0 +1,9 @@
using Sample08.Models;
namespace Sample08.Messages
{
public class OrderShipped
{
public Order Order { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Sample08.Models
{
public class Customer
{
public string Name { get; set; }
public string Email { get; set; }
}
}

View file

@ -0,0 +1,10 @@
namespace Sample08.Models
{
public class Order
{
public string Id { get; set; }
public Customer Customer { get; set; }
public string Product { get; set; }
public decimal Amount { get; set; }
}
}

View file

@ -0,0 +1,17 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace Sample08
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}

View file

@ -0,0 +1,30 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:32051",
"sslPort": 44347
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Sample07": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/values",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MassTransit.AspNetCore" Version="5.5.2" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Email\Elsa.Activities.Email.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Http\Elsa.Activities.Http.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.MassTransit\Elsa.Activities.MassTransit.csproj" />
<ProjectReference Include="..\..\src\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj" />
<ProjectReference Include="..\..\src\core\Elsa.Core\Elsa.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,56 @@
using Elsa.Activities.Email.Extensions;
using Elsa.Activities.Http.Extensions;
using Elsa.Activities.MassTransit.Extensions;
using Elsa.Activities.Timers.Extensions;
using Elsa.Core.Extensions;
using Elsa.Core.Persistence.Extensions;
using Elsa.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using NodaTime;
using Sample08.Messages;
using Sample08.Workflows;
using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment;
namespace Sample08
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services
.AddWorkflows()
.AddHttpActivities()
.AddTimerActivities(options => options.Configure(x => x.SweepInterval = Period.FromSeconds(10)))
.AddEmailActivities(options => options.Bind(Configuration.GetSection("Smtp")))
.AddRabbitMqActivities(
options => options.Bind(Configuration.GetSection("MassTransit:RabbitMq")),
typeof(CreateOrder),
typeof(OrderShipped))
.AddMemoryWorkflowDefinitionStore()
.AddMemoryWorkflowInstanceStore();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IWorkflowRegistry workflowRegistry)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpActivities();
workflowRegistry.RegisterWorkflow<CreateOrderWorkflow>();
workflowRegistry.RegisterWorkflow<HandleOrderWorkflow>();
}
}
}

View file

@ -0,0 +1,74 @@
using System;
using System.Net;
using System.Net.Http;
using Elsa.Activities.Email.Activities;
using Elsa.Activities.Http.Activities;
using Elsa.Activities.MassTransit.Activities;
using Elsa.Core.Activities.Primitives;
using Elsa.Core.Expressions;
using Elsa.Services;
using Elsa.Services.Models;
using Sample08.Messages;
namespace Sample08.Workflows
{
public class CreateOrderWorkflow : IWorkflow
{
public void Build(IWorkflowBuilder builder)
{
builder
.StartWith<HttpRequestTrigger>(
activity =>
{
activity.Method = HttpMethod.Post.Method;
activity.Path = new Uri("/orders", UriKind.RelativeOrAbsolute);
activity.ReadContent = true;
}
)
.Then<SetVariable>(
activity =>
{
activity.VariableName = "order";
activity.ValueExpression = new JavaScriptExpression<object>("lastResult().ParsedContent");
}
)
.Then<SendMassTransitMessage>(activity =>
{
activity.Message = new JavaScriptExpression<CreateOrder>("return {order: order};");
activity.MessageType = typeof(CreateOrder);
}
)
.Then<Fork>(
activity => activity.Forks = new[] { "Write-Response", "Await-Shipment" },
fork =>
{
fork
.When("Write-Response")
.Then<HttpResponseAction>(
activity =>
{
activity.Body = new PlainTextExpression("<h1>Order Received</h1><p>Your order has been received. Waiting for shipment.</p>");
activity.ContentType = new PlainTextExpression("text/html");
activity.StatusCode = HttpStatusCode.Accepted;
}
);
fork
.When("Await-Shipment")
.Then<ReceiveMassTransitMessage>(activity => activity.MessageType = typeof(OrderShipped))
.Then<SendEmail>(
activity =>
{
activity.From = new PlainTextExpression("shipment@acme.com");
activity.To = new JavaScriptExpression<string>("order.customer.email");
activity.Subject = new JavaScriptExpression<string>("`Your order with ID #${order.id} has been shipped!`");
activity.Body = new JavaScriptExpression<string>(
"`Dear ${order.customer.name}, your order has shipped!`"
);
}
);
}
);
}
}
}

View file

@ -0,0 +1,34 @@
using System;
using Elsa.Activities.MassTransit.Activities;
using Elsa.Activities.Timers.Activities;
using Elsa.Core.Activities.Primitives;
using Elsa.Core.Expressions;
using Elsa.Services;
using Elsa.Services.Models;
using Sample08.Messages;
namespace Sample08.Workflows
{
public class HandleOrderWorkflow : IWorkflow
{
public void Build(IWorkflowBuilder builder)
{
builder
.StartWith<ReceiveMassTransitMessage>(activity => activity.MessageType = typeof(CreateOrder))
.Then<SetVariable>(
activity =>
{
activity.VariableName = "order";
activity.ValueExpression = new JavaScriptExpression<object>("lastResult().Order");
}
)
.Then<TimerEvent>(activity => activity.TimeoutExpression = new PlainTextExpression<TimeSpan>("00:00:05"))
.Then<SendMassTransitMessage>(activity =>
{
activity.Message = new JavaScriptExpression<OrderShipped>("return {order: order}");
activity.MessageType = typeof(OrderShipped);
}
);
}
}
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<gcServer enabled="true"/>
</runtime>
</configuration>

View file

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View file

@ -0,0 +1,17 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"Smtp": {
"Host": "localhost",
"Port": "2525"
},
"MassTransit": {
"RabbitMq": {
"Host": "rabbitmq://localhost:5672"
}
}
}

View file

@ -0,0 +1,10 @@
version: '3.7'
services:
rabbitmq:
image: "rabbitmq:3-management"
ports:
- "15672:15672"
- "5672:5672"
labels:
name: "rabbitmq"

View file

@ -2,8 +2,8 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Expressions;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;

View file

@ -1,24 +0,0 @@
using System;
using Elsa.Activities.Cron.Activities;
using Elsa.Activities.Cron.HostedServices;
using Elsa.Activities.Cron.Options;
using Elsa.Core.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Elsa.Activities.Cron.Extensions
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCronActivities(this IServiceCollection services, Action<OptionsBuilder<CronOptions>> options = null)
{
var optionsBuilder = services.AddOptions<CronOptions>();
options?.Invoke(optionsBuilder);
return services
.AddOptions()
.AddHostedService<CronHostedService>()
.AddActivity<CronTrigger>();
}
}
}

View file

@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;

View file

@ -3,7 +3,6 @@ using System.Net.Mail;
using Elsa.Activities.Email.Activities;
using Elsa.Activities.Email.Options;
using Elsa.Core.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

View file

@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Elsa.Activities.Http.Models;
using Elsa.Activities.Http.Services;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;
@ -111,7 +112,7 @@ namespace Elsa.Activities.Http.Activities
StatusCode = response.StatusCode,
Headers = new HeaderDictionary(response.Headers.ToDictionary(x => x.Key, x => new StringValues(x.Value.ToArray()))),
Content = content,
FormattedContent = await formatter.FormatAsync(content, contentType)
FormattedContent = await formatter.ParseAsync(content, contentType)
};
workflowContext.SetLastResult(responseModel);

View file

@ -9,9 +9,7 @@ using Elsa.Activities.Http.Models;
using Elsa.Activities.Http.Services;
using Elsa.Core.Services;
using Elsa.Extensions;
using Elsa.Models;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json.Linq;
@ -98,10 +96,10 @@ namespace Elsa.Activities.Http.Activities
model.Form = (await request.ReadFormAsync(cancellationToken)).ToDictionary(x => x.Key, x => x.Value);
}
var formatter = SelectContentFormatter(request.ContentType);
var parser = SelectContentParser(request.ContentType);
var content = await request.ReadBodyAsync();
model.Content = content;
model.FormattedContent = await formatter.FormatAsync(content, request.ContentType);
model.ParsedContent = await parser.ParseAsync(content, request.ContentType);
}
workflowContext.CurrentScope.LastResult = model;
@ -109,7 +107,7 @@ namespace Elsa.Activities.Http.Activities
return Done();
}
private IContentFormatter SelectContentFormatter(string contentType)
private IContentFormatter SelectContentParser(string contentType)
{
var formatters = contentFormatters.OrderByDescending(x => x.Priority).ToList();
return formatters.FirstOrDefault(x => x.SupportedContentTypes.Contains(contentType, StringComparer.OrdinalIgnoreCase)) ?? formatters.Last();

View file

@ -5,6 +5,7 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;

View file

@ -17,7 +17,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.AsyncInitialization" Version="1.1.2" />
<PackageReference Include="CSharpFunctionalExtensions" Version="1.18.2" />
<PackageReference Include="LanguageExt.Core" Version="3.1.24" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="2.2.0" />

View file

@ -1,5 +1,4 @@
using Elsa.Activities.Http.Middleware;
using Elsa.Activities.Http.RequestHandlers;
using Elsa.Activities.Http.RequestHandlers.Handlers;
using Elsa.Activities.Http.Services;
using Microsoft.AspNetCore.Builder;

View file

@ -1,18 +1,12 @@
using Elsa.Activities.Http.Activities;
using Elsa.Activities.Http.Formatters;
using Elsa.Activities.Http.Middleware;
using Elsa.Activities.Http.Models;
using Elsa.Activities.Http.RequestHandlers;
using Elsa.Activities.Http.RequestHandlers.Handlers;
using Elsa.Activities.Http.Scripting;
using Elsa.Activities.Http.Services;
using Elsa.Core.Extensions;
using Elsa.Scripting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

View file

@ -3,7 +3,6 @@ using System.Dynamic;
using System.Threading.Tasks;
using Elsa.Activities.Http.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Elsa.Activities.Http.Formatters
{
@ -12,7 +11,7 @@ namespace Elsa.Activities.Http.Formatters
public int Priority => 0;
public IEnumerable<string> SupportedContentTypes => new[] { "application/json", "text/json" };
public Task<object> FormatAsync(string content, string contentType)
public Task<object> ParseAsync(string content, string contentType)
{
return Task.FromResult<object>(JsonConvert.DeserializeObject<ExpandoObject>(content));
}

View file

@ -9,7 +9,7 @@ namespace Elsa.Activities.Http.Formatters
public int Priority => -1;
public IEnumerable<string> SupportedContentTypes => new[] { "", default };
public Task<object> FormatAsync(string content, string contentType)
public Task<object> ParseAsync(string content, string contentType)
{
return Task.FromResult<object>(content);
}

View file

@ -1,8 +1,5 @@
using System.Threading.Tasks;
using Elsa.Activities.Http.RequestHandlers;
using Elsa.Activities.Http.Services;
using Elsa.Persistence;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
namespace Elsa.Activities.Http.Middleware

View file

@ -11,7 +11,7 @@ namespace Elsa.Activities.Http.Models
public IDictionary<string, StringValues> QueryString { get; set; }
public IDictionary<string, StringValues> Headers { get; set; }
public string Content { get; set; }
public object FormattedContent { get; set; }
public object ParsedContent { get; set; }
public IDictionary<string, StringValues> Form { get; set; }
}
}

View file

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
@ -8,6 +7,6 @@ namespace Elsa.Activities.Http.Services
{
int Priority { get; }
IEnumerable<string> SupportedContentTypes { get; }
Task<object> FormatAsync(string content, string contentType);
Task<object> ParseAsync(string content, string contentType);
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Services;
using Elsa.Extensions;
using Elsa.Results;
using Elsa.Services.Models;
using Newtonsoft.Json.Linq;
namespace Elsa.Activities.MassTransit.Activities
{
public class ReceiveMassTransitMessage : Activity
{
public static Type GetMessageType(JObject state)
{
var typeName = state.GetState<string>(nameof(MessageType));
return string.IsNullOrWhiteSpace(typeName) ? null : Type.GetType(typeName);
}
public Type MessageType
{
get => GetMessageType(State);
set => SetState(value.AssemblyQualifiedName);
}
protected override bool OnCanExecute(WorkflowExecutionContext context)
{
var message = context.Workflow.Input["message"];
var messageType = MessageType;
return message != null && messageType != null && message.GetType() == messageType;
}
protected override ActivityExecutionResult OnExecute(WorkflowExecutionContext context)
{
return Halt(true);
}
protected override Task<ActivityExecutionResult> OnResumeAsync(WorkflowExecutionContext context, CancellationToken cancellationToken)
{
var message = context.Workflow.Input["message"];
context.SetLastResult(message);
return Task.FromResult(Done());
}
}
}

View file

@ -0,0 +1,53 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Expressions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
using MassTransit;
namespace Elsa.Activities.MassTransit.Activities
{
public class SendMassTransitMessage : Activity
{
private readonly ISendEndpointProvider sender;
private readonly IWorkflowExpressionEvaluator evaluator;
public SendMassTransitMessage(ISendEndpointProvider sender, IWorkflowExpressionEvaluator evaluator)
{
this.sender = sender;
this.evaluator = evaluator;
}
public Type MessageType
{
get
{
var typeName = GetState<string>();
return string.IsNullOrWhiteSpace(typeName) ? null : Type.GetType(typeName);
}
set => SetState(value.AssemblyQualifiedName);
}
public WorkflowExpression Message
{
get => GetState(() => new WorkflowExpression(JavaScriptEvaluator.SyntaxName, string.Empty));
set => SetState(value);
}
protected override bool OnCanExecute(WorkflowExecutionContext context)
{
return MessageType != null;
}
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext context, CancellationToken cancellationToken)
{
var message = await evaluator.EvaluateAsync(Message, MessageType, context, cancellationToken);
await sender.Send(message, cancellationToken);
return Done();
}
}
}

View file

@ -0,0 +1,31 @@
using System.Threading.Tasks;
using Elsa.Activities.MassTransit.Activities;
using Elsa.Models;
using Elsa.Services;
using MassTransit;
namespace Elsa.Activities.MassTransit.Consumers
{
public class WorkflowConsumer<T> : IConsumer<T> where T : class
{
private readonly IWorkflowInvoker workflowInvoker;
public WorkflowConsumer(IWorkflowInvoker workflowInvoker)
{
this.workflowInvoker = workflowInvoker;
}
public async Task Consume(ConsumeContext<T> context)
{
var message = context.Message;
var activityType = nameof(ReceiveMassTransitMessage);
var input = new Variables { ["message"] = message };
await workflowInvoker.TriggerAsync(
activityType,
input,
x => ReceiveMassTransitMessage.GetMessageType(x) == message.GetType(),
context.CancellationToken);
}
}
}

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MassTransit.AspNetCore" Version="5.5.2" />
<PackageReference Include="MassTransit.Extensions.DependencyInjection" Version="5.5.2" />
<PackageReference Include="MassTransit.RabbitMQ" Version="5.5.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using Elsa.Activities.MassTransit.Activities;
using Elsa.Activities.MassTransit.Consumers;
using Elsa.Activities.MassTransit.Options;
using Elsa.Core.Extensions;
using MassTransit;
using MassTransit.AspNetCoreIntegration;
using MassTransit.AspNetCoreIntegration.HealthChecks;
using MassTransit.ExtensionsDependencyInjectionIntegration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace Elsa.Activities.MassTransit.Extensions
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddRabbitMqActivities(this IServiceCollection services, Action<OptionsBuilder<RabbitMqOptions>> options = null, params Type[] messageTypes)
{
var optionsBuilder = services.AddOptions<RabbitMqOptions>();
options?.Invoke(optionsBuilder);
services
.AddActivity<SendMassTransitMessage>()
.AddActivity<ReceiveMassTransitMessage>();
services.AddSingleton<SimplifiedBusHealthCheck>();
services.AddSingleton<ReceiveEndpointHealthCheck>();
services.AddMassTransit(
massTransit =>
{
massTransit.AddBus(sp => CreateUsingRabbitMq(massTransit, sp, messageTypes));
});
services.AddSingleton<IHostedService, MassTransitHostedService>();
foreach (var messageType in messageTypes)
{
var consumerType = CreateConsumerType(messageType);
services.AddSingleton(consumerType);
}
return services;
}
private static IBusControl CreateUsingRabbitMq(IServiceCollectionConfigurator massTransit, IServiceProvider sp, IEnumerable<Type> messageTypes)
{
return Bus.Factory.CreateUsingRabbitMq(
bus =>
{
var options = sp.GetRequiredService<IOptions<RabbitMqOptions>>();
var host = bus.Host(new Uri(options.Value.Host), _ => { });
foreach (var messageType in messageTypes)
{
var queueName = messageType.Name;
var consumerType = CreateConsumerType(messageType);
bus.ReceiveEndpoint(
host,
queueName,
endpoint =>
{
endpoint.PrefetchCount = 16;
endpoint.Consumer(consumerType, sp.GetRequiredService);
MapEndpointConvention(messageType, endpoint.InputAddress);
});
}
});
}
private static void MapEndpointConvention(Type messageType, Uri destinationAddress)
{
var method = typeof(EndpointConvention).GetMethod("Map", new[]{ typeof(Uri) });
var generic = method.MakeGenericMethod(messageType);
generic.Invoke(null, new object[]{destinationAddress});
}
private static Type CreateConsumerType(Type messageType)
{
return typeof(WorkflowConsumer<>).MakeGenericType(messageType);
}
}
}

View file

@ -0,0 +1,7 @@
namespace Elsa.Activities.MassTransit.Options
{
public class RabbitMqOptions
{
public string Host { get; set; }
}
}

View file

@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;
@ -8,14 +9,14 @@ using Elsa.Services.Models;
using NCrontab;
using NodaTime;
namespace Elsa.Activities.Cron.Activities
namespace Elsa.Activities.Timers.Activities
{
public class CronTrigger : Activity
public class CronEvent : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
private readonly IClock clock;
public CronTrigger(IWorkflowExpressionEvaluator expressionEvaluator, IClock clock)
public CronEvent(IWorkflowExpressionEvaluator expressionEvaluator, IClock clock)
{
this.expressionEvaluator = expressionEvaluator;
this.clock = clock;
@ -27,7 +28,7 @@ namespace Elsa.Activities.Cron.Activities
set => SetState(value);
}
public Instant? StartTimestamp
public Instant? StartTime
{
get => GetState<Instant?>();
set => SetState(value);
@ -35,9 +36,9 @@ namespace Elsa.Activities.Cron.Activities
protected override ActivityExecutionResult OnExecute(WorkflowExecutionContext workflowContext)
{
if (StartTimestamp == null)
if (StartTime == null)
{
StartTimestamp = clock.GetCurrentInstant();
StartTime = clock.GetCurrentInstant();
}
return Halt();
@ -55,8 +56,8 @@ namespace Elsa.Activities.Cron.Activities
var cronExpression = await expressionEvaluator.EvaluateAsync(CronExpression, workflowContext, cancellationToken);
var schedule = CrontabSchedule.Parse(cronExpression);
var now = clock.GetCurrentInstant();
var startTimestamp = StartTimestamp ?? now;
var nextOccurrence = schedule.GetNextOccurrence(startTimestamp.ToDateTimeUtc());
var startTime = StartTime ?? now;
var nextOccurrence = schedule.GetNextOccurrence(startTime.ToDateTimeUtc());
return now.ToDateTimeUtc() >= nextOccurrence;
}

View file

@ -0,0 +1,64 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
using NodaTime;
namespace Elsa.Activities.Timers.Activities
{
public class TimerEvent : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
private readonly IClock clock;
public TimerEvent(IWorkflowExpressionEvaluator expressionEvaluator, IClock clock)
{
this.expressionEvaluator = expressionEvaluator;
this.clock = clock;
}
public WorkflowExpression<TimeSpan> TimeoutExpression
{
get => GetState(() => new PlainTextExpression<TimeSpan>("00:01:00"));
set => SetState(value);
}
public Instant? StartTime
{
get => GetState<Instant?>();
set => SetState(value);
}
protected override ActivityExecutionResult OnExecute(WorkflowExecutionContext workflowContext)
{
if (StartTime == null)
{
StartTime = clock.GetCurrentInstant();
}
return Halt();
}
protected override async Task<ActivityExecutionResult> OnResumeAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
var isExpired = await IsExpiredAsync(workflowContext, cancellationToken);
return isExpired ? Done() : Halt();
}
private async Task<bool> IsExpiredAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
var timeSpan = await expressionEvaluator.EvaluateAsync(TimeoutExpression, workflowContext, cancellationToken);
var now = clock.GetCurrentInstant();
var startTime = StartTime ?? now;
var expiresAt = startTime.ToDateTimeUtc() + timeSpan;
return now.ToDateTimeUtc() >= expiresAt;
}
}
}

View file

@ -0,0 +1,25 @@
using System;
using Elsa.Activities.Timers.Activities;
using Elsa.Activities.Timers.HostedServices;
using Elsa.Activities.Timers.Options;
using Elsa.Core.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Elsa.Activities.Timers.Extensions
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddTimerActivities(this IServiceCollection services, Action<OptionsBuilder<TimersOptions>> options = null)
{
var optionsBuilder = services.AddOptions<TimersOptions>();
options?.Invoke(optionsBuilder);
return services
.AddOptions()
.AddHostedService<TimersHostedService>()
.AddActivity<CronEvent>()
.AddActivity<TimerEvent>();
}
}
}

View file

@ -1,23 +1,24 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Cron.Activities;
using Elsa.Activities.Cron.Options;
using Elsa.Activities.Timers.Activities;
using Elsa.Activities.Timers.Options;
using Elsa.Models;
using Elsa.Services;
using Elsa.Services.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Elsa.Activities.Cron.HostedServices
namespace Elsa.Activities.Timers.HostedServices
{
public class CronHostedService : BackgroundService
public class TimersHostedService : BackgroundService
{
private readonly IWorkflowInvoker workflowInvoker;
private readonly IOptions<CronOptions> options;
private readonly ILogger<CronHostedService> logger;
private readonly IOptions<TimersOptions> options;
private readonly ILogger<TimersHostedService> logger;
public CronHostedService(IWorkflowInvoker workflowInvoker, IOptions<CronOptions> options, ILogger<CronHostedService> logger)
public TimersHostedService(IWorkflowInvoker workflowInvoker, IOptions<TimersOptions> options, ILogger<TimersHostedService> logger)
{
this.workflowInvoker = workflowInvoker;
this.options = options;
@ -30,7 +31,8 @@ namespace Elsa.Activities.Cron.HostedServices
{
try
{
await workflowInvoker.TriggerAsync(nameof(CronTrigger), Variables.Empty, stoppingToken);
await workflowInvoker.TriggerAsync(nameof(TimerEvent), Variables.Empty, stoppingToken);
await workflowInvoker.TriggerAsync(nameof(CronEvent), Variables.Empty, stoppingToken);
}
catch (Exception ex)
{

View file

@ -1,10 +1,10 @@
using NodaTime;
namespace Elsa.Activities.Cron.Options
namespace Elsa.Activities.Timers.Options
{
public class CronOptions
public class TimersOptions
{
public CronOptions()
public TimersOptions()
{
SweepInterval = Period.FromMinutes(1);
}

View file

@ -1,8 +1,12 @@
namespace Elsa.Expressions
{
public interface IWorkflowExpression<T>
public interface IWorkflowExpression
{
string Syntax { get; }
string Expression { get; }
}
public interface IWorkflowExpression<T> : IWorkflowExpression
{
}
}

View file

@ -1,7 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Serialization.Models;
namespace Elsa.Persistence
{

View file

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

View file

@ -1,6 +1,5 @@
using System.Collections.Generic;
using Elsa.Models;
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Elsa.Serialization.Models

View file

@ -1,8 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Elsa.Models;
using Elsa.Services.Models;
using NodaTime;
namespace Elsa.Services.Extensions
{

View file

@ -0,0 +1,21 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
namespace Elsa.Services.Extensions
{
public static class WorkflowInvokerExtensions
{
/// <summary>
/// Starts new workflows that start with the specified activity name and resumes halted workflows that are blocked on activities with the specified activity name.
/// </summary>
public static Task TriggerAsync(
this IWorkflowInvoker workflowInvoker,
string activityType,
Variables input,
CancellationToken cancellationToken = default)
{
return workflowInvoker.TriggerAsync(activityType, input, cancellationToken: cancellationToken);
}
}
}

View file

@ -5,7 +5,7 @@ namespace Elsa.Services
{
public interface IActivityResolver
{
IActivity ResolveActivity(string activityTypeName, Action<IActivity> setup = null);
T ResolveActivity<T>(Action<T> configure = null) where T : class, IActivity;
IActivity ResolveActivity(string activityTypeName, Action<IActivity> setup = default);
T ResolveActivity<T>(Action<T> configure = default) where T : class, IActivity;
}
}

View file

@ -1,6 +1,5 @@
using System;
using Elsa.Models;
using Elsa.Services.Models;
namespace Elsa.Services
{

View file

@ -1,3 +1,4 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Services.Models;
@ -7,6 +8,6 @@ namespace Elsa.Services
public interface IExpressionEvaluator
{
string Syntax { get; }
Task<T> EvaluateAsync<T>(string expression, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken);
Task<object> EvaluateAsync(string expression, Type type, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken);
}
}

View file

@ -8,7 +8,7 @@ namespace Elsa.Services
{
IActivityBuilder Source { get; }
string Outcome { get; }
IActivityBuilder Then<T>(Action<T> setup, string id = null) where T : class, IActivity;
IActivityBuilder Then<T>(Action<T> setup = default, string id = default) where T : class, IActivity;
WorkflowDefinition Build();
IConnectionBuilder Then(string activityId);
}

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Elsa.Models;
using Elsa.Serialization.Models;
using Elsa.Services.Models;
namespace Elsa.Services
@ -12,10 +11,10 @@ namespace Elsa.Services
IReadOnlyList<IActivityBuilder> Activities { get; }
IWorkflowBuilder WithId(string id);
IWorkflowBuilder AsSingleton(bool value = true);
IActivityBuilder Add<T>(Action<T> setupActivity, string id = null) where T : class, IActivity;
IActivityBuilder StartWith<T>(Action<T> setup = null, string id = null) where T: class, IActivity;
IConnectionBuilder Connect(IActivityBuilder source, IActivityBuilder target, string outcome = null);
IConnectionBuilder Connect(Func<IActivityBuilder> source, Func<IActivityBuilder> target, string outcome = null);
IActivityBuilder Add<T>(Action<T> setupActivity = default, string id = default) where T : class, IActivity;
IActivityBuilder StartWith<T>(Action<T> setup = default, string id = default) where T: class, IActivity;
IConnectionBuilder Connect(IActivityBuilder source, IActivityBuilder target, string outcome = default);
IConnectionBuilder Connect(Func<IActivityBuilder> source, Func<IActivityBuilder> target, string outcome = default);
WorkflowDefinition Build();
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Expressions;
@ -7,6 +8,6 @@ namespace Elsa.Services
{
public interface IWorkflowExpressionEvaluator
{
Task<T> EvaluateAsync<T>(IWorkflowExpression<T> expression, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken);
Task<object> EvaluateAsync(IWorkflowExpression expression, Type type, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken);
}
}

View file

@ -1,9 +1,11 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Serialization.Models;
using Elsa.Services.Models;
using Newtonsoft.Json.Linq;
namespace Elsa.Services
{
@ -40,6 +42,10 @@ namespace Elsa.Services
/// <summary>
/// Starts new workflows that start with the specified activity name and resumes halted workflows that are blocked on activities with the specified activity name.
/// </summary>
Task TriggerAsync(string activityType, Variables input, CancellationToken cancellationToken = default);
Task TriggerAsync(
string activityType,
Variables input,
Func<JObject, bool> activityStatePredicate = default,
CancellationToken cancellationToken = default);
}
}

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Threading;
using Elsa.Models;
using Elsa.Services.Models;

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Elsa.Models;
using Elsa.Serialization.Models;

View file

@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;

View file

@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;

View file

@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;

View file

@ -1,17 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Expressions;
using Elsa.Scripting;
using Elsa.Services;
using Elsa.Services.Models;
using Esprima.Ast;
using Jint;
using Jint.Native;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
namespace Elsa.Core.Expressions
{
@ -32,14 +28,14 @@ namespace Elsa.Core.Expressions
public string Syntax => SyntaxName;
public Task<T> EvaluateAsync<T>(string expression, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
public Task<object> EvaluateAsync(string expression, Type type, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
{
var engine = new Engine(options => { options.AllowClr(); });
ConfigureEngine(engine, workflowExecutionContext);
engine.Execute(expression);
var result = ConvertValue<T>(engine.GetCompletionValue());
var result = ConvertValue(engine.GetCompletionValue(), type);
return Task.FromResult(result);
}
@ -52,11 +48,6 @@ namespace Elsa.Core.Expressions
}
}
private T ConvertValue<T>(JsValue value)
{
return (T)ConvertValue(value, typeof(T));
}
private object ConvertValue(JsValue value, Type targetType)
{
if (value.IsNull())
@ -75,7 +66,11 @@ namespace Elsa.Core.Expressions
return value.AsString();
if (value.IsObject())
return value.AsObject().ToObject();
{
var obj = value.AsObject().ToObject();
var json = JsonConvert.SerializeObject(obj);
return JsonConvert.DeserializeObject(json, targetType);
}
if (value.IsArray())
{

View file

@ -1,7 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Expressions;
using Elsa.Core.Extensions;
using Elsa.Services;
using Elsa.Services.Models;
@ -17,12 +17,12 @@ namespace Elsa.Core.Expressions
public const string SyntaxName = "PlainText";
public string Syntax => SyntaxName;
public Task<T> EvaluateAsync<T>(string expression, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
public Task<object> EvaluateAsync(string expression, Type type, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(expression))
return Task.FromResult(default(T));
return Task.FromResult(default(object));
return Task.FromResult((T) Convert.ChangeType(expression, typeof(T)));
return Task.FromResult(expression.Parse(type));
}
}
}

View file

@ -1,9 +1,16 @@
namespace Elsa.Core.Expressions
{
public class PlainTextExpression : WorkflowExpression<string>
public class PlainTextExpression<T> : WorkflowExpression<T>
{
public PlainTextExpression(string expression) : base(PlainTextEvaluator.SyntaxName, expression)
{
}
}
public class PlainTextExpression : PlainTextExpression<string>
{
public PlainTextExpression(string expression) : base(expression)
{
}
}
}

View file

@ -2,9 +2,9 @@
namespace Elsa.Core.Expressions
{
public class WorkflowExpression
public class WorkflowExpression : IWorkflowExpression
{
protected WorkflowExpression(string syntax, string expression)
public WorkflowExpression(string syntax, string expression)
{
Syntax = syntax;
Expression = expression;

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@ -16,14 +17,14 @@ namespace Elsa.Core.Expressions
{
this.evaluators = evaluators.ToDictionary(x => x.Syntax);
}
public async Task<T> EvaluateAsync<T>(IWorkflowExpression<T> expression, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
public async Task<object> EvaluateAsync(IWorkflowExpression expression, Type type, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
{
if (expression == null)
return default;
var evaluator = evaluators[expression.Syntax];
return await evaluator.EvaluateAsync<T>(expression.Expression, workflowExecutionContext, cancellationToken);
return await evaluator.EvaluateAsync(expression.Expression, type, workflowExecutionContext, cancellationToken);
}
}
}

View file

@ -0,0 +1,15 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Core.Extensions
{
public static class ExpressionEvaluatorExtensions
{
public static async Task<T> EvaluateAsync<T>(this IExpressionEvaluator evaluator, string expression, WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
{
return (T)await evaluator.EvaluateAsync(expression, typeof(T), workflowExecutionContext, cancellationToken);
}
}
}

View file

@ -1,10 +1,11 @@
using System;
using Elsa.Core.Activities.Primitives;
using Elsa.Core.Expressions;
using Elsa.Core.Scripting;
using Elsa.Core.Serialization;
using Elsa.Core.Serialization.Formatters;
using Elsa.Core.Services;
using Elsa.Core.Services.WorkflowBuilders;
using Elsa.Expressions;
using Elsa.Scripting;
using Elsa.Serialization;
using Elsa.Serialization.Formatters;
@ -40,6 +41,7 @@ namespace Elsa.Core.Extensions
.AddSingleton<IWorkflowExpressionEvaluator, WorkflowExpressionEvaluator>()
.AddSingleton<IWorkflowSerializerProvider, WorkflowSerializerProvider>()
.AddTransient<IWorkflowBuilder, WorkflowBuilder>()
.AddSingleton<Func<IWorkflowBuilder>>(sp => sp.GetRequiredService<IWorkflowBuilder>)
.AddSingleton<IWorkflowRegistry, WorkflowRegistry>()
.AddPrimitiveActivities();
}

View file

@ -0,0 +1,23 @@
using System;
using System.ComponentModel;
using System.Globalization;
namespace Elsa.Core.Extensions
{
public static class StringExtensions
{
public static T Parse<T>(this string value)
{
return (T)value.Parse(typeof(T));
}
public static object Parse(this string value, Type targetType)
{
if (typeof(string) == targetType)
return value;
var converter = TypeDescriptor.GetConverter(targetType);
return converter.ConvertFromString(default, CultureInfo.InvariantCulture, value);
}
}
}

View file

@ -0,0 +1,16 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Expressions;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Core.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);
}
}
}

View file

@ -3,7 +3,6 @@ using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Persistence;
using Elsa.Serialization.Models;
namespace Elsa.Core.Persistence.Memory
{

View file

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using Elsa.Expressions;
using Elsa.Scripting;
using Elsa.Services.Models;
using Jint;
namespace Elsa.Core.Expressions
namespace Elsa.Core.Scripting
{
public class CommonScriptEngineConfigurator : IScriptEngineConfigurator
{

View file

@ -1,7 +1,6 @@
using System;
using Elsa.Models;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Core.Services.WorkflowBuilders
{

View file

@ -19,7 +19,7 @@ namespace Elsa.Core.Services.WorkflowBuilders
public IActivityBuilder Source { get; }
public string Outcome { get; }
public IActivityBuilder Then<T>(Action<T> setup, string id = null) where T : class, IActivity
public IActivityBuilder Then<T>(Action<T> setup = default, string id = default) where T : class, IActivity
{
var target = WorkflowBuilder.Add(setup, id);

View file

@ -34,7 +34,7 @@ namespace Elsa.Core.Services.WorkflowBuilders
return this;
}
public IActivityBuilder Add<T>(Action<T> setupActivity, string id = null) where T : class, IActivity
public IActivityBuilder Add<T>(Action<T> setupActivity = default, string id = default) where T : class, IActivity
{
var activity = activityResolver.ResolveActivity(setupActivity);
var activityBlueprint = ActivityDefinition.FromActivity(activity);
@ -47,12 +47,12 @@ namespace Elsa.Core.Services.WorkflowBuilders
return activityBuilder;
}
public IConnectionBuilder Connect(IActivityBuilder source, IActivityBuilder target, string outcome = null)
public IConnectionBuilder Connect(IActivityBuilder source, IActivityBuilder target, string outcome = default)
{
return Connect(() => source, () => target, outcome);
}
public IConnectionBuilder Connect(Func<IActivityBuilder> source, Func<IActivityBuilder> target, string outcome = null)
public IConnectionBuilder Connect(Func<IActivityBuilder> source, Func<IActivityBuilder> target, string outcome = default)
{
var connectionBuilder = new ConnectionBuilder(this, source, target, outcome);

View file

@ -13,6 +13,7 @@ using Elsa.Services;
using Elsa.Services.Extensions;
using Elsa.Services.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Elsa.Core.Services
@ -89,15 +90,23 @@ namespace Elsa.Core.Services
return InvokeAsync(definition, input, workflowInstance, startActivityIds, cancellationToken);
}
public async Task TriggerAsync(string activityType, Variables input, CancellationToken cancellationToken)
public async Task TriggerAsync(
string activityType,
Variables input,
Func<JObject, bool> activityStatePredicate = default,
CancellationToken cancellationToken = default)
{
var workflowDefinitions = workflowRegistry.ListByStartActivity(activityType).ToDictionary(x => x.Item1.Id);
var workflowInstances = await workflowInstanceStore.ListByBlockingActivityAsync(activityType, cancellationToken).ToListAsync();
var workflowInstancesByDefinitionId = workflowInstances.ToDictionary(x => x.Item1.DefinitionId);
var workflowsToStart = workflowDefinitions.Values.Where(x => !workflowInstancesByDefinitionId.ContainsKey(x.Item1.Id));
var workflowDefinitions = workflowRegistry.ListByStartActivity(activityType).ToList();
await StartWorkflowsAsync(workflowsToStart, input, cancellationToken);
await ResumeWorkflowsAsync(workflowDefinitions, workflowInstances, input, cancellationToken);
if (activityStatePredicate != null)
workflowDefinitions = workflowDefinitions.Where(x => activityStatePredicate(x.Item2.State)).ToList();
if (activityStatePredicate != null)
workflowInstances = workflowInstances.Where(x => activityStatePredicate(x.Item2.State)).ToList();
await StartWorkflowsAsync(workflowDefinitions, input, cancellationToken);
await ResumeWorkflowsAsync(workflowInstances, input, cancellationToken);
}
private async Task StartWorkflowsAsync(IEnumerable<(WorkflowDefinition, ActivityDefinition)> workflowDefinitions, Variables variables, CancellationToken cancellationToken1)
@ -109,15 +118,15 @@ namespace Elsa.Core.Services
}
}
private async Task ResumeWorkflowsAsync(IReadOnlyDictionary<string, (WorkflowDefinition, ActivityDefinition)> workflowDefinitions, IList<(WorkflowInstance, ActivityInstance)> workflowInstances, Variables input, CancellationToken cancellationToken)
private async Task ResumeWorkflowsAsync(IEnumerable<(WorkflowInstance, ActivityInstance)> workflowInstances, Variables input, CancellationToken cancellationToken)
{
foreach (var (workflowInstance, startActivityInstance) in workflowInstances)
{
var workflowDefinition = workflowDefinitions[workflowInstance.DefinitionId];
var workflowDefinition = workflowRegistry.GetById(workflowInstance.DefinitionId);
workflowInstance.Status = WorkflowStatus.Resuming;
await InvokeAsync(workflowDefinition.Item1, input, workflowInstance, new[] { startActivityInstance.Id }, cancellationToken);
await InvokeAsync(workflowDefinition, input, workflowInstance, new[] { startActivityInstance.Id }, cancellationToken);
}
}

View file

@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Elsa.Models;
using Elsa.Services;
using Elsa.Services.Extensions;
@ -10,12 +10,12 @@ namespace Elsa.Core.Services
{
public class WorkflowRegistry : IWorkflowRegistry
{
private readonly IWorkflowBuilder workflowBuilder;
private readonly Func<IWorkflowBuilder> workflowBuilderFactory;
private readonly IDictionary<string, WorkflowDefinition> workflowDefinitions;
public WorkflowRegistry(IWorkflowBuilder workflowBuilder)
public WorkflowRegistry(Func<IWorkflowBuilder> workflowBuilderFactory)
{
this.workflowBuilder = workflowBuilder;
this.workflowBuilderFactory = workflowBuilderFactory;
workflowDefinitions = new Dictionary<string, WorkflowDefinition>();
}
@ -26,7 +26,7 @@ namespace Elsa.Core.Services
public void RegisterWorkflow<T>() where T : IWorkflow, new()
{
var definition = workflowBuilder.Build<T>();
var definition = workflowBuilderFactory().Build<T>();
RegisterWorkflow(definition);
}