diff --git a/Directory.Build.props b/Directory.Build.props index 60657b4e..7af11a0f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -38,7 +38,7 @@ - 3.6.0-preview.2950 - 3.6.0-preview.1018 + 3.6.0-preview.3000 + 3.6.0-preview.1029 \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 7b0fb022..e83e38ec 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,6 @@ false - @@ -17,17 +16,17 @@ - - - + + + - - + + @@ -51,6 +50,7 @@ + @@ -97,9 +97,9 @@ - - - + + + diff --git a/src/alterations/Elsa.Alterations/Activities/AlterationPlanCompleted.cs b/src/alterations/Elsa.Alterations/Activities/AlterationPlanCompleted.cs index 5f0cba01..45cd022e 100644 --- a/src/alterations/Elsa.Alterations/Activities/AlterationPlanCompleted.cs +++ b/src/alterations/Elsa.Alterations/Activities/AlterationPlanCompleted.cs @@ -17,20 +17,20 @@ namespace Elsa.Alterations.Activities; public class AlterationPlanCompleted : Trigger { /// - public AlterationPlanCompleted(Variable planId, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public AlterationPlanCompleted(Variable planId, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { PlanId = new Input(planId); } - + /// - public AlterationPlanCompleted([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public AlterationPlanCompleted([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } - + /// /// The ID of the alteration plan. /// - public Input PlanId { get; set; } = default!; + public Input PlanId { get; set; } = null!; /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) diff --git a/src/alterations/Elsa.Alterations/Activities/CompleteAlterationPlan.cs b/src/alterations/Elsa.Alterations/Activities/CompleteAlterationPlan.cs index 17ec7010..0c1d504b 100644 --- a/src/alterations/Elsa.Alterations/Activities/CompleteAlterationPlan.cs +++ b/src/alterations/Elsa.Alterations/Activities/CompleteAlterationPlan.cs @@ -17,20 +17,20 @@ namespace Elsa.Alterations.Activities; public class CompleteAlterationPlan : CodeActivity { /// - public CompleteAlterationPlan(Variable planId, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public CompleteAlterationPlan(Variable planId, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { PlanId = new Input(planId); } /// - public CompleteAlterationPlan([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public CompleteAlterationPlan([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } /// /// The ID of the alteration plan. /// - public Input PlanId { get; set; } = default!; + public Input PlanId { get; set; } = null!; /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) diff --git a/src/alterations/Elsa.Alterations/Activities/DispatchAlterationJobs.cs b/src/alterations/Elsa.Alterations/Activities/DispatchAlterationJobs.cs index a22a4565..965a25c6 100644 --- a/src/alterations/Elsa.Alterations/Activities/DispatchAlterationJobs.cs +++ b/src/alterations/Elsa.Alterations/Activities/DispatchAlterationJobs.cs @@ -19,20 +19,20 @@ namespace Elsa.Alterations.Activities; public class DispatchAlterationJobs : CodeActivity { /// - public DispatchAlterationJobs(Variable planId, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public DispatchAlterationJobs(Variable planId, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { PlanId = new Input(planId); } /// - public DispatchAlterationJobs([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public DispatchAlterationJobs([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } /// /// The ID of the alteration plan. /// - public Input PlanId { get; set; } = default!; + public Input PlanId { get; set; } = null!; /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) diff --git a/src/alterations/Elsa.Alterations/Activities/GenerateAlterationJobs.cs b/src/alterations/Elsa.Alterations/Activities/GenerateAlterationJobs.cs index be949b0e..8bae6664 100644 --- a/src/alterations/Elsa.Alterations/Activities/GenerateAlterationJobs.cs +++ b/src/alterations/Elsa.Alterations/Activities/GenerateAlterationJobs.cs @@ -23,12 +23,12 @@ namespace Elsa.Alterations.Activities; public class GenerateAlterationJobs : CodeActivity { /// - public GenerateAlterationJobs([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public GenerateAlterationJobs([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } /// - public GenerateAlterationJobs(Variable planId, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public GenerateAlterationJobs(Variable planId, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { PlanId = new Input(planId); } @@ -36,7 +36,7 @@ public GenerateAlterationJobs(Variable planId, [CallerFilePath] string? /// /// The ID of the submitted alteration plan. /// - public Input PlanId { get; set; } = default!; + public Input PlanId { get; set; } = null!; /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) diff --git a/src/alterations/Elsa.Alterations/Activities/SubmitAlterationPlan.cs b/src/alterations/Elsa.Alterations/Activities/SubmitAlterationPlan.cs index 82f1562c..d1624a4c 100644 --- a/src/alterations/Elsa.Alterations/Activities/SubmitAlterationPlan.cs +++ b/src/alterations/Elsa.Alterations/Activities/SubmitAlterationPlan.cs @@ -25,7 +25,7 @@ public class SubmitAlterationPlan : CodeActivity /// /// The parameters for the alteration plan to be submitted. /// - public Input Params { get; set; } = default!; + public Input Params { get; set; } = null!; /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) diff --git a/src/dropins/Elsa.DropIns/HostedServices/DropInDirectoryMonitorHostedService.cs b/src/dropins/Elsa.DropIns/HostedServices/DropInDirectoryMonitorHostedService.cs index 7fed31ed..dda2fec5 100644 --- a/src/dropins/Elsa.DropIns/HostedServices/DropInDirectoryMonitorHostedService.cs +++ b/src/dropins/Elsa.DropIns/HostedServices/DropInDirectoryMonitorHostedService.cs @@ -120,4 +120,24 @@ private Task UnloadDropInAssemblyAsync(string fullPath) } return Task.CompletedTask; } + + /// + public override async Task StopAsync(CancellationToken cancellationToken) + { + _watcher.EnableRaisingEvents = false; + _watcher.Changed -= OnChanged; + _watcher.Deleted -= OnDeleted; + + await base.StopAsync(cancellationToken); + } + + /// + public override void Dispose() + { + _watcher.Changed -= OnChanged; + _watcher.Deleted -= OnDeleted; + _watcher.Dispose(); + + base.Dispose(); + } } \ No newline at end of file diff --git a/src/email/Elsa.Email/Activities/SendEmail.cs b/src/email/Elsa.Email/Activities/SendEmail.cs index db68773c..f761a1e8 100644 --- a/src/email/Elsa.Email/Activities/SendEmail.cs +++ b/src/email/Elsa.Email/Activities/SendEmail.cs @@ -24,7 +24,7 @@ namespace Elsa.Email.Activities; public class SendEmail : Activity { /// - public SendEmail([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public SendEmail([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } @@ -32,13 +32,13 @@ public SendEmail([CallerFilePath] string? source = default, [CallerLineNumber] i /// The sender's email address. /// [Input(Description = "The sender's email address.")] - public Input From { get; set; } = default!; + public Input From { get; set; } = null!; /// /// The recipients email addresses. /// [Input(Description = "The recipients email addresses.", UIHint = InputUIHints.MultiText)] - public Input> To { get; set; } = default!; + public Input> To { get; set; } = null!; /// /// The CC recipient email addresses. @@ -47,7 +47,7 @@ public SendEmail([CallerFilePath] string? source = default, [CallerLineNumber] i Description = "The CC recipient email addresses.", UIHint = InputUIHints.MultiText, Category = "More")] - public Input> Cc { get; set; } = default!; + public Input> Cc { get; set; } = null!; /// /// The BCC recipients email addresses. @@ -56,13 +56,13 @@ public SendEmail([CallerFilePath] string? source = default, [CallerLineNumber] i Description = "The BCC recipients email addresses.", UIHint = InputUIHints.MultiText, Category = "More")] - public Input> Bcc { get; set; } = default!; + public Input> Bcc { get; set; } = null!; /// /// The subject of the email message. /// [Input(Description = "The subject of the email message.")] - public Input Subject { get; set; } = default!; + public Input Subject { get; set; } = null!; /// /// The attachments to send with the email message. @@ -71,7 +71,7 @@ public SendEmail([CallerFilePath] string? source = default, [CallerLineNumber] i Description = "The attachments to send with the email message. Can be (an array of) a fully-qualified file path, URL, stream, byte array or instances of EmailAttachment.", UIHint = InputUIHints.MultiLine )] - public Input Attachments { get; set; } = default!; + public Input Attachments { get; set; } = null!; /// /// The body of the email message. @@ -80,7 +80,7 @@ public SendEmail([CallerFilePath] string? source = default, [CallerLineNumber] i Description = "The body of the email message.", UIHint = InputUIHints.MultiLine )] - public Input Body { get; set; } = default!; + public Input Body { get; set; } = null!; /// /// The activity to execute when an error occurs while trying to send the email. diff --git a/src/persistence/Elsa.Persistence.EFCore.Common/ElsaDbContextBase.cs b/src/persistence/Elsa.Persistence.EFCore.Common/ElsaDbContextBase.cs index 4c6fcd39..fd3b29d3 100644 --- a/src/persistence/Elsa.Persistence.EFCore.Common/ElsaDbContextBase.cs +++ b/src/persistence/Elsa.Persistence.EFCore.Common/ElsaDbContextBase.cs @@ -19,7 +19,7 @@ public abstract class ElsaDbContextBase : DbContext, IElsaDbContextSchema }; protected IServiceProvider ServiceProvider { get; } - private readonly ElsaDbContextOptions? _elsaDbContextOptions; + private readonly ElsaDbContextOptions? elsaDbContextOptions; public string? TenantId { get; set; } /// @@ -41,10 +41,10 @@ public abstract class ElsaDbContextBase : DbContext, IElsaDbContextSchema protected ElsaDbContextBase(DbContextOptions options, IServiceProvider serviceProvider) : base(options) { ServiceProvider = serviceProvider; - _elsaDbContextOptions = options.FindExtension()?.Options; - + elsaDbContextOptions = options.FindExtension()?.Options; + // ReSharper disable once VirtualMemberCallInConstructor - Schema = !string.IsNullOrWhiteSpace(_elsaDbContextOptions?.SchemaName) ? _elsaDbContextOptions.SchemaName : ElsaSchema; + Schema = !string.IsNullOrWhiteSpace(elsaDbContextOptions?.SchemaName) ? elsaDbContextOptions.SchemaName : ElsaSchema; var tenantAccessor = serviceProvider.GetService(); var tenantId = tenantAccessor?.Tenant?.Id; @@ -63,11 +63,11 @@ public override async Task SaveChangesAsync(CancellationToken cancellationT /// protected override void OnModelCreating(ModelBuilder modelBuilder) { - if (!string.IsNullOrWhiteSpace(Schema)) + if (!string.IsNullOrWhiteSpace(Schema)) modelBuilder.HasDefaultSchema(Schema); - var additionalConfigurations = _elsaDbContextOptions?.GetModelConfigurations(this); - + var additionalConfigurations = elsaDbContextOptions?.GetModelConfigurations(this); + additionalConfigurations?.Invoke(modelBuilder); using var scope = ServiceProvider.CreateScope(); @@ -75,7 +75,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) foreach (var entityType in modelBuilder.Model.GetEntityTypes().ToList()) { - foreach (var handler in entityTypeHandlers) + foreach (var handler in entityTypeHandlers) handler.Handle(this, modelBuilder, entityType); } } diff --git a/src/persistence/Elsa.Persistence.EFCore.Common/EntityHandlers/SetTenantIdFilter.cs b/src/persistence/Elsa.Persistence.EFCore.Common/EntityHandlers/SetTenantIdFilter.cs index 552a9a63..7ae5eafb 100644 --- a/src/persistence/Elsa.Persistence.EFCore.Common/EntityHandlers/SetTenantIdFilter.cs +++ b/src/persistence/Elsa.Persistence.EFCore.Common/EntityHandlers/SetTenantIdFilter.cs @@ -1,9 +1,7 @@ using System.Linq.Expressions; using Elsa.Common.Entities; -using Elsa.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Query; namespace Elsa.Persistence.EFCore.EntityHandlers; @@ -15,15 +13,32 @@ public class SetTenantIdFilter : IEntityModelCreatingHandler /// public void Handle(ElsaDbContextBase dbContext, ModelBuilder modelBuilder, IMutableEntityType entityType) { - if (!entityType.ClrType.IsAssignableTo(typeof(Entity))) + if (!typeof(Entity).IsAssignableFrom(entityType.ClrType)) return; - var tenantId = dbContext.TenantId.NullIfEmpty(); - var parameter = Expression.Parameter(entityType.ClrType); - Expression> filterExpr = entity => entity.TenantId == tenantId; - var body = ReplacingExpressionVisitor.Replace(filterExpr.Parameters[0], parameter, filterExpr.Body); - var lambdaExpression = Expression.Lambda(body, parameter); + modelBuilder + .Entity(entityType.ClrType) + .HasQueryFilter(CreateTenantFilterExpression(dbContext, entityType.ClrType)); + } + + private LambdaExpression CreateTenantFilterExpression(ElsaDbContextBase dbContext, Type clrType) + { + var parameter = Expression.Parameter(clrType, "e"); + + // e => EF.Property(e, "TenantId") == this.TenantId + var tenantIdProperty = Expression.Call( + typeof(EF), + nameof(EF.Property), + [typeof(string)], + parameter, + Expression.Constant("TenantId")); + + var tenantIdOnContext = Expression.Property( + Expression.Constant(dbContext), + nameof(ElsaDbContextBase.TenantId)); + + var body = Expression.Equal(tenantIdProperty, tenantIdOnContext); - entityType.SetQueryFilter(lambdaExpression); + return Expression.Lambda(body, parameter); } } \ No newline at end of file diff --git a/src/runtimes/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowClient.cs b/src/runtimes/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowClient.cs index d483819a..251ac52d 100644 --- a/src/runtimes/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowClient.cs +++ b/src/runtimes/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowClient.cs @@ -50,7 +50,8 @@ public async Task CreateAndRunInstanceAsync(CreateA Variables = request.Variables, Properties = request.Properties, TriggerActivityId = request.TriggerActivityId, - ActivityHandle = request.ActivityHandle + ActivityHandle = request.ActivityHandle, + IncludeWorkflowOutput = request.IncludeWorkflowOutput }, cancellationToken)); } diff --git a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Actors/WorkflowInstance.cs b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Actors/WorkflowInstance.cs index 938d376a..6a639459 100644 --- a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Actors/WorkflowInstance.cs +++ b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Actors/WorkflowInstance.cs @@ -73,6 +73,7 @@ public override Task Create(CreateWorkflowInstanceRequest request, Action respond, Action onError) { var mappedRequest = mappers.RunWorkflowParamsMapper.Map(request); + var includeOutput = request.IncludeWorkflowOutput; Context.ReenterAfter(RunAsync(mappedRequest), async completedTask => { if (completedTask.IsFaulted) @@ -80,7 +81,7 @@ public override Task Run(RunWorkflowInstanceRequest request, Action CreateAndRunAsync(CreateAndRunWo var result = await RunAsync(runWorkflowOptions); - return mappers.RunWorkflowInstanceResponseMapper.Map(result); + return mappers.RunWorkflowInstanceResponseMapper.Map(result, request.IncludeWorkflowOutput); } public override Task Stop() diff --git a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Extensions/ProtoOutputExtensions.cs b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Extensions/ProtoOutputExtensions.cs new file mode 100644 index 00000000..43470e78 --- /dev/null +++ b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Extensions/ProtoOutputExtensions.cs @@ -0,0 +1,15 @@ +using Elsa.Workflows.Runtime.ProtoActor.ProtoBuf; + +namespace Elsa.Workflows.Runtime.ProtoActor.Extensions; + +internal static class ProtoOutputExtensions +{ + public static IDictionary DeserializeOutput(this Output output) => output.Data.Deserialize(); + + public static Output SerializeOutput(this IDictionary output) + { + var result = new Output(); + output.Serialize(result.Data); + return result; + } +} \ No newline at end of file diff --git a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/CreateAndRunWorkflowInstanceRequestMapper.cs b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/CreateAndRunWorkflowInstanceRequestMapper.cs index 0a09719b..fe763f5c 100644 --- a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/CreateAndRunWorkflowInstanceRequestMapper.cs +++ b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/CreateAndRunWorkflowInstanceRequestMapper.cs @@ -30,6 +30,7 @@ public ProtoCreateAndRunWorkflowInstanceRequest Map(string workflowInstanceId, C Properties = source.Properties?.SerializeProperties() ?? new ProtoProperties(), TriggerActivityId = source.TriggerActivityId.EmptyIfNull(), ActivityHandle = activityHandleMapper.Map(source?.ActivityHandle) ?? new(), + IncludeWorkflowOutput = source.IncludeWorkflowOutput, }; } @@ -49,6 +50,7 @@ public CreateAndRunWorkflowInstanceRequest Map(ProtoCreateAndRunWorkflowInstance Input = source.Input?.DeserializeInput(), Properties = source.Properties?.DeserializeProperties(), ActivityHandle = activityHandleMapper.Map(source.ActivityHandle), + IncludeWorkflowOutput = source.IncludeWorkflowOutput, }; } } \ No newline at end of file diff --git a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/RunWorkflowInstanceRequestMapper.cs b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/RunWorkflowInstanceRequestMapper.cs index 89296648..5cda525d 100644 --- a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/RunWorkflowInstanceRequestMapper.cs +++ b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/RunWorkflowInstanceRequestMapper.cs @@ -22,6 +22,7 @@ public ProtoRunWorkflowInstanceRequest Map(RunWorkflowInstanceRequest? source) TriggerActivityId = source?.TriggerActivityId.EmptyIfNull(), Input = source?.Input?.SerializeInput(), Properties = source?.Properties?.SerializeProperties(), + IncludeWorkflowOutput = source?.IncludeWorkflowOutput ?? false, }; } @@ -37,6 +38,7 @@ public RunWorkflowInstanceRequest Map(ProtoRunWorkflowInstanceRequest source) TriggerActivityId = source.TriggerActivityId, Input = source.Input?.DeserializeInput(), Properties = source.Properties?.DeserializeProperties(), + IncludeWorkflowOutput = source.IncludeWorkflowOutput, }; } } \ No newline at end of file diff --git a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/RunWorkflowInstanceResponseMapper.cs b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/RunWorkflowInstanceResponseMapper.cs index 8ae2c535..a62df191 100644 --- a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/RunWorkflowInstanceResponseMapper.cs +++ b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Mappers/RunWorkflowInstanceResponseMapper.cs @@ -2,6 +2,7 @@ using Elsa.Workflows.Models; using Elsa.Workflows.Runtime.Messages; using ProtoRunWorkflowInstanceResponse = Elsa.Workflows.Runtime.ProtoActor.ProtoBuf.RunWorkflowInstanceResponse; +using Elsa.Workflows.Runtime.ProtoActor.Extensions; namespace Elsa.Workflows.Runtime.ProtoActor.Mappers; @@ -16,11 +17,11 @@ public class RunWorkflowInstanceResponseMapper( /// /// Maps to . /// - public ProtoRunWorkflowInstanceResponse Map(RunWorkflowResult source) + public ProtoRunWorkflowInstanceResponse Map(RunWorkflowResult source, bool includeOutput) { - if(source.WorkflowState == null!) + if (source.WorkflowState == null!) return new(); - + var response = new ProtoRunWorkflowInstanceResponse { Status = workflowStatusMapper.Map(source.WorkflowState.Status), @@ -28,6 +29,9 @@ public ProtoRunWorkflowInstanceResponse Map(RunWorkflowResult source) }; response.Incidents.AddRange(activityIncidentMapper.Map(source.WorkflowState.Incidents)); + + if (includeOutput) + response.Output = source.WorkflowState.Output.SerializeOutput(); return response; } @@ -41,10 +45,10 @@ public RunWorkflowInstanceResponse Map(string workflowInstanceId, ProtoRunWorkfl WorkflowInstanceId = workflowInstanceId, Status = workflowStatusMapper.Map(source.Status), SubStatus = workflowSubStatusMapper.Map(source.SubStatus), + Output = source.Output?.DeserializeOutput() }; response.Incidents.AddRange(activityIncidentMapper.Map(source.Incidents)); return response; - } } \ No newline at end of file diff --git a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Proto/Shared.proto b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Proto/Shared.proto index b0119b81..a6000345 100644 --- a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Proto/Shared.proto +++ b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Proto/Shared.proto @@ -18,6 +18,10 @@ message Properties { map Data = 1; } +message Output { + map Data = 1; +} + message ExceptionState { string Type = 1; string Message = 2; diff --git a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.Messages.proto b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.Messages.proto index 909c8c73..5f653948 100644 --- a/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.Messages.proto +++ b/src/runtimes/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.Messages.proto @@ -61,12 +61,14 @@ message RunWorkflowInstanceRequest{ optional Input input = 3; optional Properties properties = 4; optional string TriggerActivityId = 5; + bool IncludeWorkflowOutput = 6; } message RunWorkflowInstanceResponse { WorkflowStatus Status = 1; WorkflowSubStatus SubStatus = 2; repeated ActivityIncident Incidents = 3; + optional Output output = 4; } message CreateAndRunWorkflowInstanceRequest{ @@ -79,6 +81,7 @@ message CreateAndRunWorkflowInstanceRequest{ optional Properties properties = 7; optional ActivityHandle ActivityHandle = 8; optional string TriggerActivityId = 9; + bool IncludeWorkflowOutput = 10; } message ExportWorkflowStateResponse { diff --git a/src/scheduling/Elsa.Scheduling/Activities/StartAt.cs b/src/scheduling/Elsa.Scheduling/Activities/StartAt.cs index 6060ac4e..26075294 100644 --- a/src/scheduling/Elsa.Scheduling/Activities/StartAt.cs +++ b/src/scheduling/Elsa.Scheduling/Activities/StartAt.cs @@ -20,15 +20,15 @@ public class StartAt : Trigger private const string InputKey = "ExecuteAt"; /// - public StartAt([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public StartAt([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } /// - public StartAt(Input dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => DateTime = dateTime; + public StartAt(Input dateTime, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line) => DateTime = dateTime; /// - public StartAt(Func dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) + public StartAt(Func dateTime, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(new Input(dateTime), source, line) { } @@ -36,35 +36,35 @@ public StartAt(Func dateTime, [Calle /// public StartAt( Func> dateTime, - [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(new Input(dateTime), source, line) + [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(new Input(dateTime), source, line) { } /// - public StartAt(Func> dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) + public StartAt(Func> dateTime, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(new Input(dateTime), source, line) { } /// - public StartAt(Func dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) + public StartAt(Func dateTime, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(new Input(dateTime), source, line) { } /// - public StartAt(DateTimeOffset dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => + public StartAt(DateTimeOffset dateTime, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line) => DateTime = new Input(dateTime); /// - public StartAt(Variable dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => + public StartAt(Variable dateTime, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line) => DateTime = new Input(dateTime); /// /// The timestamp at which the workflow should be triggered. /// [Input] - public Input DateTime { get; set; } = default!; + public Input DateTime { get; set; } = null!; /// protected override object GetTriggerPayload(TriggerIndexingContext context) @@ -103,5 +103,5 @@ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context /// /// Creates a new activity set to trigger at the specified timestamp. /// - public static StartAt From(DateTimeOffset value, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) => new(value, source, line); + public static StartAt From(DateTimeOffset value, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) => new(value, source, line); } \ No newline at end of file diff --git a/src/scheduling/Elsa.Scheduling/Bookmarks/CronTriggerPayload.cs b/src/scheduling/Elsa.Scheduling/Bookmarks/CronTriggerPayload.cs index 52d74265..fc3c3c62 100644 --- a/src/scheduling/Elsa.Scheduling/Bookmarks/CronTriggerPayload.cs +++ b/src/scheduling/Elsa.Scheduling/Bookmarks/CronTriggerPayload.cs @@ -1,3 +1,3 @@ namespace Elsa.Scheduling.Bookmarks; -internal record CronTriggerPayload(string CronExpression); \ No newline at end of file +public record CronTriggerPayload(string CronExpression); \ No newline at end of file diff --git a/src/scheduling/Elsa.Scheduling/Elsa.Scheduling.csproj b/src/scheduling/Elsa.Scheduling/Elsa.Scheduling.csproj index 4e3fbdf2..fb824142 100644 --- a/src/scheduling/Elsa.Scheduling/Elsa.Scheduling.csproj +++ b/src/scheduling/Elsa.Scheduling/Elsa.Scheduling.csproj @@ -1,4 +1,4 @@ - + diff --git a/src/scheduling/Elsa.Scheduling/Features/SchedulingFeature.cs b/src/scheduling/Elsa.Scheduling/Features/SchedulingFeature.cs index 7ef3dfde..41eb4b5e 100644 --- a/src/scheduling/Elsa.Scheduling/Features/SchedulingFeature.cs +++ b/src/scheduling/Elsa.Scheduling/Features/SchedulingFeature.cs @@ -4,9 +4,11 @@ using Elsa.Features.Abstractions; using Elsa.Features.Attributes; using Elsa.Features.Services; +using Elsa.Scheduling.Bookmarks; using Elsa.Scheduling.Handlers; using Elsa.Scheduling.HostedServices; using Elsa.Scheduling.Services; +using Elsa.Scheduling.TriggerPayloadValidators; using Elsa.Workflows.Management.Features; using Microsoft.Extensions.DependencyInjection; @@ -49,7 +51,10 @@ public override void Apply() .AddSingleton(CronParser) .AddScoped(WorkflowScheduler) .AddBackgroundTask() - .AddHandlersFrom(); + .AddHandlersFrom() + + //Trigger payload validators. + .AddTriggerPaylodValidator(); Module.Configure(management => management.AddActivitiesFrom()); } diff --git a/src/scheduling/Elsa.Scheduling/TriggerPayloadValidators/CronTriggerPayloadValidator.cs b/src/scheduling/Elsa.Scheduling/TriggerPayloadValidators/CronTriggerPayloadValidator.cs new file mode 100644 index 00000000..7b3478af --- /dev/null +++ b/src/scheduling/Elsa.Scheduling/TriggerPayloadValidators/CronTriggerPayloadValidator.cs @@ -0,0 +1,30 @@ +using Cronos; +using Elsa.Scheduling.Bookmarks; +using Elsa.Workflows.Activities; +using Elsa.Workflows.Management.Models; +using Elsa.Workflows.Runtime.Contracts; +using Elsa.Workflows.Runtime.Entities; + +namespace Elsa.Scheduling.TriggerPayloadValidators; + +public class CronTriggerPayloadValidator(ICronParser cronParser) : ITriggerPayloadValidator +{ + public Task ValidateAsync( + CronTriggerPayload payload, + Workflow workflow, + StoredTrigger trigger, + ICollection validationErrors, + CancellationToken cancellationToken) + { + try + { + cronParser.GetNextOccurrence(payload.CronExpression); + } + catch (CronFormatException ex) + { + validationErrors.Add(new("Error when parsing cron expression: " + ex.Message, + trigger.ActivityId)); + } + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/security/Elsa.SasTokens/Extensions/ServiceCollectionExtensions.cs b/src/security/Elsa.SasTokens/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index 8b4356d4..00000000 --- a/src/security/Elsa.SasTokens/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Elsa.SasTokens.Contracts; -using Microsoft.AspNetCore.DataProtection; -using Microsoft.Extensions.DependencyInjection; - -namespace Elsa.SasTokens.Extensions; - -/// -/// Contains extension methods for the interface. -/// -public static class ServiceCollectionExtensions -{ - /// - /// Adds the SAS tokens module to the service collection. - /// - public static IServiceCollection AddSasTokens(this IServiceCollection services, Func dataProtectionProvider) - { - services.AddScoped(sp => - { - var protectionProvider = dataProtectionProvider(sp); - return new DataProtectorTokenService(protectionProvider); - }); - return services; - } -} \ No newline at end of file diff --git a/src/security/Elsa.SasTokens/Features/SasTokensFeature.cs b/src/security/Elsa.SasTokens/Features/SasTokensFeature.cs index b4b3ec0b..2678766e 100644 --- a/src/security/Elsa.SasTokens/Features/SasTokensFeature.cs +++ b/src/security/Elsa.SasTokens/Features/SasTokensFeature.cs @@ -1,28 +1,35 @@ using Elsa.Features.Abstractions; using Elsa.Features.Services; -using Elsa.SasTokens.Extensions; +using Elsa.SasTokens.Contracts; using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.DependencyInjection; namespace Elsa.SasTokens.Features; /// /// Adds the SAS tokens feature to the workflow runtime. /// -public class SasTokensFeature : FeatureBase +/// +public class SasTokensFeature(IModule module) : FeatureBase(module) { - /// - public SasTokensFeature(IModule module) : base(module) - { - } + /// + /// Configures the used for setting up data protection. + /// Defaults to setting the application name to "Elsa Workflows". + /// + public Action ConfigureDataProtectionBuilder { get; set; } = b => { b.SetApplicationName("Elsa Workflows"); }; /// - /// Gets or sets the data protection provider used to create the used to encrypt and decrypt the SAS tokens. + /// Factory method to create an instance of . + /// Defaults to creating a using dependency injection. /// - public Func DataProtectionProvider { get; set; } = _ => Microsoft.AspNetCore.DataProtection.DataProtectionProvider.Create("Elsa Workflows"); + public Func TokenService { get; set; } = sp => ActivatorUtilities.CreateInstance(sp); /// public override void Apply() { - Services.AddSasTokens(DataProtectionProvider); + var builder = Services.AddDataProtection(); + ConfigureDataProtectionBuilder(builder); + + Services.AddScoped(TokenService); } } \ No newline at end of file diff --git a/src/servicebus/Elsa.ServiceBus.Kafka/Activities/MessageReceived.cs b/src/servicebus/Elsa.ServiceBus.Kafka/Activities/MessageReceived.cs index 773ef58e..1fd93a27 100644 --- a/src/servicebus/Elsa.ServiceBus.Kafka/Activities/MessageReceived.cs +++ b/src/servicebus/Elsa.ServiceBus.Kafka/Activities/MessageReceived.cs @@ -10,18 +10,18 @@ namespace Elsa.ServiceBus.Kafka.Activities; -[Activity("Elsa.ServiceBus.Kafka", "Kafka", "Executes when a message is received from a given set of topics")] +[Activity("Elsa.Kafka", "Kafka", "Executes when a message is received from a given set of topics")] public class MessageReceived : Trigger { internal const string InputKey = "TransportMessage"; /// - public MessageReceived([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public MessageReceived([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } /// - public MessageReceived(Input consumerDefinitionId, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public MessageReceived(Input consumerDefinitionId, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { ConsumerDefinitionId = consumerDefinitionId; } @@ -35,8 +35,8 @@ public MessageReceived(Input consumerDefinitionId, [CallerFilePath] stri UIHandler = typeof(ConsumerDefinitionsDropdownOptionsProvider), UIHint = InputUIHints.DropDown )] - public Input ConsumerDefinitionId { get; set; } = default!; - + public Input ConsumerDefinitionId { get; set; } = null!; + /// /// The topics to read from. /// @@ -45,7 +45,7 @@ public MessageReceived(Input consumerDefinitionId, [CallerFilePath] stri Description = "The topics to read from.", UIHint = InputUIHints.MultiText )] - public Input> Topics { get; set; } = default!; + public Input> Topics { get; set; } = null!; [Input( Description = "Optional. A predicate to filter messages.", @@ -53,15 +53,15 @@ public MessageReceived(Input consumerDefinitionId, [CallerFilePath] stri DefaultSyntax = "JavaScript", UIHint = InputUIHints.ExpressionEditor )] - public Input Predicate { get; set; } = default!; - + public Input Predicate { get; set; } = null!; + [Input(DisplayName = "Local", Description = "Whether the event is local to the workflow. When checked, only events delivered to this workflow instance will resume this activity.")] - public Input IsLocal { get; set; } = default!; + public Input IsLocal { get; set; } = null!; /// /// The received transport message. /// - public Output TransportMessage = default!; + public Output TransportMessage = null!; /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) @@ -106,7 +106,7 @@ private object GetStimulus(ExpressionExecutionContext context) var inputDescriptor = activityDescriptor.GetWrappedInputPropertyDescriptor(activity, nameof(Predicate)); var predicateInput = (Input?)inputDescriptor!.ValueGetter(activity); var predicateExpression = predicateInput?.Expression; - + return new MessageReceivedStimulus { ConsumerDefinitionId = consumerDefinitionId, diff --git a/src/servicebus/Elsa.ServiceBus.MassTransit/Activities/PublishMessage.cs b/src/servicebus/Elsa.ServiceBus.MassTransit/Activities/PublishMessage.cs index b0acd349..16608aaa 100644 --- a/src/servicebus/Elsa.ServiceBus.MassTransit/Activities/PublishMessage.cs +++ b/src/servicebus/Elsa.ServiceBus.MassTransit/Activities/PublishMessage.cs @@ -18,14 +18,14 @@ namespace Elsa.ServiceBus.MassTransit.Activities; public class PublishMessage : CodeActivity { /// - public PublishMessage([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public PublishMessage([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } /// /// The message type to publish. /// - public Type MessageType { get; set; } = default!; + public Type MessageType { get; set; } = null!; /// /// The message to send. Must be a concrete implementation of the configured . @@ -34,7 +34,7 @@ public PublishMessage([CallerFilePath] string? source = default, [CallerLineNumb Description = "The message to send. Must be a concrete implementation of the configured message type.", UIHint = InputUIHints.MultiLine )] - public Input Message { get; set; } = default!; + public Input Message { get; set; } = null!; /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) diff --git a/src/servicebus/Elsa.ServiceBus.MassTransit/Consumers/DispatchStimulusRequestConsumer.cs b/src/servicebus/Elsa.ServiceBus.MassTransit/Consumers/DispatchStimulusRequestConsumer.cs index 36a0dbb2..d68c28e3 100644 --- a/src/servicebus/Elsa.ServiceBus.MassTransit/Consumers/DispatchStimulusRequestConsumer.cs +++ b/src/servicebus/Elsa.ServiceBus.MassTransit/Consumers/DispatchStimulusRequestConsumer.cs @@ -16,16 +16,19 @@ public async Task Consume(ConsumeContext context) var message = context.Message; var json = message.SerializedRequest; var request = payloadSerializer.Deserialize(json); - - if(request.ActivityTypeName != null) + + if (request.ActivityTypeName != null) { await stimulusSender.SendAsync(request.ActivityTypeName, request.Stimulus!, request.Metadata, cancellationToken); return; } - - if(request.StimulusHash != null) + + if (request.StimulusHash != null) + { await stimulusSender.SendAsync(request.StimulusHash!, request.Metadata, cancellationToken); - + return; + } + throw new InvalidOperationException("Either ActivityTypeName or StimulusHash must be specified."); } } \ No newline at end of file diff --git a/src/servicebus/Elsa.ServiceBus.MassTransit/Features/MassTransitFeature.cs b/src/servicebus/Elsa.ServiceBus.MassTransit/Features/MassTransitFeature.cs index e8437ccd..1e9e0fc4 100644 --- a/src/servicebus/Elsa.ServiceBus.MassTransit/Features/MassTransitFeature.cs +++ b/src/servicebus/Elsa.ServiceBus.MassTransit/Features/MassTransitFeature.cs @@ -12,7 +12,6 @@ using Elsa.ServiceBus.MassTransit.Options; using Elsa.ServiceBus.MassTransit.Services; using Elsa.Workflows.Attributes; -using Elsa.Workflows.Management.Models; using Elsa.Workflows.Management.Options; using MassTransit; using Microsoft.Extensions.DependencyInjection; @@ -26,6 +25,7 @@ namespace Elsa.ServiceBus.MassTransit.Features; public class MassTransitFeature : FeatureBase { private bool _runInMemory; + private Action _configureServiceBus = _ => { }; /// public MassTransitFeature(IModule module) : base(module) @@ -41,10 +41,19 @@ public MassTransitFeature(IModule module) : base(module) public bool DisableConsumers { get; set; } /// - /// A delegate that can be set to configure MassTransit's . Used by transport-level features such as AzureServiceBusFeature and RabbitMqServiceBusFeature. + /// A delegate that can be set to configure MassTransit's . Used by transport-level features such as AzureServiceBusFeature and RabbitMqServiceBusFeature. /// public Action? BusConfigurator { get; set; } + /// + /// Allows configuring the service bus after the transport has been configured. + /// + public MassTransitFeature ConfigureServiceBus(Action configure) + { + _configureServiceBus += configure; + return this; + } + /// /// A factory that creates a . /// @@ -111,6 +120,7 @@ private void AddMassTransit(Action busConfigurator bus.AddConsumer(definition.ConsumerType, definition.ConsumerDefinitionType); busConfigurator(bus); + _configureServiceBus(bus); }); Services.AddOptions().Configure(options => diff --git a/src/workflows/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs b/src/workflows/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs index f032acc3..4a67942b 100644 --- a/src/workflows/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs +++ b/src/workflows/Elsa.WorkflowContexts/Activities/SetWorkflowContextParameter.cs @@ -17,12 +17,12 @@ namespace Elsa.WorkflowContexts.Activities; public class SetWorkflowContextParameter : CodeActivity { /// - public SetWorkflowContextParameter([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public SetWorkflowContextParameter([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } /// - public SetWorkflowContextParameter(Type providerType, string? parameterName, object parameterValue, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public SetWorkflowContextParameter(Type providerType, string? parameterName, object parameterValue, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { ProviderType = new(providerType); ParameterName = new(parameterName); @@ -41,8 +41,8 @@ public SetWorkflowContextParameter(Type providerType, string? parameterName, obj public static SetWorkflowContextParameter For( string parameterName, object parameterValue, - [CallerFilePath] string? source = default, - [CallerLineNumber] int? line = default) where T : IWorkflowContextProvider => new(typeof(T), parameterName, parameterValue, source, line); + [CallerFilePath] string? source = null, + [CallerLineNumber] int? line = null) where T : IWorkflowContextProvider => new(typeof(T), parameterName, parameterValue, source, line); /// /// Create a new instance of for the specified provider type. @@ -52,7 +52,7 @@ public static SetWorkflowContextParameter For( /// /// The type of the workflow context provider. /// - public static SetWorkflowContextParameter For(object parameterValue, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) where T : IWorkflowContextProvider + public static SetWorkflowContextParameter For(object parameterValue, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) where T : IWorkflowContextProvider { return new SetWorkflowContextParameter(source, line) { @@ -69,7 +69,7 @@ public static SetWorkflowContextParameter For(object parameterValue, [CallerF /// /// The type of the workflow context provider. /// - public static SetWorkflowContextParameter For(Func parameterValue, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) where T : IWorkflowContextProvider + public static SetWorkflowContextParameter For(Func parameterValue, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) where T : IWorkflowContextProvider { return new SetWorkflowContextParameter(source, line) { @@ -85,19 +85,19 @@ public static SetWorkflowContextParameter For(Func ProviderType { get; set; } = default!; + public Input ProviderType { get; set; } = null!; /// /// The name of the parameter to set. If not specified, the parameter name will be inferred from the workflow context provider. /// [Input(Description = "Optional. The name of the parameter to set. If not specified, the parameter name will be inferred from the workflow context provider.")] - public Input ParameterName { get; set; } = default!; + public Input ParameterName { get; set; } = null!; /// /// The value of the parameter to set. /// [Input(Description = "The value of the parameter to set.")] - public Input ParameterValue { get; set; } = default!; + public Input ParameterValue { get; set; } = null!; /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)