diff --git a/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionCreated.cs b/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionCreated.cs
index bd8b2b452..4638f7574 100644
--- a/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionCreated.cs
+++ b/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionCreated.cs
@@ -5,6 +5,13 @@ namespace Elsa.MassTransit.Messages;
///
public class WorkflowDefinitionCreated(string id, bool usableAsActivity)
{
- public string Id { get; set; } = id;
- public bool UsableAsActivity { get; set; } = usableAsActivity;
+ ///
+ /// The ID of the created workflow definition.
+ ///
+ public string Id { get; } = id;
+
+ ///
+ /// Whether the created workflow definition is usable as an activity.
+ ///
+ public bool UsableAsActivity { get; } = usableAsActivity;
}
\ No newline at end of file
diff --git a/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionDeleted.cs b/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionDeleted.cs
index bc3c286a1..ac7d54348 100644
--- a/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionDeleted.cs
+++ b/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionDeleted.cs
@@ -5,5 +5,8 @@ namespace Elsa.MassTransit.Messages;
///
public class WorkflowDefinitionDeleted(string id)
{
- public string Id { get; set; } = id;
+ ///
+ /// The ID of the deleted workflow definition.
+ ///
+ public string Id { get; } = id;
}
\ No newline at end of file
diff --git a/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionPublished.cs b/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionPublished.cs
index 86f219da1..9c77f2fc0 100644
--- a/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionPublished.cs
+++ b/src/modules/Elsa.MassTransit/Messages/WorkflowDefinitionPublished.cs
@@ -1,7 +1,17 @@
namespace Elsa.MassTransit.Messages;
+///
+/// Represents a distributed message that is triggered when a workflow definition is published.
+///
public class WorkflowDefinitionPublished(string id, bool usableAsActivity)
{
- public string Id { get; set; } = id;
- public bool UsableAsActivity { get; set; } = usableAsActivity;
+ ///
+ /// The ID of the published workflow definition.
+ ///
+ public string Id { get; } = id;
+
+ ///
+ /// Whether the published workflow definition is usable as an activity.
+ ///
+ public bool UsableAsActivity { get; } = usableAsActivity;
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Scheduling/Services/DefaultTriggerScheduler.cs b/src/modules/Elsa.Scheduling/Services/DefaultTriggerScheduler.cs
index 50735ab85..b626d72c0 100644
--- a/src/modules/Elsa.Scheduling/Services/DefaultTriggerScheduler.cs
+++ b/src/modules/Elsa.Scheduling/Services/DefaultTriggerScheduler.cs
@@ -91,7 +91,7 @@ public class DefaultTriggerScheduler(IWorkflowScheduler workflowScheduler, ISyst
}
catch (FormatException ex)
{
- logger.LogWarning($"Cron expression format error: {ex.Message}. CronExpression: {cronExpression}");
+ logger.LogWarning(ex,"Cron expression format error: {exceptionMessage}. CronExpression: {cronExpression}", ex.Message, cronExpression);
}
}
}
diff --git a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs
index c8d77efbd..3af335fbe 100644
--- a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs
+++ b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs
@@ -5,6 +5,7 @@ using Elsa.Expressions.Models;
using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Contracts;
+using Elsa.Workflows.Exceptions;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
using Elsa.Workflows.Options;
@@ -580,7 +581,7 @@ public partial class WorkflowExecutionContext : IExecutionContext
///
public async Task CreateActivityExecutionContext(IActivity activity, ActivityInvocationOptions? options = default)
{
- var activityDescriptor = await ActivityRegistryLookup.Find(activity) ?? throw new Exception($"Activity with type {activity.Type} not found in registry");
+ var activityDescriptor = await ActivityRegistryLookup.Find(activity) ?? throw new ActivityNotFoundException(activity.Type);
var tag = options?.Tag;
var parentContext = options?.Owner;
var parentExpressionExecutionContext = parentContext?.ExpressionExecutionContext ?? ExpressionExecutionContext;
diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IActivityRegistry.cs b/src/modules/Elsa.Workflows.Core/Contracts/IActivityRegistry.cs
index c3ed1630f..4a7f44287 100644
--- a/src/modules/Elsa.Workflows.Core/Contracts/IActivityRegistry.cs
+++ b/src/modules/Elsa.Workflows.Core/Contracts/IActivityRegistry.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.Contracts;
@@ -74,7 +75,7 @@ public interface IActivityRegistry : IActivityProvider
///
/// The activity type to register.
/// An optional cancellation token.
- Task RegisterAsync(Type activityType, CancellationToken cancellationToken = default);
+ Task RegisterAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType, CancellationToken cancellationToken = default);
///
/// Registers multiple activity types.
diff --git a/src/modules/Elsa.Workflows.Core/Exceptions/ActivityNotFoundException.cs b/src/modules/Elsa.Workflows.Core/Exceptions/ActivityNotFoundException.cs
index b7f2be040..caad78481 100644
--- a/src/modules/Elsa.Workflows.Core/Exceptions/ActivityNotFoundException.cs
+++ b/src/modules/Elsa.Workflows.Core/Exceptions/ActivityNotFoundException.cs
@@ -5,6 +5,12 @@ namespace Elsa.Workflows.Exceptions;
///
public class ActivityNotFoundException : Exception
{
+ ///
+ public ActivityNotFoundException(string missingTypeName) : base($"Activity type '{missingTypeName}' could not be found.")
+ {
+ MissingTypeName = missingTypeName;
+ }
+
///
public ActivityNotFoundException(string missingTypeName, int missingTypeVersion) : base($"Activity type '{missingTypeName}' version '{missingTypeVersion}' could not be found.")
{
diff --git a/src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs b/src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs
index 8e596007b..beaea748f 100644
--- a/src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs
+++ b/src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Helpers;
using Elsa.Workflows.Models;
@@ -48,7 +49,7 @@ public class ActivityRegistry(IActivityDescriber activityDescriber, IEnumerable<
}
///
- public async Task RegisterAsync(Type activityType, CancellationToken cancellationToken)
+ public async Task RegisterAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType, CancellationToken cancellationToken)
{
var activityTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
@@ -70,6 +71,7 @@ public class ActivityRegistry(IActivityDescriber activityDescriber, IEnumerable<
///
public ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default) => new(_manualActivityDescriptors);
+ ///
public async Task RefreshDescriptors(IEnumerable activityProviders, CancellationToken cancellationToken = default)
{
var providersDictionary = new ConcurrentDictionary>();
@@ -94,7 +96,7 @@ public class ActivityRegistry(IActivityDescriber activityDescriber, IEnumerable<
Add(descriptor, _activityDescriptors, target);
}
- private void Add(ActivityDescriptor descriptor, ConcurrentDictionary<(string Type, int Version), ActivityDescriptor> activityDescriptors, ICollection providerDescriptors)
+ private void Add(ActivityDescriptor? descriptor, ConcurrentDictionary<(string Type, int Version), ActivityDescriptor> activityDescriptors, ICollection providerDescriptors)
{
if (descriptor is null)
{
diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/AutoUpdateTests.cs b/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/AutoUpdateTests.cs
index 040ff1726..8276dd1ff 100644
--- a/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/AutoUpdateTests.cs
+++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/AutoUpdateTests.cs
@@ -3,7 +3,6 @@ using Elsa.Http;
using Elsa.Http.Bookmarks;
using Elsa.Http.Contracts;
using Elsa.Workflows.Contracts;
-using Elsa.Workflows.Helpers;
using Elsa.Workflows.Management.Contracts;
using Elsa.Workflows.Runtime.Filters;
using Microsoft.Extensions.Caching.Memory;
@@ -14,7 +13,6 @@ namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowActivities;
public class AutoUpdateTests : AppComponentTest
{
private readonly IMemoryCache _cache;
- private readonly IBookmarkHasher _bookmarkHasher;
private readonly IHasher _hasher;
private readonly IWorkflowDefinitionCacheManager _definitionCacheManager;
private readonly IWorkflowDefinitionPublisher _publisher;
@@ -26,9 +24,9 @@ public class AutoUpdateTests : AppComponentTest
private readonly IHttpWorkflowsCacheManager _httpCacheManager;
private readonly IWorkflowDefinitionCacheManager _workflowCacheManager;
- private string _httpChangeToken;
- private string _triggerChangeToken;
- private string _graphChangeToken;
+ private string? _httpChangeToken;
+ private string? _triggerChangeToken;
+ private string? _graphChangeToken;
private static readonly object HttpChangeTokenSignal = new();
private static readonly object TriggerChangeTokenSignal = new();
@@ -41,7 +39,6 @@ public class AutoUpdateTests : AppComponentTest
public AutoUpdateTests(App app) : base(app)
{
_cache = Scope.ServiceProvider.GetRequiredService();
- _bookmarkHasher = Scope.ServiceProvider.GetRequiredService();
_hasher = Scope.ServiceProvider.GetRequiredService();
_definitionCacheManager = Scope.ServiceProvider.GetRequiredService();
_publisher = Scope.ServiceProvider.GetRequiredService();
diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/DeleteWorkflowTests.cs b/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/DeleteWorkflowTests.cs
index 249eab3fb..47949871c 100644
--- a/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/DeleteWorkflowTests.cs
+++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/DeleteWorkflowTests.cs
@@ -1,5 +1,4 @@
using Elsa.Workflows.Contracts;
-using Elsa.Workflows.Management.Activities.WorkflowDefinitionActivity;
using Elsa.Workflows.Management.Contracts;
using Microsoft.Extensions.DependencyInjection;
@@ -28,43 +27,42 @@ public class DeleteWorkflowTests : AppComponentTest
[Fact]
public async Task DeleteWorkflow()
{
- EnsureWorkflowInRegistry(_scope1);
+ EnsureWorkflowInRegistry(_scope1, Workflows.DeleteWorkflow.Type);
var workflowDefinitionManager = _scope1.ServiceProvider.GetRequiredService();
await workflowDefinitionManager.DeleteByDefinitionIdAsync(Workflows.DeleteWorkflow.DefinitionId);
- WorkflowTypeDeletedFromRegistry(_scope1);
+ WorkflowTypeDeletedFromRegistry(_scope1, Workflows.DeleteWorkflow.Type);
}
[Fact(Skip = "Clustered tests are interfering with other event driven tests")]
public async Task DeleteWorkflow_Clustered()
{
- EnsureWorkflowInRegistry(_scope1);
- EnsureWorkflowInRegistry(_scope2);
- EnsureWorkflowInRegistry(_scope3);
+ EnsureWorkflowInRegistry(_scope1, Workflows.DeleteWorkflowClustered.Type);
+ EnsureWorkflowInRegistry(_scope2, Workflows.DeleteWorkflowClustered.Type);
+ EnsureWorkflowInRegistry(_scope3, Workflows.DeleteWorkflowClustered.Type);
var workflowDefinitionManager = _scope1.ServiceProvider.GetRequiredService();
- await workflowDefinitionManager.DeleteByDefinitionIdAsync(Workflows.DeleteWorkflow.DefinitionId);
+ await workflowDefinitionManager.DeleteByDefinitionIdAsync(Workflows.DeleteWorkflowClustered.DefinitionId);
- WorkflowTypeDeletedFromRegistry(_scope1);
+ WorkflowTypeDeletedFromRegistry(_scope1, Workflows.DeleteWorkflowClustered.Type);
await _signalManager.WaitAsync(WorkflowDeletedSignal);
- WorkflowTypeDeletedFromRegistry(_scope2);
- WorkflowTypeDeletedFromRegistry(_scope3);
+ WorkflowTypeDeletedFromRegistry(_scope2, Workflows.DeleteWorkflowClustered.Type);
+ WorkflowTypeDeletedFromRegistry(_scope3, Workflows.DeleteWorkflowClustered.Type);
}
- private void EnsureWorkflowInRegistry(IServiceScope scope)
+ private static void EnsureWorkflowInRegistry(IServiceScope scope, string type)
{
var activityRegistry = scope.ServiceProvider.GetRequiredService();
- var descriptor = activityRegistry.Find(Workflows.DeleteWorkflow.Type);
- if (descriptor is null)
- activityRegistry.Add(typeof(WorkflowDefinitionActivityProvider), descriptor);
+ var descriptor = activityRegistry.Find(type);
+ Assert.NotNull(descriptor);
}
- private void WorkflowTypeDeletedFromRegistry(IServiceScope scope)
+ private static void WorkflowTypeDeletedFromRegistry(IServiceScope scope, string type)
{
var activityRegistry = scope.ServiceProvider.GetRequiredService();
- var descriptor = activityRegistry.Find(Workflows.DeleteWorkflow.Type);
+ var descriptor = activityRegistry.Find(type);
Assert.Null(descriptor);
}
diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/Workflows/DeleteWorkflow_Clustered.cs b/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/Workflows/DeleteWorkflow_Clustered.cs
new file mode 100644
index 000000000..00b1722eb
--- /dev/null
+++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/WorkflowActivities/Workflows/DeleteWorkflow_Clustered.cs
@@ -0,0 +1,26 @@
+using Elsa.Scheduling.Activities;
+using Elsa.Workflows.Activities;
+using Elsa.Workflows.Contracts;
+
+namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowActivities.Workflows;
+
+public class DeleteWorkflowClustered : WorkflowBase
+{
+ public static readonly string DefinitionId = Guid.NewGuid().ToString();
+ public static readonly string Type = nameof(DeleteWorkflowClustered);
+
+ protected override void Build(IWorkflowBuilder builder)
+ {
+ builder.Name = Type;
+ builder.WithDefinitionId(DefinitionId);
+ builder.WorkflowOptions.UsableAsActivity = true;
+ builder.Root = new Sequence
+ {
+ Activities =
+ {
+ new Delay(TimeSpan.FromMilliseconds(250)),
+ new WriteLine("This workflow will be deleted!")
+ }
+ };
+ }
+}
\ No newline at end of file