MassTransit activities (#3613)
* Implement MessageReceived for MT * Implement Publish activity provider * Only create Publish activity for class types * Add MassTransit sample project * Fix comment
This commit is contained in:
parent
0220b8c891
commit
2c65081110
7
Elsa.sln
7
Elsa.sln
|
|
@ -150,6 +150,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Webhooks.Workf
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Webhooks.ExternalApp", "src\samples\aspnet\Elsa.Samples.Webhooks.ExternalApp\Elsa.Samples.Webhooks.ExternalApp.csproj", "{036D287A-33E1-4B28-BE13-14AEA16BC91F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.MassTransitActivities", "src\samples\aspnet\Elsa.Samples.MassTransitActivities\Elsa.Samples.MassTransitActivities.csproj", "{A1A8AD89-C9C4-41BF-BBCB-EF0544A28BFB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -380,6 +382,10 @@ Global
|
|||
{036D287A-33E1-4B28-BE13-14AEA16BC91F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{036D287A-33E1-4B28-BE13-14AEA16BC91F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{036D287A-33E1-4B28-BE13-14AEA16BC91F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A1A8AD89-C9C4-41BF-BBCB-EF0544A28BFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1A8AD89-C9C4-41BF-BBCB-EF0544A28BFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1A8AD89-C9C4-41BF-BBCB-EF0544A28BFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1A8AD89-C9C4-41BF-BBCB-EF0544A28BFB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
|
||||
|
|
@ -445,5 +451,6 @@ Global
|
|||
{2BBFDE36-28A7-4875-8EBE-CC25C76B97FF} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
{130A7A00-A9AF-4EA8-8107-BBEA07F166DF} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
{036D287A-33E1-4B28-BE13-14AEA16BC91F} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
{A1A8AD89-C9C4-41BF-BBCB-EF0544A28BFB} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Scheduling.Activities;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.WorkflowServer.Web.Models;
|
||||
|
||||
namespace Elsa.WorkflowServer.Web.Activities;
|
||||
|
||||
[Activity("Demo", "Models the entire process of ordering, preparing, and delivering a pizza.")]
|
||||
public class GetPizza : Composite<Pizza>
|
||||
{
|
||||
[Input(
|
||||
UIHint = InputUIHints.Dropdown,
|
||||
Options = new[] { "Margaritha", "Fungi", "Veggie", "Carbonara", "Pepperoni", "Hawaii" },
|
||||
DefaultValue = "Margaritha"
|
||||
)]
|
||||
public Input<string> Flavor { get; set; } = new("Margaritha");
|
||||
|
||||
[Input(
|
||||
UIHint = InputUIHints.Dropdown,
|
||||
Options = new[] { 20, 30, 40, 80 }
|
||||
)]
|
||||
public Input<int> Size { get; set; } = default!;
|
||||
|
||||
public GetPizza()
|
||||
{
|
||||
Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new WriteLine("Submitting order..."),
|
||||
Delay.FromSeconds(2),
|
||||
new WriteLine("Order submitted"),
|
||||
Delay.FromSeconds(2),
|
||||
new Fork
|
||||
{
|
||||
JoinMode = ForkJoinMode.WaitAll,
|
||||
Branches =
|
||||
{
|
||||
new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new WriteLine("Heating oven..."),
|
||||
Delay.FromSeconds(2),
|
||||
new WriteLine("Oven heated."),
|
||||
}
|
||||
},
|
||||
new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new WriteLine("Preparing dough and toppings..."),
|
||||
Delay.FromSeconds(2),
|
||||
new WriteLine("Pizza ready for heating."),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new WriteLine("Heating pizza in oven..."),
|
||||
Delay.FromSeconds(2),
|
||||
new WriteLine("Pizza is ready for delivery!"),
|
||||
Delay.FromSeconds(2),
|
||||
Inline(context => context.Set(Result, new Pizza(Size.Get(context), Flavor.Get(context)))),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
namespace Elsa.WorkflowServer.Web.Activities;
|
||||
|
||||
/// <summary>
|
||||
/// A sample activity that simulates doing some very heavy lifting.
|
||||
/// </summary>
|
||||
[Activity("Demo", "Demo", "Simulates very heavy lifting, which takes 15 seconds to complete.", Kind = ActivityKind.Task)]
|
||||
public class ProcessVideo : Activity
|
||||
{
|
||||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
const int frameCount = 15;
|
||||
Console.WriteLine("Begin processing...");
|
||||
for(var frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
Console.WriteLine("Processing frame {0}", frame + 1);
|
||||
}
|
||||
Console.WriteLine("Finished processing");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
using Elsa.Jobs.Abstractions;
|
||||
using Elsa.Jobs.Activities.Features;
|
||||
using Elsa.Jobs.Models;
|
||||
using Elsa.Jobs.Services;
|
||||
|
||||
namespace Elsa.WorkflowServer.Web.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// Jobs can be scheduled manually using <see cref="IJobQueue"/>,
|
||||
/// but when enabling the <see cref="JobActivitiesFeature"/>, these jobs become available as activities too.
|
||||
/// </summary>
|
||||
public class IndexBlockchainJob : Job
|
||||
{
|
||||
protected override async ValueTask ExecuteAsync(JobExecutionContext context)
|
||||
{
|
||||
Console.WriteLine("Indexing blockchain...");
|
||||
await Task.Delay(1000);
|
||||
Console.WriteLine("Finished indexing blockchain.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
namespace Elsa.WorkflowServer.Web.Models;
|
||||
|
||||
public record Pizza(int Size, string Flavor);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace Elsa.WorkflowServer.Web.Models;
|
||||
|
||||
/// <summary>
|
||||
/// A user class for security demo purposes.
|
||||
/// </summary>
|
||||
public record User(string FullName);
|
||||
|
|
@ -1,15 +1,10 @@
|
|||
using Elsa.EntityFrameworkCore.Extensions;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Identity;
|
||||
using Elsa.Identity.Options;
|
||||
using Elsa.Jobs.Activities.Services;
|
||||
using Elsa.EntityFrameworkCore.Modules.ActivityDefinitions;
|
||||
using Elsa.EntityFrameworkCore.Modules.Labels;
|
||||
using Elsa.EntityFrameworkCore.Modules.Management;
|
||||
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
using Elsa.Requirements;
|
||||
using Elsa.WorkflowServer.Web.Jobs;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Proto.Persistence.Sqlite;
|
||||
|
||||
|
|
@ -17,7 +12,6 @@ var builder = WebApplication.CreateBuilder(args);
|
|||
var services = builder.Services;
|
||||
var configuration = builder.Configuration;
|
||||
var sqliteConnectionString = configuration.GetConnectionString("Sqlite")!;
|
||||
var rabbitMqConnectionString = configuration.GetConnectionString("RabbitMq")!;
|
||||
var identityOptions = new IdentityOptions();
|
||||
var identityTokenOptions = new IdentityTokenOptions();
|
||||
var identitySection = configuration.GetSection("Identity");
|
||||
|
|
@ -43,13 +37,12 @@ services
|
|||
runtime.UseAsyncWorkflowStateExporter();
|
||||
runtime.UseMassTransitDispatcher();
|
||||
})
|
||||
.UseMassTransit(massTransit => massTransit.UseRabbitMq(rabbitMqConnectionString))
|
||||
.UseLabels(labels => labels.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
|
||||
.UseActivityDefinitions(feature => feature.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
|
||||
.UseJobs(jobs => jobs.ConfigureOptions = options => options.WorkerCount = 10)
|
||||
.UseJobActivities()
|
||||
.UseScheduling()
|
||||
.UseWorkflowsApi()
|
||||
.UseWorkflowsApi(api => api.AddFastEndpointsAssembly<Program>())
|
||||
.UseJavaScript()
|
||||
.UseLiquid()
|
||||
.UseHttp()
|
||||
|
|
@ -62,11 +55,6 @@ services.AddHttpContextAccessor();
|
|||
|
||||
// Configure middleware pipeline.
|
||||
var app = builder.Build();
|
||||
var serviceProvider = app.Services;
|
||||
|
||||
// Register a dummy job for demo purposes.
|
||||
var jobRegistry = serviceProvider.GetRequiredService<IJobRegistry>();
|
||||
jobRegistry.Add(typeof(IndexBlockchainJob));
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@
|
|||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;",
|
||||
"RabbitMq": "rabbitmq://guest:guest@localhost"
|
||||
"Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;"
|
||||
},
|
||||
"Identity": {
|
||||
"CreateDefaultAdmin": true,
|
||||
|
|
|
|||
|
|
@ -193,8 +193,6 @@ export namespace Components {
|
|||
interface ElsaMultiTextInput {
|
||||
"inputContext": ActivityInputContext;
|
||||
}
|
||||
interface ElsaNewButton {
|
||||
}
|
||||
interface ElsaNotificationsManager {
|
||||
"modalState": boolean;
|
||||
}
|
||||
|
|
@ -382,10 +380,6 @@ export interface ElsaMonacoEditorCustomEvent<T> extends CustomEvent<T> {
|
|||
detail: T;
|
||||
target: HTMLElsaMonacoEditorElement;
|
||||
}
|
||||
export interface ElsaNewButtonCustomEvent<T> extends CustomEvent<T> {
|
||||
detail: T;
|
||||
target: HTMLElsaNewButtonElement;
|
||||
}
|
||||
export interface ElsaPagerCustomEvent<T> extends CustomEvent<T> {
|
||||
detail: T;
|
||||
target: HTMLElsaPagerElement;
|
||||
|
|
@ -619,12 +613,6 @@ declare global {
|
|||
prototype: HTMLElsaMultiTextInputElement;
|
||||
new (): HTMLElsaMultiTextInputElement;
|
||||
};
|
||||
interface HTMLElsaNewButtonElement extends Components.ElsaNewButton, HTMLStencilElement {
|
||||
}
|
||||
var HTMLElsaNewButtonElement: {
|
||||
prototype: HTMLElsaNewButtonElement;
|
||||
new (): HTMLElsaNewButtonElement;
|
||||
};
|
||||
interface HTMLElsaNotificationsManagerElement extends Components.ElsaNotificationsManager, HTMLStencilElement {
|
||||
}
|
||||
var HTMLElsaNotificationsManagerElement: {
|
||||
|
|
@ -830,7 +818,6 @@ declare global {
|
|||
"elsa-monaco-editor": HTMLElsaMonacoEditorElement;
|
||||
"elsa-multi-line-input": HTMLElsaMultiLineInputElement;
|
||||
"elsa-multi-text-input": HTMLElsaMultiTextInputElement;
|
||||
"elsa-new-button": HTMLElsaNewButtonElement;
|
||||
"elsa-notifications-manager": HTMLElsaNotificationsManagerElement;
|
||||
"elsa-pager": HTMLElsaPagerElement;
|
||||
"elsa-panel": HTMLElsaPanelElement;
|
||||
|
|
@ -1029,9 +1016,6 @@ declare namespace LocalJSX {
|
|||
interface ElsaMultiTextInput {
|
||||
"inputContext"?: ActivityInputContext;
|
||||
}
|
||||
interface ElsaNewButton {
|
||||
"onNewClicked"?: (event: ElsaNewButtonCustomEvent<any>) => void;
|
||||
}
|
||||
interface ElsaNotificationsManager {
|
||||
"modalState"?: boolean;
|
||||
}
|
||||
|
|
@ -1189,7 +1173,6 @@ declare namespace LocalJSX {
|
|||
"elsa-monaco-editor": ElsaMonacoEditor;
|
||||
"elsa-multi-line-input": ElsaMultiLineInput;
|
||||
"elsa-multi-text-input": ElsaMultiTextInput;
|
||||
"elsa-new-button": ElsaNewButton;
|
||||
"elsa-notifications-manager": ElsaNotificationsManager;
|
||||
"elsa-pager": ElsaPager;
|
||||
"elsa-panel": ElsaPanel;
|
||||
|
|
@ -1255,7 +1238,6 @@ declare module "@stencil/core" {
|
|||
"elsa-monaco-editor": LocalJSX.ElsaMonacoEditor & JSXBase.HTMLAttributes<HTMLElsaMonacoEditorElement>;
|
||||
"elsa-multi-line-input": LocalJSX.ElsaMultiLineInput & JSXBase.HTMLAttributes<HTMLElsaMultiLineInputElement>;
|
||||
"elsa-multi-text-input": LocalJSX.ElsaMultiTextInput & JSXBase.HTMLAttributes<HTMLElsaMultiTextInputElement>;
|
||||
"elsa-new-button": LocalJSX.ElsaNewButton & JSXBase.HTMLAttributes<HTMLElsaNewButtonElement>;
|
||||
"elsa-notifications-manager": LocalJSX.ElsaNotificationsManager & JSXBase.HTMLAttributes<HTMLElsaNotificationsManagerElement>;
|
||||
"elsa-pager": LocalJSX.ElsaPager & JSXBase.HTMLAttributes<HTMLElsaPagerElement>;
|
||||
"elsa-panel": LocalJSX.ElsaPanel & JSXBase.HTMLAttributes<HTMLElsaPanelElement>;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
|
|
@ -6,6 +9,21 @@ namespace Elsa.Extensions;
|
|||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, string> SimpleAssemblyQualifiedTypeNameCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly-qualified name of the type, without any version info etc.
|
||||
/// E.g. "System.String, System.Private.CoreLib"
|
||||
/// </summary>
|
||||
public static string GetSimpleAssemblyQualifiedName(this Type type)
|
||||
{
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
|
||||
return SimpleAssemblyQualifiedTypeNameCache.GetOrAdd(type, GetSimpleAssemblyQualifiedNameInternal);
|
||||
}
|
||||
|
||||
private static string GetSimpleAssemblyQualifiedNameInternal(Type type) => $"{type.FullName}, {Assembly.GetAssembly(type)!.GetName().Name}";
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default value for the specified type.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ public static class ObjectConverter
|
|||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
|
||||
var underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
|
||||
var underlyingSourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
|
||||
|
||||
if (value is JsonElement jsonNumber && jsonNumber.ValueKind == JsonValueKind.Number && underlyingTargetType == typeof(string))
|
||||
return jsonNumber.ToString().ConvertTo(underlyingTargetType);
|
||||
|
|
@ -55,9 +56,24 @@ public static class ObjectConverter
|
|||
{
|
||||
if (jsonObject.ValueKind == JsonValueKind.String && underlyingTargetType != typeof(string))
|
||||
return jsonObject.GetString().ConvertTo(underlyingTargetType);
|
||||
|
||||
|
||||
return jsonObject.Deserialize(targetType, options);
|
||||
}
|
||||
|
||||
if (underlyingSourceType == typeof(string) && !underlyingTargetType.IsPrimitive)
|
||||
{
|
||||
var stringValue = (string)value;
|
||||
|
||||
try
|
||||
{
|
||||
if (stringValue.TrimStart().StartsWith("{"))
|
||||
return JsonSerializer.Deserialize(stringValue, underlyingTargetType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new TypeConversionException($"Failed to deserialize {stringValue} to {underlyingTargetType}", value, underlyingTargetType, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetType == typeof(object))
|
||||
return value;
|
||||
|
|
@ -65,8 +81,6 @@ public static class ObjectConverter
|
|||
if (underlyingTargetType.IsInstanceOfType(value))
|
||||
return value;
|
||||
|
||||
var underlyingSourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
|
||||
|
||||
if (underlyingSourceType == underlyingTargetType)
|
||||
return value;
|
||||
|
||||
|
|
@ -94,20 +108,7 @@ public static class ObjectConverter
|
|||
return Enum.ToObject(underlyingTargetType, Convert.ChangeType(value, typeof(int)));
|
||||
}
|
||||
|
||||
if (underlyingSourceType == typeof(string) && !underlyingTargetType.IsPrimitive)
|
||||
{
|
||||
var stringValue = (string)value;
|
||||
|
||||
try
|
||||
{
|
||||
if (stringValue.TrimStart().StartsWith("{"))
|
||||
return JsonSerializer.Deserialize(stringValue, underlyingTargetType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new TypeConversionException($"Failed to deserialize {stringValue} to {underlyingTargetType}", value, underlyingTargetType, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (value is string s && string.IsNullOrWhiteSpace(s))
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -9,26 +9,51 @@ using Elsa.Workflows.Runtime.Entities;
|
|||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Adds extensions to <see cref="IRouteTable"/>.
|
||||
/// </summary>
|
||||
public static class RouteTableExtensions
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions;
|
||||
|
||||
static RouteTableExtensions()
|
||||
{
|
||||
SerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds routes from the specified set of triggers.
|
||||
/// </summary>
|
||||
public static void AddRoutes(this IRouteTable routeTable, IEnumerable<StoredTrigger> triggers)
|
||||
{
|
||||
var paths = Filter(triggers).Select(Deserialize).Select(x => x.Path).ToList();
|
||||
var paths = Filter(triggers).Select(Deserialize).Select(x => x.Path).Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
|
||||
routeTable.AddRange(paths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds routes from the specified set of bookmarks.
|
||||
/// </summary>
|
||||
public static void AddRoutes(this IRouteTable routeTable, IEnumerable<Bookmark> bookmarks)
|
||||
{
|
||||
var paths = Filter(bookmarks).Select(Deserialize).Select(x => x.Path).ToList();
|
||||
routeTable.AddRange(paths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes routes from the specified set of triggers.
|
||||
/// </summary>
|
||||
public static void RemoveRoutes(this IRouteTable routeTable, IEnumerable<StoredTrigger> triggers)
|
||||
{
|
||||
var paths = Filter(triggers).Select(Deserialize).Select(x => x.Path).ToList();
|
||||
routeTable.RemoveRange(paths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes routes from the specified set of bookmarks.
|
||||
/// </summary>
|
||||
public static void RemoveRoutes(this IRouteTable routeTable, IEnumerable<Bookmark> bookmarks)
|
||||
{
|
||||
var paths = Filter(bookmarks).Select(Deserialize).Select(x => x.Path).ToList();
|
||||
|
|
@ -39,5 +64,5 @@ public static class RouteTableExtensions
|
|||
private static IEnumerable<Bookmark> Filter(IEnumerable<Bookmark> triggers) => triggers.Where(x => x.Name == ActivityTypeNameHelper.GenerateTypeName<HttpEndpoint>());
|
||||
private static HttpEndpointBookmarkPayload Deserialize(StoredTrigger trigger) => Deserialize(trigger.Data!);
|
||||
private static HttpEndpointBookmarkPayload Deserialize(Bookmark bookmark) => Deserialize(bookmark.Data!);
|
||||
private static HttpEndpointBookmarkPayload Deserialize(string model) => JsonSerializer.Deserialize<HttpEndpointBookmarkPayload>(model)!;
|
||||
private static HttpEndpointBookmarkPayload Deserialize(string model) => JsonSerializer.Deserialize<HttpEndpointBookmarkPayload>(model, SerializerOptions)!;
|
||||
}
|
||||
|
|
@ -5,26 +5,38 @@ using Microsoft.Extensions.Caching.Memory;
|
|||
|
||||
namespace Elsa.Http.Implementations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class RouteTable : IRouteTable
|
||||
{
|
||||
private static readonly object Key = new();
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
public RouteTable(IMemoryCache cache) => _cache = cache;
|
||||
private ConcurrentDictionary<string, string> Routes => _cache.GetOrCreate(Key, _ => new ConcurrentDictionary<string, string>());
|
||||
private ConcurrentDictionary<string, string> Routes => _cache.GetOrCreate(Key, _ => new ConcurrentDictionary<string, string>())!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(string path) => Routes.TryAdd(path, path);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove(string path) => Routes.TryRemove(path, out _);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddRange(IEnumerable<string> paths)
|
||||
{
|
||||
foreach (var path in paths) Add(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveRange(IEnumerable<string> paths)
|
||||
{
|
||||
foreach (var path in paths) Remove(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<string> GetEnumerator() => Routes.Values.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ public class JsonElementHttpResponseContentReader : IHttpResponseContentReader
|
|||
public bool GetSupportsContentType(string contentType) => contentType.Contains("/json", StringComparison.OrdinalIgnoreCase);
|
||||
public async Task<object> ReadAsync(HttpResponseMessage response, object context, CancellationToken cancellationToken)
|
||||
{
|
||||
var json = (await response.Content.ReadAsStringAsync()).Trim();
|
||||
var json = (await response.Content.ReadAsStringAsync(cancellationToken)).Trim();
|
||||
return JsonDocument.Parse(json).RootElement;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ namespace Elsa.Jobs.Activities.Attributes;
|
|||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class JobAttribute : Attribute
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public JobAttribute(string @namespace, string? category, string? description = default)
|
||||
{
|
||||
Namespace = @namespace;
|
||||
|
|
@ -10,6 +11,7 @@ public class JobAttribute : Attribute
|
|||
Category = category;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public JobAttribute(string @namespace, string? description = default)
|
||||
{
|
||||
Namespace = @namespace;
|
||||
|
|
@ -17,6 +19,7 @@ public class JobAttribute : Attribute
|
|||
Category = @namespace;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public JobAttribute(string @namespace, string? activityType, string? description = default, string? category = default)
|
||||
{
|
||||
Namespace = @namespace;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ public class JobActivityProvider : IActivityProvider
|
|||
_jobRegistry = jobRegistry;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<ActivityDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var jobTypes = _jobRegistry.List();
|
||||
|
|
|
|||
58
src/modules/Elsa.MassTransit/Activities/MessageReceived.cs
Normal file
58
src/modules/Elsa.MassTransit/Activities/MessageReceived.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.MassTransit.Implementations;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
namespace Elsa.MassTransit.Activities;
|
||||
|
||||
/// <summary>
|
||||
/// A generic activity that waits for a message of a given type to be received. Used by the <see cref="MassTransitActivityTypeProvider"/>.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public class MessageReceived : Trigger<object>
|
||||
{
|
||||
internal const string InputKey = "Message";
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonConstructor]
|
||||
public MessageReceived()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The message type to receive.
|
||||
/// </summary>
|
||||
public Type MessageType { get; set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object GetTriggerPayload(TriggerIndexingContext context) => GetBookmarkPayload(context.ExpressionExecutionContext);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
// If we did not receive external input, it means we are just now encountering this activity and we need to block execution by creating a bookmark.
|
||||
if (!context.TryGetInput<object>(InputKey, out var message))
|
||||
{
|
||||
// Create bookmarks for when we receive the expected HTTP request.
|
||||
context.CreateBookmark(GetBookmarkPayload(context.ExpressionExecutionContext));
|
||||
return;
|
||||
}
|
||||
|
||||
// Provide the received message as output.
|
||||
context.Set(Result, message);
|
||||
|
||||
// Complete.
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
|
||||
private object GetBookmarkPayload(ExpressionExecutionContext context)
|
||||
{
|
||||
// Generate bookmark data for message type.
|
||||
return new MessageReceivedBookmarkPayload(MessageType);
|
||||
}
|
||||
}
|
||||
|
||||
internal record MessageReceivedBookmarkPayload(Type MessageType);
|
||||
43
src/modules/Elsa.MassTransit/Activities/PublishMessage.cs
Normal file
43
src/modules/Elsa.MassTransit/Activities/PublishMessage.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Expressions.Helpers;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.MassTransit.Implementations;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using MassTransit;
|
||||
using MassTransit.Middleware;
|
||||
|
||||
namespace Elsa.MassTransit.Activities;
|
||||
|
||||
/// <summary>
|
||||
/// A generic activity that publishes a message of a given type. Used by the <see cref="MassTransitActivityTypeProvider"/>.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public class PublishMessage : Activity
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[JsonConstructor]
|
||||
public PublishMessage()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The message type to publish.
|
||||
/// </summary>
|
||||
public Type MessageType { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The message to send. Must be a concrete implementation of the configured <see cref="MessageType"/>.
|
||||
/// </summary>
|
||||
[Input(Description = "The message to send. Must be a concrete implementation of the configured message type.")]
|
||||
public Input<object> Message { get; set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var bus = context.GetRequiredService<IBus>();
|
||||
var message = Message.Get(context).ConvertTo(MessageType)!;
|
||||
await bus.Publish(message, context.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
using Elsa.MassTransit.Activities;
|
||||
using Elsa.Workflows.Core.Helpers;
|
||||
using Elsa.Workflows.Runtime.Models;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using MassTransit;
|
||||
|
||||
namespace Elsa.MassTransit.Consumers;
|
||||
|
||||
/// <summary>
|
||||
/// A consumer of various dispatch message types to asynchronously execute workflows.
|
||||
/// </summary>
|
||||
public class WorkflowMessageConsumer<T> : IConsumer<T> where T : class
|
||||
{
|
||||
private readonly IWorkflowDispatcher _workflowRuntime;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public WorkflowMessageConsumer(IWorkflowDispatcher workflowRuntime)
|
||||
{
|
||||
_workflowRuntime = workflowRuntime;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Consume(ConsumeContext<T> context)
|
||||
{
|
||||
var cancellationToken = context.CancellationToken;
|
||||
var messageType = typeof(T);
|
||||
var message = context.Message;
|
||||
var activityTypeName = ActivityTypeNameHelper.GenerateTypeName(messageType);
|
||||
var bookmark = new MessageReceivedBookmarkPayload(messageType);
|
||||
var correlationId = context.CorrelationId?.ToString();
|
||||
var input = new Dictionary<string, object> { [MessageReceived.InputKey] = message };
|
||||
var request = new DispatchTriggerWorkflowsRequest(activityTypeName, bookmark, correlationId, input);
|
||||
await _workflowRuntime.DispatchAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using Elsa.Features.Services;
|
||||
using Elsa.MassTransit.Features;
|
||||
using Elsa.MassTransit.Implementations;
|
||||
using MassTransit;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
|
|
@ -11,6 +12,7 @@ namespace Elsa.Extensions;
|
|||
public static class MassTransitFeatureExtensions
|
||||
{
|
||||
private static readonly object ServiceBusConsumerTypesKey = new();
|
||||
private static readonly object MessageTypesKey = new();
|
||||
|
||||
/// <summary>
|
||||
/// Registers the specified type for MassTransit service bus consumer discovery.
|
||||
|
|
@ -26,9 +28,29 @@ public static class MassTransitFeatureExtensions
|
|||
types.Add(type);
|
||||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a message type which is to be used by the <see cref="MassTransitActivityTypeProvider"/> to dynamically provide activities to send and receive these messages.
|
||||
/// </summary>
|
||||
public static MassTransitFeature AddMessageType<T>(this MassTransitFeature feature) where T : class => feature.AddMessageType(typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Returns all collected types for discovery of service bus consumers.
|
||||
/// Registers a message type which is to be used by the <see cref="MassTransitActivityTypeProvider"/> to dynamically provide activities to send and receive these messages.
|
||||
/// </summary>
|
||||
public static MassTransitFeature AddMessageType(this MassTransitFeature feature, Type type)
|
||||
{
|
||||
var types = feature.Module.Properties.GetOrAdd(MessageTypesKey, () => new HashSet<Type>());
|
||||
types.Add(type);
|
||||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all collected consumer types.
|
||||
/// </summary>
|
||||
internal static IEnumerable<Type> GetConsumers(this MassTransitFeature feature) => feature.Module.Properties.GetOrAdd(ServiceBusConsumerTypesKey, () => new HashSet<Type>());
|
||||
|
||||
/// <summary>
|
||||
/// Returns all collected message types.
|
||||
/// </summary>
|
||||
internal static IEnumerable<Type> GetMessages(this MassTransitFeature feature) => feature.Module.Properties.GetOrAdd(MessageTypesKey, () => new HashSet<Type>());
|
||||
}
|
||||
|
|
@ -1,10 +1,20 @@
|
|||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.MassTransit.Consumers;
|
||||
using Elsa.MassTransit.Implementations;
|
||||
using Elsa.MassTransit.Options;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Helpers;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Core.Serialization.Converters;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.Workflows.Management.Options;
|
||||
using Humanizer;
|
||||
using MassTransit;
|
||||
using MassTransit.Serialization;
|
||||
using MassTransit.Serialization.JsonConverters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.MassTransit.Features;
|
||||
|
|
@ -22,7 +32,7 @@ public class MassTransitFeature : FeatureBase
|
|||
/// <summary>
|
||||
/// A delegate that can be set to configure MassTransit's <see cref="IBusRegistrationConfigurator"/>.
|
||||
/// </summary>
|
||||
public Action<IBusRegistrationConfigurator>? BusConfigurator { get; set; }
|
||||
public Action<IBusRegistrationConfigurator>? BusConfigurator { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
|
|
@ -31,12 +41,36 @@ public class MassTransitFeature : FeatureBase
|
|||
{
|
||||
configure.UsingInMemory((context, configurator) => { configurator.ConfigureEndpoints(context); });
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
var messageTypes = this.GetMessages();
|
||||
|
||||
Services.AddActivityProvider<MassTransitActivityTypeProvider>();
|
||||
AddMassTransit(BusConfigurator);
|
||||
|
||||
// Add collected message types to options.
|
||||
Services.Configure<MassTransitActivityOptions>(options => options.MessageTypes = new HashSet<Type>(messageTypes));
|
||||
|
||||
// Add collected message types as available variable types.
|
||||
Services.Configure<ManagementOptions>(options =>
|
||||
{
|
||||
foreach (var messageType in messageTypes)
|
||||
{
|
||||
var activityAttr = messageType.GetCustomAttribute<ActivityAttribute>();
|
||||
var categoryAttr = messageType.GetCustomAttribute<CategoryAttribute>();
|
||||
var category = categoryAttr?.Category ?? activityAttr?.Category ?? "MassTransit";
|
||||
var descriptionAttr = messageType.GetCustomAttribute<DescriptionAttribute>();
|
||||
var description = descriptionAttr?.Description ?? activityAttr?.Description;
|
||||
options.VariableDescriptors.Add(new VariableDescriptor(messageType, category, description));
|
||||
}
|
||||
});
|
||||
|
||||
// Configure message serializer.
|
||||
SystemTextJsonMessageSerializer.Options.Converters.Add(new TypeJsonConverter(new WellKnownTypeRegistry()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -44,7 +78,12 @@ public class MassTransitFeature : FeatureBase
|
|||
/// </summary>
|
||||
private void AddMassTransit(Action<IBusRegistrationConfigurator>? config)
|
||||
{
|
||||
var consumerTypes = this.GetConsumers().ToArray();
|
||||
// For each message type, create a concrete WorkflowMessageConsumer<T>.
|
||||
var workflowMessageConsumerType = typeof(WorkflowMessageConsumer<>);
|
||||
var workflowMessageConsumers = this.GetMessages().Select(x => workflowMessageConsumerType.MakeGenericType(x));
|
||||
|
||||
// Concatenate the manually registered consumers with the workflow message consumers.
|
||||
var consumerTypes = this.GetConsumers().Concat(workflowMessageConsumers).ToArray();
|
||||
|
||||
Services.AddMassTransit(bus =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.MassTransit.Activities;
|
||||
using Elsa.MassTransit.Options;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Helpers;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.Workflows.Management.Services;
|
||||
using Humanizer;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.MassTransit.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// Provides activities to the system from the configured MassTransit message types.
|
||||
/// </summary>
|
||||
public class MassTransitActivityTypeProvider : IActivityProvider
|
||||
{
|
||||
private readonly IActivityFactory _activityFactory;
|
||||
private readonly MassTransitActivityOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public MassTransitActivityTypeProvider(IActivityFactory activityFactory, IOptions<MassTransitActivityOptions> options)
|
||||
{
|
||||
_activityFactory = activityFactory;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<ActivityDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var messageTypes = _options.MessageTypes;
|
||||
var descriptors = CreateDescriptors(messageTypes).ToList();
|
||||
return new(descriptors);
|
||||
}
|
||||
|
||||
private IEnumerable<ActivityDescriptor> CreateDescriptors(IEnumerable<Type> messageTypes)
|
||||
{
|
||||
foreach (var messageType in messageTypes)
|
||||
{
|
||||
yield return CreateMessageReceivedDescriptor(messageType);
|
||||
|
||||
if(messageType.IsClass)
|
||||
yield return CreatePublishMessageDescriptor(messageType);
|
||||
}
|
||||
}
|
||||
|
||||
private ActivityDescriptor CreateMessageReceivedDescriptor(Type messageType)
|
||||
{
|
||||
var activityAttr = messageType.GetCustomAttribute<ActivityAttribute>();
|
||||
var typeName = activityAttr?.Type ?? messageType.Name;
|
||||
var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(messageType);
|
||||
var displayNameAttr = messageType.GetCustomAttribute<DisplayNameAttribute>();
|
||||
var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? typeName.Humanize(LetterCasing.Title);
|
||||
var categoryAttr = messageType.GetCustomAttribute<CategoryAttribute>();
|
||||
var category = categoryAttr?.Category ?? activityAttr?.Category ?? "MassTransit";
|
||||
var descriptionAttr = messageType.GetCustomAttribute<DescriptionAttribute>();
|
||||
var description = descriptionAttr?.Description ?? activityAttr?.Description;
|
||||
|
||||
return new()
|
||||
{
|
||||
TypeName = fullTypeName,
|
||||
Version = 1,
|
||||
DisplayName = displayName,
|
||||
Description = description,
|
||||
Category = category,
|
||||
Kind = ActivityKind.Trigger,
|
||||
IsBrowsable = true,
|
||||
ActivityType = typeof(MessageReceived),
|
||||
Outputs =
|
||||
{
|
||||
new OutputDescriptor
|
||||
{
|
||||
Description = "The received message",
|
||||
DisplayName = "Received Message",
|
||||
Name = nameof(MessageReceived.Result),
|
||||
Type = typeof(object)
|
||||
}
|
||||
},
|
||||
Constructor = context =>
|
||||
{
|
||||
var activity = _activityFactory.Create<MessageReceived>(context);
|
||||
activity.Type = fullTypeName;
|
||||
activity.MessageType = messageType;
|
||||
return activity;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ActivityDescriptor CreatePublishMessageDescriptor(Type messageType)
|
||||
{
|
||||
var activityAttr = messageType.GetCustomAttribute<ActivityAttribute>();
|
||||
var typeName = activityAttr?.Type ?? messageType.Name;
|
||||
var ns = ActivityTypeNameHelper.GenerateNamespace(messageType);
|
||||
var fullTypeName = ns + ".Publish" + typeName;
|
||||
var displayNameAttr = messageType.GetCustomAttribute<DisplayNameAttribute>();
|
||||
var displayName = "Publish " + (displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? typeName.Humanize(LetterCasing.Title));
|
||||
var categoryAttr = messageType.GetCustomAttribute<CategoryAttribute>();
|
||||
var category = categoryAttr?.Category ?? activityAttr?.Category ?? "MassTransit";
|
||||
var descriptionAttr = messageType.GetCustomAttribute<DescriptionAttribute>();
|
||||
var description = descriptionAttr?.Description ?? activityAttr?.Description;
|
||||
|
||||
return new()
|
||||
{
|
||||
TypeName = fullTypeName,
|
||||
Version = 1,
|
||||
DisplayName = displayName,
|
||||
Description = description,
|
||||
Category = category,
|
||||
Kind = ActivityKind.Action,
|
||||
IsBrowsable = true,
|
||||
ActivityType = typeof(PublishMessage),
|
||||
Inputs =
|
||||
{
|
||||
new InputDescriptor
|
||||
{
|
||||
Description = "The message to publish.",
|
||||
UIHint = InputUIHints.MultiLine,
|
||||
DisplayName = "Message",
|
||||
Type = typeof(Input<object>),
|
||||
Name = nameof(PublishMessage.Message)
|
||||
}
|
||||
},
|
||||
Constructor = context =>
|
||||
{
|
||||
var activity = _activityFactory.Create<PublishMessage>(context);
|
||||
activity.Type = fullTypeName;
|
||||
activity.MessageType = messageType;
|
||||
return activity;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
namespace Elsa.MassTransit.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Provides settings to the RabbitMQ broker for MassTransit.
|
||||
/// </summary>
|
||||
public class MassTransitActivityOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of message types that can be sent and received in the form of workflow activities.
|
||||
/// </summary>
|
||||
public ISet<Type> MessageTypes { get; set; } = new HashSet<Type>();
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ namespace Elsa.MassTransit.Options;
|
|||
/// </summary>
|
||||
public class RabbitMqOptions
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Host { get; set; } = default!;
|
||||
public string Username { get; set; } = default!;
|
||||
public string Password { get; set; } = default!;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Mediator.Services;
|
||||
using Elsa.ProtoActor.Extensions;
|
||||
using Elsa.ProtoActor.Grains;
|
||||
using Elsa.ProtoActor.Protos;
|
||||
using Elsa.Workflows.Core.Notifications;
|
||||
|
|
@ -32,7 +33,7 @@ internal class UpdateRunningWorkflowsHandler : INotificationHandler<WorkflowExec
|
|||
{
|
||||
DefinitionId = workflowState.DefinitionId,
|
||||
Version = workflowState.DefinitionVersion,
|
||||
CorrelationId = workflowState.CorrelationId,
|
||||
CorrelationId = workflowState.CorrelationId.EmptyIfNull(),
|
||||
InstanceId = workflowState.Id
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
using Elsa.Workflows.Api.Features;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
public static class WorkflowsApiFeatureExtensions
|
||||
{
|
||||
public static WorkflowsApiFeature AddFastEndpointsAssembly<TMarker>(this WorkflowsApiFeature feature)
|
||||
{
|
||||
feature.Module.AddFastEndpointsAssembly<TMarker>();
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ public class WorkflowsApiFeature : FeatureBase
|
|||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
|
||||
Module.AddFastEndpointsAssembly(GetType());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
public static class TypeExtensions
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, string> SimpleAssemblyQualifiedTypeNameCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly-qualified name of the type, without any version info etc.
|
||||
/// E.g. "System.String, System.Private.CoreLib"
|
||||
/// </summary>
|
||||
public static string GetSimpleAssemblyQualifiedName(this Type type)
|
||||
{
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
|
||||
return SimpleAssemblyQualifiedTypeNameCache.GetOrAdd(type, GetSimpleAssemblyQualifiedNameInternal);
|
||||
}
|
||||
|
||||
private static string GetSimpleAssemblyQualifiedNameInternal(Type type) => $"{type.FullName}, {Assembly.GetAssembly(type)!.GetName().Name}";
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ public class WorkflowsFeature : FeatureBase
|
|||
.AddSingleton<IBookmarkHasher, BookmarkHasher>()
|
||||
.AddSingleton<IIdentityGenerator, GuidIdentityGenerator>()
|
||||
.AddSingleton<IWorkflowExecutionContextFactory, DefaultWorkflowExecutionContextFactory>()
|
||||
.AddSingleton<IBookmarkPayloadSerializer, BookmarkPayloadSerializer>()
|
||||
.AddSingleton<IBookmarkPayloadSerializer>(sp => ActivatorUtilities.CreateInstance<BookmarkPayloadSerializer>(sp))
|
||||
.AddTransient<WorkflowBuilder>()
|
||||
.AddSingleton(typeof(Func<IWorkflowBuilder>), sp => () => sp.GetRequiredService<WorkflowBuilder>())
|
||||
.AddSingleton<IWorkflowBuilderFactory, WorkflowBuilderFactory>()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Workflows.Core.Serialization.Converters;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
namespace Elsa.Workflows.Core.Implementations;
|
||||
|
|
@ -7,13 +8,15 @@ public class BookmarkPayloadSerializer : IBookmarkPayloadSerializer
|
|||
{
|
||||
private readonly JsonSerializerOptions _settings;
|
||||
|
||||
public BookmarkPayloadSerializer()
|
||||
public BookmarkPayloadSerializer(IWellKnownTypeRegistry wellKnownTypeRegistry)
|
||||
{
|
||||
_settings = new JsonSerializerOptions
|
||||
{
|
||||
// Enables serialization of ValueTuples, which use fields instead of properties.
|
||||
IncludeFields = true
|
||||
};
|
||||
|
||||
_settings.Converters.Add(new TypeJsonConverter(wellKnownTypeRegistry));
|
||||
}
|
||||
|
||||
public T Deserialize<T>(string json) where T : notnull => JsonSerializer.Deserialize<T>(json, _settings)!;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class PolymorphicConverter : JsonConverter<object>
|
|||
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
|
||||
{
|
||||
var typeName = value.GetType().GetSimpleAssemblyQualifiedName();
|
||||
var wrappedValue = JsonSerializer.SerializeToNode(value)!;
|
||||
var wrappedValue = JsonSerializer.SerializeToNode(value, options)!;
|
||||
wrappedValue["$type"] = typeName;
|
||||
wrappedValue.WriteTo(writer);
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ public class PolymorphicConverter : JsonConverter<object>
|
|||
var element = JsonElement.ParseValue(ref reader);
|
||||
var typeName = element.GetProperty("$type").GetString()!;
|
||||
var type = Type.GetType(typeName)!;
|
||||
var value = element.Deserialize(type);
|
||||
var value = element.Deserialize(type, options);
|
||||
|
||||
return value!;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Elsa.Expressions.Services;
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Core.Helpers;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
using Elsa.Workflows.Management.Entities;
|
||||
using Elsa.Workflows.Runtime.Comparers;
|
||||
|
|
@ -30,6 +31,7 @@ public class TriggerIndexer : ITriggerIndexer
|
|||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IBookmarkHasher _hasher;
|
||||
private readonly ILogger _logger;
|
||||
private readonly JsonSerializerOptions _serializerOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
|
|
@ -43,6 +45,7 @@ public class TriggerIndexer : ITriggerIndexer
|
|||
IEventPublisher eventPublisher,
|
||||
IServiceProvider serviceProvider,
|
||||
IBookmarkHasher hasher,
|
||||
SerializerOptionsProvider serializerOptionsProvider,
|
||||
ILogger<TriggerIndexer> logger)
|
||||
{
|
||||
_activityWalker = activityWalker;
|
||||
|
|
@ -54,6 +57,7 @@ public class TriggerIndexer : ITriggerIndexer
|
|||
_hasher = hasher;
|
||||
_logger = logger;
|
||||
_workflowDefinitionService = workflowDefinitionService;
|
||||
_serializerOptions = serializerOptionsProvider.CreateDefaultOptions();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -152,7 +156,7 @@ public class TriggerIndexer : ITriggerIndexer
|
|||
Name = triggerTypeName,
|
||||
ActivityId = trigger.Id,
|
||||
Hash = _hasher.Hash(triggerTypeName, x),
|
||||
Data = JsonSerializer.Serialize(x)
|
||||
Data = JsonSerializer.Serialize(x, _serializerOptions)
|
||||
});
|
||||
|
||||
return triggers.ToList();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\bundles\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Http\Elsa.Http.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Identity\Elsa.Identity.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Liquid\Elsa.Liquid.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.MassTransit\Elsa.MassTransit.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Workflows.Designer\Elsa.Workflows.Designer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Pages\Index.cshtml" />
|
||||
<AdditionalFiles Include="Pages\_ViewImports.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using Elsa.Samples.MassTransitActivities.Messages;
|
||||
using FastEndpoints;
|
||||
using MassTransit;
|
||||
|
||||
namespace Elsa.Samples.MassTransitActivities.Endpoints.Orders.Create;
|
||||
|
||||
public class Create : EndpointWithoutRequest
|
||||
{
|
||||
private readonly IBus _bus;
|
||||
|
||||
public Create(IBus bus)
|
||||
{
|
||||
_bus = bus;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("orders");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _bus.Publish<OrderCreated>(new
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
CustomerId = "1",
|
||||
Product = "Pizza",
|
||||
Quantity = 5,
|
||||
Total = 62.5
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Elsa.Samples.MassTransitActivities.Messages;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public record OrderCompleted(string OrderId);
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
namespace Elsa.Samples.MassTransitActivities.Messages;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public record OrderCreated(
|
||||
|
||||
string Id,
|
||||
string CustomerId,
|
||||
string Product,
|
||||
int Quantity,
|
||||
decimal Total
|
||||
);
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
@page
|
||||
@using Elsa.Workflows.Designer
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@{
|
||||
var serverUrl = Url.Content("elsa/api");
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Elsa Workflows 3.0</title>
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
<link rel="stylesheet" href="_content/Elsa.Workflows.Designer/elsa-workflows-designer/elsa-workflows-designer.css">
|
||||
<script src="_content/Elsa.Workflows.Designer/monaco-editor/min/vs/loader.js"></script>
|
||||
<script type="module" src="_content/Elsa.Workflows.Designer/elsa-workflows-designer/elsa-workflows-designer.esm.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<component type="typeof(ElsaStudio)" render-mode="ServerPrerendered" param-ServerUrl="@serverUrl"/>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@namespace Elsa.Samples.MassTransitActivities.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
using Elsa.EntityFrameworkCore.Extensions;
|
||||
using Elsa.EntityFrameworkCore.Modules.Management;
|
||||
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Samples.MassTransitActivities.Messages;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var configuration = builder.Configuration;
|
||||
var rabbitMqConnectionString = configuration.GetConnectionString("RabbitMq")!;
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddElsa(elsa =>
|
||||
{
|
||||
// Configure management feature to use EF Core.
|
||||
elsa.UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlite()));
|
||||
|
||||
// Configure runtime feature to use EF Core.
|
||||
elsa.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UseSqlite()));
|
||||
|
||||
// Expose API endpoints.
|
||||
elsa.UseWorkflowsApi();
|
||||
|
||||
// Add services for HTTP activities and workflow middleware.
|
||||
elsa.UseHttp();
|
||||
|
||||
// Use JavaScript and Liquid.
|
||||
elsa.UseJavaScript();
|
||||
elsa.UseLiquid();
|
||||
|
||||
// Configure identity so that we can create a default admin user.
|
||||
elsa.UseIdentity(identity =>
|
||||
{
|
||||
identity.IdentityOptions.CreateDefaultAdmin = builder.Environment.IsDevelopment();
|
||||
identity.TokenOptions.SigningKey = "secret-token-signing-key";
|
||||
identity.TokenOptions.Lifetime = TimeSpan.FromDays(1);
|
||||
});
|
||||
|
||||
// Use default authentication (JWT).
|
||||
elsa.UseDefaultAuthentication();
|
||||
|
||||
// Configure MassTransit.
|
||||
elsa.UseMassTransit(massTransit =>
|
||||
{
|
||||
massTransit.UseRabbitMq(rabbitMqConnectionString);
|
||||
massTransit.AddMessageType<OrderCompleted>();
|
||||
massTransit.AddMessageType<OrderCreated>();
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));
|
||||
|
||||
// Add Razor pages.
|
||||
builder.Services.AddRazorPages();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
app.UseHttpsRedirection();
|
||||
app.UseCors();
|
||||
app.UseStaticFiles();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseWorkflowsApi();
|
||||
app.UseWorkflows();
|
||||
app.MapRazorPages();
|
||||
app.Run();
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:38703",
|
||||
"sslPort": 44342
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5207",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7299;http://localhost:5207",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"RabbitMq": "rabbitmq://guest:guest@localhost"
|
||||
}
|
||||
}
|
||||
|
|
@ -15,9 +15,4 @@
|
|||
<ProjectReference Include="..\..\..\modules\Elsa.Workflows.Designer\Elsa.Workflows.Designer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Pages\Index.cshtml" />
|
||||
<AdditionalFiles Include="Pages\_ViewImports.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
Loading…
Reference in a new issue