diff --git a/src/bundles/Elsa.Server.Web/Program.cs b/src/bundles/Elsa.Server.Web/Program.cs index 28631b6a1..bb2fc19ad 100644 --- a/src/bundles/Elsa.Server.Web/Program.cs +++ b/src/bundles/Elsa.Server.Web/Program.cs @@ -10,7 +10,6 @@ using Elsa.EntityFrameworkCore.Modules.Identity; using Elsa.EntityFrameworkCore.Modules.Management; using Elsa.EntityFrameworkCore.Modules.Runtime; using Elsa.Extensions; -using Elsa.Http.Handlers; using Elsa.Http.Options; using Elsa.MongoDb.Extensions; using Elsa.MongoDb.Modules.Identity; diff --git a/src/bundles/Elsa.Server.Web/appsettings.json b/src/bundles/Elsa.Server.Web/appsettings.json index 9859fc4f8..131ccbe59 100644 --- a/src/bundles/Elsa.Server.Web/appsettings.json +++ b/src/bundles/Elsa.Server.Web/appsettings.json @@ -17,7 +17,7 @@ "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;", "MongoDb": "mongodb://localhost:27017/elsa-workflows", "AzureServiceBus": "", - "RabbitMq": "amqp://guest:guest@localhost:5672/elsa" + "RabbitMq": "amqp://guest:guest@localhost:5672" }, "Smtp": { "Host": "localhost", diff --git a/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj b/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj index 97e625599..4029b6de1 100644 --- a/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj +++ b/src/bundles/Elsa.ServerAndStudio.Web/Elsa.ServerAndStudio.Web.csproj @@ -18,6 +18,7 @@ + diff --git a/src/bundles/Elsa.ServerAndStudio.Web/Program.cs b/src/bundles/Elsa.ServerAndStudio.Web/Program.cs index eb19d40ac..3726828da 100644 --- a/src/bundles/Elsa.ServerAndStudio.Web/Program.cs +++ b/src/bundles/Elsa.ServerAndStudio.Web/Program.cs @@ -6,6 +6,8 @@ using Elsa.Extensions; using Elsa.Webhooks.Extensions; using Microsoft.AspNetCore.Mvc; +const bool useMassTransit = true; + var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseStaticWebAssets(); var services = builder.Services; @@ -16,32 +18,39 @@ var identityTokenSection = identitySection.GetSection("Tokens"); // Add Elsa services. services - .AddElsa(elsa => elsa - .UseSasTokens() - .UseIdentity(identity => + .AddElsa(elsa => + { + elsa + .UseSasTokens() + .UseIdentity(identity => + { + identity.IdentityOptions = options => identitySection.Bind(options); + identity.TokenOptions = options => identityTokenSection.Bind(options); + identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options)); + identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options)); + identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options)); + }) + .UseDefaultAuthentication() + .UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString))) + .UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString))) + .UseScheduling() + .UseJavaScript(options => options.AllowClrAccess = true) + .UseLiquid() + .UseCSharp() + .UsePython() + .UseHttp(http => http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options)) + .UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options)) + .UseWebhooks(webhooks => webhooks.WebhookOptions = options => builder.Configuration.GetSection("Webhooks").Bind(options)) + .UseWorkflowsApi() + .UseRealTimeWorkflows() + .AddActivitiesFrom() + .AddWorkflowsFrom(); + + if (useMassTransit) { - identity.IdentityOptions = options => identitySection.Bind(options); - identity.TokenOptions = options => identityTokenSection.Bind(options); - identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options)); - identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options)); - identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options)); - }) - .UseDefaultAuthentication() - .UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString))) - .UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString))) - .UseScheduling() - .UseJavaScript(options => options.AllowClrAccess = true) - .UseLiquid() - .UseCSharp() - .UsePython() - .UseHttp(http => http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options)) - .UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options)) - .UseWebhooks(webhooks => webhooks.WebhookOptions = options => builder.Configuration.GetSection("Webhooks").Bind(options)) - .UseWorkflowsApi() - .UseRealTimeWorkflows() - .AddActivitiesFrom() - .AddWorkflowsFrom() - ); + elsa.UseMassTransit(); + } + }); services.AddHealthChecks(); diff --git a/src/bundles/Elsa.ServerAndStudio.Web/appsettings.json b/src/bundles/Elsa.ServerAndStudio.Web/appsettings.json index 6a427b4da..4f0c9d1bc 100644 --- a/src/bundles/Elsa.ServerAndStudio.Web/appsettings.json +++ b/src/bundles/Elsa.ServerAndStudio.Web/appsettings.json @@ -13,7 +13,8 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;" + "Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;", + "RabbitMq": "rabbitmq://guest:guest@localhost:5672" }, "Hosting": { "BasePath": "" diff --git a/src/modules/Elsa.MassTransit/Activities/PublishMessage.cs b/src/modules/Elsa.MassTransit/Activities/PublishMessage.cs index e5972aa46..1802f3bd8 100644 --- a/src/modules/Elsa.MassTransit/Activities/PublishMessage.cs +++ b/src/modules/Elsa.MassTransit/Activities/PublishMessage.cs @@ -6,6 +6,7 @@ using Elsa.MassTransit.Implementations; using Elsa.Workflows; using Elsa.Workflows.Attributes; using Elsa.Workflows.Models; +using Elsa.Workflows.UIHints; using MassTransit; namespace Elsa.MassTransit.Activities; @@ -29,7 +30,10 @@ public class PublishMessage : CodeActivity /// /// The message to send. Must be a concrete implementation of the configured . /// - [Input(Description = "The message to send. Must be a concrete implementation of the configured message type.")] + [Input( + Description = "The message to send. Must be a concrete implementation of the configured message type.", + UIHint = InputUIHints.MultiLine + )] public Input Message { get; set; } = default!; /// diff --git a/src/modules/Elsa.MassTransit/Implementations/MassTransitActivityTypeProvider.cs b/src/modules/Elsa.MassTransit/Implementations/MassTransitActivityTypeProvider.cs index 75d9b32a6..e2dabe143 100644 --- a/src/modules/Elsa.MassTransit/Implementations/MassTransitActivityTypeProvider.cs +++ b/src/modules/Elsa.MassTransit/Implementations/MassTransitActivityTypeProvider.cs @@ -6,8 +6,8 @@ using Elsa.MassTransit.Options; using Elsa.Workflows; using Elsa.Workflows.Attributes; using Elsa.Workflows.Contracts; -using Elsa.Workflows.UIHints; using Elsa.Workflows.Helpers; +using Elsa.Workflows.Management; using Elsa.Workflows.Models; using Humanizer; using Microsoft.Extensions.Options; @@ -17,40 +17,31 @@ namespace Elsa.MassTransit.Implementations; /// /// Provides activities to the system from the configured MassTransit message types. /// -public class MassTransitActivityTypeProvider : IActivityProvider +public class MassTransitActivityTypeProvider(IActivityFactory activityFactory, IOptions options, IActivityDescriber activityDescriber) : IActivityProvider { - private readonly IActivityFactory _activityFactory; - private readonly MassTransitActivityOptions _options; - - /// - /// Constructor. - /// - public MassTransitActivityTypeProvider(IActivityFactory activityFactory, IOptions options) - { - _activityFactory = activityFactory; - _options = options.Value; - } - /// - public ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default) + public async ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default) { - var messageTypes = _options.MessageTypes; - var descriptors = CreateDescriptors(messageTypes).ToList(); - return new(descriptors); + var messageTypes = options.Value.MessageTypes; + var descriptors = await CreateDescriptorsAsync(messageTypes, cancellationToken); + return descriptors.ToList(); } - private IEnumerable CreateDescriptors(IEnumerable messageTypes) + private async Task> CreateDescriptorsAsync(IEnumerable messageTypes, CancellationToken cancellationToken = default) { + var descriptors = new List(); foreach (var messageType in messageTypes) { - yield return CreateMessageReceivedDescriptor(messageType); + descriptors.Add(await CreateMessageReceivedDescriptor(messageType, cancellationToken)); if(messageType.IsClass) - yield return CreatePublishMessageDescriptor(messageType); + descriptors.Add(await CreatePublishMessageDescriptor(messageType, cancellationToken)); } + + return descriptors; } - private ActivityDescriptor CreateMessageReceivedDescriptor(Type messageType) + private async Task CreateMessageReceivedDescriptor(Type messageType, CancellationToken cancellationToken = default) { var activityAttr = messageType.GetCustomAttribute(); var typeName = activityAttr?.Type ?? messageType.Name; @@ -61,6 +52,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider var category = categoryAttr?.Category ?? activityAttr?.Category ?? "MassTransit"; var descriptionAttr = messageType.GetCustomAttribute(); var description = descriptionAttr?.Description ?? activityAttr?.Description; + + var outputDescriptor = await activityDescriber.DescribeOutputProperty(x => x.Result!, cancellationToken); + var openOutputType = typeof(Output<>); + var outputType = openOutputType.MakeGenericType(messageType); + outputDescriptor.Type = outputType; return new() { @@ -73,17 +69,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider IsBrowsable = true, Outputs = { - new OutputDescriptor - { - Description = "The received message", - DisplayName = "Received Message", - Name = nameof(MessageReceived.Result), - Type = typeof(object) - } + outputDescriptor }, Constructor = context => { - var activity = _activityFactory.Create(context); + var activity = activityFactory.Create(context); activity.Type = fullTypeName; activity.MessageType = messageType; return activity; @@ -91,7 +81,7 @@ public class MassTransitActivityTypeProvider : IActivityProvider }; } - private ActivityDescriptor CreatePublishMessageDescriptor(Type messageType) + private async Task CreatePublishMessageDescriptor(Type messageType, CancellationToken cancellationToken = default) { var activityAttr = messageType.GetCustomAttribute(); var typeName = activityAttr?.Type ?? messageType.Name; @@ -104,6 +94,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider var descriptionAttr = messageType.GetCustomAttribute(); var description = descriptionAttr?.Description ?? activityAttr?.Description; + var messageInputDescriptor = await activityDescriber.DescribeInputPropertyAsync(x => x.Message, cancellationToken: cancellationToken); + var openInputType = typeof(Input<>); + var inputType = openInputType.MakeGenericType(messageType); + messageInputDescriptor.Type = inputType; + return new() { TypeName = fullTypeName, @@ -115,18 +110,11 @@ public class MassTransitActivityTypeProvider : IActivityProvider IsBrowsable = true, Inputs = { - new InputDescriptor - { - Description = "The message to publish.", - UIHint = InputUIHints.MultiLine, - DisplayName = "Message", - Type = typeof(Input), - Name = nameof(PublishMessage.Message) - } + messageInputDescriptor }, Constructor = context => { - var activity = _activityFactory.Create(context); + var activity = activityFactory.Create(context); activity.Type = fullTypeName; activity.MessageType = messageType; return activity; diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs index cb9dd853c..b93272793 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs @@ -292,7 +292,12 @@ public static class ExpressionExecutionContextExtensions select v; var variable = q.FirstOrDefault(); - variable?.Set(context, value); + + if(variable != null) + variable.Set(context, value); + + if (variable == null) + CreateVariable(context, variableName, value); } /// diff --git a/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs b/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs index 15bfb6322..942d8931b 100644 --- a/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs +++ b/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs @@ -36,6 +36,31 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor return state; } + /// + public WorkflowExecutionContext Apply(WorkflowExecutionContext workflowExecutionContext, WorkflowState state) + { + workflowExecutionContext.Id = state.Id; + workflowExecutionContext.CorrelationId = state.CorrelationId; + workflowExecutionContext.SubStatus = state.SubStatus; + workflowExecutionContext.Bookmarks = state.Bookmarks; + workflowExecutionContext.Output = state.Output; + workflowExecutionContext.ExecutionLogSequence = state.ExecutionLogSequence; + workflowExecutionContext.CreatedAt = state.CreatedAt; + ApplyInput(state, workflowExecutionContext); + ApplyProperties(state, workflowExecutionContext); + ApplyActivityExecutionContexts(state, workflowExecutionContext); + ApplyCompletionCallbacks(state, workflowExecutionContext); + ApplyScheduledActivities(state, workflowExecutionContext); + return workflowExecutionContext; + } + + private void ApplyInput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) + { + // Only add input from state if the input doesn't already exist on the workflow execution context. + foreach (var inputItem in state.Input) + if (!workflowExecutionContext.Input.ContainsKey(inputItem.Key)) workflowExecutionContext.Input.Add(inputItem.Key, inputItem.Value); + } + private IDictionary GetPersistableInput(WorkflowExecutionContext workflowExecutionContext) { // TODO: This is a temporary solution. We need to find a better way to handle this. @@ -52,24 +77,6 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor return filteredInput; } - /// - public WorkflowExecutionContext Apply(WorkflowExecutionContext workflowExecutionContext, WorkflowState state) - { - // Do not map input. We don't want to overwrite the input that was passed to the workflow. - workflowExecutionContext.Id = state.Id; - workflowExecutionContext.CorrelationId = state.CorrelationId; - workflowExecutionContext.SubStatus = state.SubStatus; - workflowExecutionContext.Bookmarks = state.Bookmarks; - workflowExecutionContext.Output = state.Output; - workflowExecutionContext.ExecutionLogSequence = state.ExecutionLogSequence; - workflowExecutionContext.CreatedAt = state.CreatedAt; - ApplyProperties(state, workflowExecutionContext); - ApplyActivityExecutionContexts(state, workflowExecutionContext); - ApplyCompletionCallbacks(state, workflowExecutionContext); - ApplyScheduledActivities(state, workflowExecutionContext); - return workflowExecutionContext; - } - private void ExtractProperties(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) { state.Properties = workflowExecutionContext.Properties; diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.MassTransitActivities/Elsa.Samples.AspNet.MassTransitActivities.csproj b/src/samples/aspnet/Elsa.Samples.AspNet.MassTransitActivities/Elsa.Samples.AspNet.MassTransitActivities.csproj index 6241d130a..00da2f741 100644 --- a/src/samples/aspnet/Elsa.Samples.AspNet.MassTransitActivities/Elsa.Samples.AspNet.MassTransitActivities.csproj +++ b/src/samples/aspnet/Elsa.Samples.AspNet.MassTransitActivities/Elsa.Samples.AspNet.MassTransitActivities.csproj @@ -9,6 +9,7 @@ + diff --git a/src/samples/aspnet/Elsa.Samples.AspNet.MassTransitActivities/Program.cs b/src/samples/aspnet/Elsa.Samples.AspNet.MassTransitActivities/Program.cs index 5ba3ecd92..405f28a5a 100644 --- a/src/samples/aspnet/Elsa.Samples.AspNet.MassTransitActivities/Program.cs +++ b/src/samples/aspnet/Elsa.Samples.AspNet.MassTransitActivities/Program.cs @@ -28,16 +28,20 @@ builder.Services.AddElsa(elsa => // Add services for HTTP activities and workflow middleware. elsa.UseHttp(); - - // Use JavaScript. - elsa.UseJavaScript(javaScript => + + // Use C#. + elsa.UseCSharp(options => { - javaScript.AllowClrAccess = true; - javaScript.ConfigureEngine(options => - { - options.RegisterType(); - options.RegisterType(); - }); + options.Assemblies.Add(typeof(OrderCreated).Assembly); + options.Namespaces.Add(typeof(OrderCreated).Namespace!); + }); + + // Use JavaScript. + elsa.UseJavaScript(options => + { + options.AllowClrAccess = true; + options.RegisterType(); + options.RegisterType(); }); // Use C#. @@ -56,7 +60,7 @@ builder.Services.AddElsa(elsa => identity.UseAdminUserProvider(); identity.TokenOptions = options => { - options.SigningKey = "secret-token-signing-key"; + options.SigningKey = "super-secret-and-securely-stored-token-signing-key"; options.AccessTokenLifetime = TimeSpan.FromDays(1); }; }); @@ -67,7 +71,7 @@ builder.Services.AddElsa(elsa => // Configure MassTransit. elsa.UseMassTransit(massTransit => { - massTransit.UseRabbitMq(rabbitMqConnectionString); + //massTransit.UseRabbitMq(rabbitMqConnectionString); massTransit.AddMessageType(); massTransit.AddMessageType(); });