diff --git a/src/common/Elsa.Features/Implementations/Module.cs b/src/common/Elsa.Features/Implementations/Module.cs
index c64056133..4821e91d7 100644
--- a/src/common/Elsa.Features/Implementations/Module.cs
+++ b/src/common/Elsa.Features/Implementations/Module.cs
@@ -47,11 +47,13 @@ public class Module : IModule
}
///
- public T Configure(Action? configure = default) where T : class, IFeature
- => Configure(module => (T)Activator.CreateInstance(typeof(T), module)!, configure);
+ public T Configure(Action? configure = null) where T : class, IFeature
+ {
+ return Configure(module => (T)Activator.CreateInstance(typeof(T), module)!, configure);
+ }
///
- public T Configure(Func factory, Action? configure = default) where T : class, IFeature
+ public T Configure(Func factory, Action? configure = null) where T : class, IFeature
{
if (!_features.TryGetValue(typeof(T), out var feature))
{
@@ -81,7 +83,7 @@ public class Module : IModule
///
public IModule ConfigureHostedService(Type hostedServiceType, int priority = 0)
{
- _hostedServiceDescriptors.Add(new HostedServiceDescriptor(priority, hostedServiceType));
+ _hostedServiceDescriptors.Add(new(priority, hostedServiceType));
return this;
}
@@ -121,7 +123,7 @@ public class Module : IModule
var ns = "Elsa";
var displayName = type.GetCustomAttribute()?.DisplayName ?? name;
var description = type.GetCustomAttribute()?.Description;
- registry.Add(new FeatureDescriptor(name, ns, displayName, description));
+ registry.Add(new(name, ns, displayName, description));
}
Services.AddSingleton(registry);
diff --git a/src/common/Elsa.Features/Services/IModule.cs b/src/common/Elsa.Features/Services/IModule.cs
index 48d45795a..da070d3f6 100644
--- a/src/common/Elsa.Features/Services/IModule.cs
+++ b/src/common/Elsa.Features/Services/IModule.cs
@@ -31,12 +31,12 @@ public interface IModule
///
/// Creates and configures a feature of the specified type.
///
- T Configure(Action? configure = default) where T : class, IFeature;
+ T Configure(Action? configure = null) where T : class, IFeature;
///
/// Creates and configures a feature of the specified type.
///
- T Configure(Func factory, Action? configure = default) where T : class, IFeature;
+ T Configure(Func factory, Action? configure = null) where T : class, IFeature;
///
/// Configures a using an optional priority to control in which order it will be registered with the service container.
diff --git a/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs b/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs
index e7135b2c9..a188ee69a 100644
--- a/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs
+++ b/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs
@@ -134,10 +134,11 @@ public class MongoDbStore(IMongoCollection collection, ITe
await collection.BulkWriteAsync(writes, cancellationToken: cancellationToken);
}
- public async Task UpdatePartialAsync(
+ public async Task UpdatePartialAsync(
string id,
IDictionary updatedFields,
string primaryKey = nameof(Entity.Id),
+ bool throwIfNotFound = true,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
@@ -154,7 +155,14 @@ public class MongoDbStore(IMongoCollection collection, ITe
var updateResult = await collection.UpdateOneAsync(filter, updateDefinition, cancellationToken: cancellationToken);
if (updateResult.MatchedCount == 0)
+ {
+ if (!throwIfNotFound)
+ return false;
+
throw new InvalidOperationException($"No document found with ID '{id}'.");
+ }
+
+ return updateResult.ModifiedCount > 0;
}
///
diff --git a/src/modules/Elsa.MongoDb/Features/MongoDbFeature.cs b/src/modules/Elsa.MongoDb/Features/MongoDbFeature.cs
index 09c7afba3..0d9b3b057 100644
--- a/src/modules/Elsa.MongoDb/Features/MongoDbFeature.cs
+++ b/src/modules/Elsa.MongoDb/Features/MongoDbFeature.cs
@@ -1,20 +1,16 @@
-using System.Text.Json;
-using System.Text.Json.Nodes;
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
using Elsa.KeyValues.Entities;
using Elsa.MongoDb.Contracts;
+using Elsa.MongoDb.HostedServices;
using Elsa.MongoDb.NamingStrategies;
using Elsa.MongoDb.Options;
-using Elsa.MongoDb.Serializers;
-using Elsa.Workflows.Memory;
+using Elsa.Workflows.Activities.Flowchart.Models;
using Elsa.Workflows.Runtime.Entities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
-using MongoDB.Bson;
using MongoDB.Bson.Serialization;
-using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver;
namespace Elsa.MongoDb.Features;
@@ -27,7 +23,7 @@ public class MongoDbFeature(IModule module) : FeatureBase(module)
///
/// The MongoDB connection string.
///
- public string ConnectionString { get; set; } = default!;
+ public string ConnectionString { get; set; } = null!;
///
/// A delegate that configures MongoDb.
@@ -39,6 +35,11 @@ public class MongoDbFeature(IModule module) : FeatureBase(module)
///
public Func CollectionNamingStrategy { get; set; } = sp => sp.GetRequiredService();
+ public override void ConfigureHostedServices()
+ {
+ Module.ConfigureHostedService(-10);
+ }
+
///
public override void Apply()
{
@@ -47,24 +48,13 @@ public class MongoDbFeature(IModule module) : FeatureBase(module)
var mongoUrl = new MongoUrl(ConnectionString);
Services.AddSingleton(sp => CreateMongoClient(sp, mongoUrl));
Services.AddScoped(sp => CreateDatabase(sp, mongoUrl));
-
+
Services.TryAddScoped();
Services.AddScoped(CollectionNamingStrategy);
- RegisterSerializers();
RegisterClassMaps();
}
- private static void RegisterSerializers()
- {
- TryRegisterSerializerOrSkipWhenExist(typeof(object), new PolymorphicSerializer());
- TryRegisterSerializerOrSkipWhenExist(typeof(Type), new TypeSerializer());
- TryRegisterSerializerOrSkipWhenExist(typeof(Variable), new VariableSerializer());
- TryRegisterSerializerOrSkipWhenExist(typeof(Version), new VersionSerializer());
- TryRegisterSerializerOrSkipWhenExist(typeof(JsonElement), new JsonElementSerializer());
- TryRegisterSerializerOrSkipWhenExist(typeof(JsonNode), new JsonNodeBsonConverter());
- }
-
private static void RegisterClassMaps()
{
BsonClassMap.TryRegisterClassMap(cm =>
@@ -78,19 +68,14 @@ public class MongoDbFeature(IModule module) : FeatureBase(module)
map.SetIgnoreExtraElements(true); // Needed for missing ID property
map.MapProperty(x => x.Key); // Needed for non-setter property
});
+
+ BsonClassMap.TryRegisterClassMap(map =>
+ {
+ map.AutoMap();
+ map.SetIgnoreExtraElements(true);
+ });
}
- private static void TryRegisterSerializerOrSkipWhenExist(Type type, IBsonSerializer serializer)
- {
- try
- {
- BsonSerializer.TryRegisterSerializer(type, serializer);
- }
- catch (BsonSerializationException ex)
- {
- }
- }
-
private static IMongoClient CreateMongoClient(IServiceProvider sp, MongoUrl mongoUrl)
{
var options = sp.GetRequiredService>().Value;
@@ -99,7 +84,7 @@ public class MongoDbFeature(IModule module) : FeatureBase(module)
// TODO: Uncomment once https://github.com/jbogard/MongoDB.Driver.Core.Extensions.DiagnosticSources/pull/41 is merged and deployed.
//settings.ClusterConfigurator = cb => cb.Subscribe(new DiagnosticsActivityEventSubscriber());
-
+
settings.ApplicationName = GetApplicationName(settings);
settings.WriteConcern = options.WriteConcern;
settings.ReadConcern = options.ReadConcern;
diff --git a/src/modules/Elsa.MongoDb/Helpers/ExpressionHelpers.cs b/src/modules/Elsa.MongoDb/Helpers/ExpressionHelpers.cs
index e6514a25c..88db796fc 100644
--- a/src/modules/Elsa.MongoDb/Helpers/ExpressionHelpers.cs
+++ b/src/modules/Elsa.MongoDb/Helpers/ExpressionHelpers.cs
@@ -5,10 +5,10 @@ using Elsa.Workflows.Runtime.Entities;
namespace Elsa.MongoDb.Helpers;
-internal class ExpressionHelpers
+internal static class ExpressionHelpers
{
public static readonly Expression> WorkflowDefinitionSummary =
- workflowDefinition => new WorkflowDefinitionSummary
+ workflowDefinition => new()
{
Id = workflowDefinition.Id,
DefinitionId = workflowDefinition.DefinitionId,
@@ -18,11 +18,14 @@ internal class ExpressionHelpers
IsLatest = workflowDefinition.IsLatest,
IsPublished = workflowDefinition.IsPublished,
MaterializerName = workflowDefinition.MaterializerName,
- CreatedAt = workflowDefinition.CreatedAt
+ CreatedAt = workflowDefinition.CreatedAt,
+ IsReadonly = workflowDefinition.IsReadonly,
+ ProviderName = workflowDefinition.ProviderName,
+ ToolVersion = workflowDefinition.ToolVersion
};
public static readonly Expression> WorkflowInstanceSummary =
- workflowInstance => new WorkflowInstanceSummary
+ workflowInstance => new()
{
Id = workflowInstance.Id,
DefinitionId = workflowInstance.DefinitionId,
@@ -34,16 +37,17 @@ internal class ExpressionHelpers
Name = workflowInstance.Name,
CreatedAt = workflowInstance.CreatedAt,
UpdatedAt = workflowInstance.UpdatedAt,
- FinishedAt = workflowInstance.FinishedAt
+ FinishedAt = workflowInstance.FinishedAt,
+ IncidentCount = workflowInstance.IncidentCount,
};
- public static readonly Expression> WorkflowInstanceId = workflowInstance => new WorkflowInstanceId
+ public static readonly Expression> WorkflowInstanceId = workflowInstance => new()
{
Id = workflowInstance.Id
};
public static readonly Expression> ActivityExecutionRecordSummary =
- workflowInstance => new ActivityExecutionRecordSummary
+ workflowInstance => new()
{
Id = workflowInstance.Id,
Status = workflowInstance.Status,
@@ -53,6 +57,11 @@ internal class ExpressionHelpers
ActivityTypeVersion = workflowInstance.ActivityTypeVersion,
ActivityName = workflowInstance.ActivityName,
StartedAt = workflowInstance.StartedAt,
- HasBookmarks = workflowInstance.HasBookmarks
+ HasBookmarks = workflowInstance.HasBookmarks,
+ CompletedAt = workflowInstance.CompletedAt,
+ AggregateFaultCount = workflowInstance.AggregateFaultCount,
+ Metadata = workflowInstance.Metadata,
+ WorkflowInstanceId = workflowInstance.WorkflowInstanceId,
+ TenantId = workflowInstance.TenantId,
};
}
\ No newline at end of file
diff --git a/src/modules/Elsa.MongoDb/HostedServices/ConfigureMongoDbSerializers.cs b/src/modules/Elsa.MongoDb/HostedServices/ConfigureMongoDbSerializers.cs
new file mode 100644
index 000000000..927bc81b0
--- /dev/null
+++ b/src/modules/Elsa.MongoDb/HostedServices/ConfigureMongoDbSerializers.cs
@@ -0,0 +1,43 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using Elsa.MongoDb.Serializers;
+using Elsa.Workflows;
+using Elsa.Workflows.Activities.Flowchart.Models;
+using Elsa.Workflows.Memory;
+using JetBrains.Annotations;
+using Microsoft.Extensions.Hosting;
+using MongoDB.Bson.Serialization.Serializers;
+using static MongoDB.Bson.Serialization.BsonSerializer;
+
+namespace Elsa.MongoDb.HostedServices;
+
+///
+/// A hosted service that configures and registers custom MongoDB serializers for various types.
+///
+///
+/// This class implements and is responsible for registering serializers to handle
+/// specific types such as , , , ,
+/// , , and .
+/// It uses helper methods to register these serializers during the application's startup process.
+///
+[UsedImplicitly]
+public class ConfigureMongoDbSerializers(IPayloadSerializer payloadSerializer) : IHostedService
+{
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ TryRegisterSerializer(typeof(object), new PolymorphicSerializer());
+ TryRegisterSerializer(typeof(Type), new TypeSerializer());
+ TryRegisterSerializer(typeof(Variable), new VariableSerializer());
+ TryRegisterSerializer(typeof(Version), new VersionSerializer());
+ TryRegisterSerializer(typeof(JsonElement), new JsonElementSerializer());
+ TryRegisterSerializer(typeof(JsonNode), new JsonNodeBsonConverter());
+ TryRegisterSerializer(typeof(FlowScope), new FlowScopeSerializer(payloadSerializer));
+
+ return Task.CompletedTask;
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.MongoDb/Modules/Management/WorkflowInstanceStore.cs b/src/modules/Elsa.MongoDb/Modules/Management/WorkflowInstanceStore.cs
index 6913ff35f..03e95c3a5 100644
--- a/src/modules/Elsa.MongoDb/Modules/Management/WorkflowInstanceStore.cs
+++ b/src/modules/Elsa.MongoDb/Modules/Management/WorkflowInstanceStore.cs
@@ -8,6 +8,7 @@ using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Management.Models;
using JetBrains.Annotations;
+using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Open.Linq.AsyncExtensions;
@@ -18,7 +19,7 @@ namespace Elsa.MongoDb.Modules.Management;
/// A MongoDb implementation of .
///
[UsedImplicitly]
-public class MongoWorkflowInstanceStore(MongoDbStore mongoDbStore) : IWorkflowInstanceStore
+public class MongoWorkflowInstanceStore(MongoDbStore mongoDbStore, ILogger logger) : IWorkflowInstanceStore
{
///
public async ValueTask FindAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default)
@@ -130,7 +131,10 @@ public class MongoWorkflowInstanceStore(MongoDbStore mongoDbSt
[nameof(WorkflowInstance.UpdatedAt)] = value
};
- await mongoDbStore.UpdatePartialAsync(workflowInstanceId, props, cancellationToken: cancellationToken);
+ var updated = await mongoDbStore.UpdatePartialAsync(workflowInstanceId, props, throwIfNotFound: false, cancellationToken: cancellationToken);
+
+ if (!updated)
+ logger.LogDebug("Failed to update the 'UpdatedAt' timestamp for workflow instance with ID '{WorkflowInstanceId}'. This means this workflow does not yet exist in the DB.", workflowInstanceId);
}
///
diff --git a/src/modules/Elsa.MongoDb/Serializers/FlowScopeSerializer.cs b/src/modules/Elsa.MongoDb/Serializers/FlowScopeSerializer.cs
new file mode 100644
index 000000000..f6d78e931
--- /dev/null
+++ b/src/modules/Elsa.MongoDb/Serializers/FlowScopeSerializer.cs
@@ -0,0 +1,52 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Activities.Flowchart.Models;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization;
+
+namespace Elsa.MongoDb.Serializers;
+
+///
+/// Serializes a .
+///
+public class FlowScopeSerializer(IPayloadSerializer payloadSerializer) : IBsonSerializer
+{
+ ///
+ public Type ValueType => typeof(FlowScope);
+
+ void IBsonSerializer.Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) => Serialize(context, args, (FlowScope)value);
+ object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context, args);
+
+ ///
+ public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, FlowScope? value)
+ {
+ if (value is null)
+ context.Writer.WriteNull();
+ else
+ {
+ var json = payloadSerializer.Serialize(value);
+ context.Writer.WriteString(json);
+ }
+ }
+
+ ///
+ public FlowScope Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
+ {
+ var reader = context.Reader;
+ var bsonType = reader.GetCurrentBsonType();
+
+ if (bsonType == BsonType.Null)
+ {
+ reader.ReadNull();
+ return new();
+ }
+
+ if(bsonType == BsonType.String)
+ {
+ var json = context.Reader.ReadString();
+
+ return string.IsNullOrEmpty(json) ? new() : payloadSerializer.Deserialize(json);
+ }
+
+ return new();
+ }
+}
\ No newline at end of file