diff --git a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Serialization/FlowchartJsonConverter.cs b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Serialization/FlowchartJsonConverter.cs
index f2cd5fd77..4cde5d8a6 100644
--- a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Serialization/FlowchartJsonConverter.cs
+++ b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Serialization/FlowchartJsonConverter.cs
@@ -5,11 +5,16 @@ using Elsa.Workflows.Core.Contracts;
namespace Elsa.Workflows.Core.Activities.Flowchart.Serialization;
+///
+/// A JSON converter for .
+///
public class FlowchartJsonConverter : JsonConverter
{
private const string AllActivitiesKey = "AllActivities";
private const string AllConnectionsKey = "AllConnections";
+ private const string NotFoundConnectionsKey = "NotFoundConnectionsKey";
+ ///
public override Activities.Flowchart Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (!JsonDocument.TryParseValue(ref reader, out var doc))
@@ -27,9 +32,14 @@ public class FlowchartJsonConverter : JsonConverter
var activityDictionary = activities.ToDictionary(x => x.Id);
connectionSerializerOptions.Converters.Add(new ConnectionJsonConverter(activityDictionary));
+
var connections = connectionsElement.ValueKind != JsonValueKind.Undefined
? connectionsElement.Deserialize>(connectionSerializerOptions)?.Where(x => x.Source != null! && x.Target != null!).ToList() ?? new List()
: new List();
+
+ var notFoundConnections = GetNotFoundConnections(doc.RootElement, connectionSerializerOptions, activities, connections);
+ var connectionsToRestore = FindConnectionsThatCanBeRestored(notFoundConnections, activities);
+ var connectionsWithRestoredOnes = connections.Except(notFoundConnections).Union(connectionsToRestore).ToList();
var flowChart = new Activities.Flowchart
{
@@ -37,17 +47,63 @@ public class FlowchartJsonConverter : JsonConverter
Metadata = metadata,
Start = start,
Activities = activities,
- Connections = connections,
+ Connections = connectionsWithRestoredOnes,
CustomProperties =
{
[AllActivitiesKey] = activities.ToList(),
- [AllConnectionsKey] = connections.ToList()
+ [AllConnectionsKey] = connectionsWithRestoredOnes,
+ [NotFoundConnectionsKey] = notFoundConnections.Except(connectionsToRestore)
}
};
return flowChart;
}
+ private static List GetNotFoundConnections(JsonElement rootElement, JsonSerializerOptions connectionSerializerOptions, IEnumerable activities, IEnumerable connections)
+ {
+ var applicationPropertiesElement = rootElement.TryGetProperty("applicationProperties", out var applicationPropertiesEl) ? applicationPropertiesEl : default;
+ var notFoundConnectionsElement = applicationPropertiesElement.ValueKind != JsonValueKind.Undefined ? applicationPropertiesElement.TryGetProperty(NotFoundConnectionsKey, out var notFoundConnectionsEl) ? notFoundConnectionsEl : default : default;
+
+ var notFoundConnections = notFoundConnectionsElement.ValueKind != JsonValueKind.Undefined
+ ? notFoundConnectionsElement.Deserialize>(connectionSerializerOptions)
+ ?.Where(x => x.Source != null! && x.Target != null!).ToList() ?? new List()
+ : new List();
+
+ // Add connections of NotFoundActivity to the list if they aren't already in it.
+ var notFoundActivities = activities.Where(x => x is NotFoundActivity).Cast().ToList();
+ var notFoundActivityConnections = connections.Where(x => notFoundActivities.Contains(x.Source)).ToList();
+
+ foreach (var notFoundConnection in notFoundActivityConnections)
+ {
+ if (notFoundConnections.All(x => x.Source != notFoundConnection.Source || x.Target != notFoundConnection.Target))
+ notFoundConnections.Add(notFoundConnection);
+ }
+
+ return notFoundConnections;
+ }
+
+ private static List FindConnectionsThatCanBeRestored(IEnumerable notFoundConnections, IEnumerable activities)
+ {
+ var connectionsThatCanBeRestored = new List();
+ var foundActivities = activities.Where(x => x is not NotFoundActivity).ToList();
+
+ foreach (var notFoundConnection in notFoundConnections.ToList())
+ {
+ var missingSource = notFoundConnection.Source;
+ var missingTarget = notFoundConnection.Target;
+ var source = foundActivities.FirstOrDefault(x => x.Id == missingSource.Id);
+ var target = foundActivities.FirstOrDefault(x => x.Id == missingTarget.Id);
+
+ if (source == null || target == null) continue;
+
+ var connection = notFoundConnection with {Source = source, Target = target};
+ connectionsThatCanBeRestored.Add(connection);
+ }
+
+ return connectionsThatCanBeRestored;
+ }
+
+ ///
public override void Write(Utf8JsonWriter writer, Activities.Flowchart value, JsonSerializerOptions options)
{
var activities = value.Activities;
@@ -57,7 +113,7 @@ public class FlowchartJsonConverter : JsonConverter
connectionSerializerOptions.Converters.Add(new ConnectionJsonConverter(activityDictionary));
var allActivities = value.CustomProperties.TryGetValue(AllActivitiesKey, out var a) ? a : activities;
- var allConnections = value.CustomProperties.TryGetValue(AllConnectionsKey, out var c) ? c : value.Connections;
+ var allConnections = (ICollection)(value.CustomProperties.TryGetValue(AllConnectionsKey, out var c) ? c : value.Connections);
var applicationProperties = new Dictionary(value.CustomProperties);
applicationProperties.Remove(AllActivitiesKey);
@@ -65,8 +121,8 @@ public class FlowchartJsonConverter : JsonConverter
var model = new
{
- Type = value.Type,
- Version = value.Version,
+ value.Type,
+ value.Version,
value.Id,
value.Metadata,
ApplicationProperties = applicationProperties,
diff --git a/src/modules/Elsa.Workflows.Core/Activities/NotFoundActivity.cs b/src/modules/Elsa.Workflows.Core/Activities/NotFoundActivity.cs
index 24ff531b3..72f8eb2f9 100644
--- a/src/modules/Elsa.Workflows.Core/Activities/NotFoundActivity.cs
+++ b/src/modules/Elsa.Workflows.Core/Activities/NotFoundActivity.cs
@@ -1,6 +1,7 @@
using System.ComponentModel;
using Elsa.Workflows.Core.Attributes;
using Elsa.Workflows.Core.Models;
+using JetBrains.Annotations;
namespace Elsa.Workflows.Core.Activities;
@@ -9,6 +10,7 @@ namespace Elsa.Workflows.Core.Activities;
///
[Browsable(false)]
[Activity("Elsa", "System", "A placeholder activity that will be used in case a workflow definition references an activity type that cannot be found.")]
+[PublicAPI]
public class NotFoundActivity : CodeActivity
{
///
@@ -32,5 +34,8 @@ public class NotFoundActivity : CodeActivity
///
public int MissingTypeVersion { get; set; }
- public string OriginalActivityJson { get; set; }
+ ///
+ /// The original activity JSON.
+ ///
+ public string OriginalActivityJson { get; set; } = default!;
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IActivityDescriber.cs b/src/modules/Elsa.Workflows.Core/Contracts/IActivityDescriber.cs
index adae9b03b..ddef3d6ab 100644
--- a/src/modules/Elsa.Workflows.Core/Contracts/IActivityDescriber.cs
+++ b/src/modules/Elsa.Workflows.Core/Contracts/IActivityDescriber.cs
@@ -4,11 +4,58 @@ using Elsa.Workflows.Core.Models;
namespace Elsa.Workflows.Core.Contracts;
+///
+/// Creates instances of for a given activity type.
+///
public interface IActivityDescriber
{
+ ///
+ /// Describes the specified activity type.
+ ///
+ /// The activity type to describe.
+ /// The cancellation token.
+ /// The activity descriptor.
ValueTask DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType, CancellationToken cancellationToken = default);
+
+ ///
+ /// Creates an instance of for the specified property.
+ ///
+ /// The property to describe.
+ /// The output descriptor.
OutputDescriptor DescribeOutputProperty(PropertyInfo propertyInfo);
+
+ ///
+ /// Creates an instance of for the specified property.
+ ///
+ /// The property to describe.
+ /// The input descriptor.
InputDescriptor DescribeInputProperty(PropertyInfo propertyInfo);
+
+ ///
+ /// Describes the input properties of the specified activity type.
+ ///
+ /// The activity type to describe.
+ /// The input descriptors.
IEnumerable DescribeInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType);
+
+ ///
+ /// Describes the output properties of the specified activity type.
+ ///
+ /// The activity type to describe.
+ /// The output descriptors.
IEnumerable DescribeOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType);
+
+ ///
+ /// Gets the input properties of the specified activity type.
+ ///
+ /// The activity type.
+ /// The input properties.
+ IEnumerable GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType);
+
+ ///
+ /// Gets the output properties of the specified activity type.
+ ///
+ /// The activity type.
+ /// The output properties.
+ IEnumerable GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType);
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IMissingActivityTypesRegistry.cs b/src/modules/Elsa.Workflows.Core/Contracts/IMissingActivityTypesRegistry.cs
new file mode 100644
index 000000000..815c84d2b
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Core/Contracts/IMissingActivityTypesRegistry.cs
@@ -0,0 +1,28 @@
+using Elsa.Workflows.Core.Models;
+
+namespace Elsa.Workflows.Core.Contracts;
+
+///
+/// A registry for activity types that are missing from the workflow definition.
+///
+public interface IMissingActivityTypesRegistry
+{
+ ///
+ /// Adds a missing activity type.
+ ///
+ /// The missing activity type.
+ void Add(MissingActivityType missingActivityType);
+
+ ///
+ /// Removes a missing activity type.
+ ///
+ /// The type name of the missing activity type to remove.
+ /// The version of the missing activity type to remove.
+ void Remove(string typeName, int version);
+
+ ///
+ /// Returns a list of missing activity types.
+ ///
+ /// A list of missing activity types.
+ IEnumerable List();
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Models/MissingActivityType.cs b/src/modules/Elsa.Workflows.Core/Models/MissingActivityType.cs
new file mode 100644
index 000000000..f39e2bc06
--- /dev/null
+++ b/src/modules/Elsa.Workflows.Core/Models/MissingActivityType.cs
@@ -0,0 +1,27 @@
+namespace Elsa.Workflows.Core.Models;
+
+///
+/// Represents a missing activity type.
+///
+public class MissingActivityType
+{
+ ///
+ /// The type name of the missing activity type.
+ ///
+ public string TypeName { get; set; } = default!;
+
+ ///
+ /// The version of the missing activity type.
+ ///
+ public int Version { get; set; }
+
+ ///
+ /// The original activity JSON.
+ ///
+ public string ActivityJson { get; set; } = default!;
+
+ ///
+ /// The original activity outcomes.
+ ///
+ public ICollection Outcomes { get; set; } = new List();
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs b/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs
index 78bccc612..56f1ec9d5 100644
--- a/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs
+++ b/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs
@@ -64,7 +64,7 @@ public class ActivityDescriber : IActivityDescriber
Name = x,
DisplayName = x
}).ToDictionary(x => x.Name) ?? new Dictionary();
-
+
var allPorts = embeddedPorts.Concat(flowPorts.Values);
var inputProperties = GetInputProperties(activityType).ToList();
var outputProperties = GetOutputProperties(activityType).ToList();
@@ -94,10 +94,12 @@ public class ActivityDescriber : IActivityDescriber
return ValueTask.FromResult(descriptor);
}
-
+
+ ///
public IEnumerable GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType) =>
activityType.GetProperties().Where(x => typeof(Input).IsAssignableFrom(x.PropertyType) || x.GetCustomAttribute() != null).DistinctBy(x => x.Name);
-
+
+ ///
public IEnumerable GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType) =>
activityType.GetProperties().Where(x => typeof(Output).IsAssignableFrom(x.PropertyType)).DistinctBy(x => x.Name).ToList();
diff --git a/src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs b/src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs
index d6533e289..d11d8691a 100644
--- a/src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs
+++ b/src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs
@@ -8,7 +8,6 @@ namespace Elsa.Workflows.Core.Services;
public class ActivityRegistry : IActivityRegistry
{
private readonly IActivityDescriber _activityDescriber;
- private readonly IActivityVisitor _activityVisitor;
private readonly ISet _manualActivityDescriptors = new HashSet();
private readonly IDictionary> _providedActivityDescriptors = new Dictionary>();
private readonly IDictionary<(string Type, int Version), ActivityDescriptor> _activityDescriptors = new Dictionary<(string Type, int Version), ActivityDescriptor>();
@@ -16,10 +15,9 @@ public class ActivityRegistry : IActivityRegistry
///
/// Initializes a new instance of the class.
///
- public ActivityRegistry(IActivityDescriber activityDescriber, IActivityVisitor activityVisitor)
+ public ActivityRegistry(IActivityDescriber activityDescriber)
{
_activityDescriber = activityDescriber;
- _activityVisitor = activityVisitor;
}
///
diff --git a/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs b/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs
index 68aa9d340..be3d952aa 100644
--- a/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs
+++ b/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs
@@ -1,7 +1,5 @@
-using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
-using Elsa.Common.Models;
using Elsa.Expressions.Contracts;
using Elsa.Expressions.Helpers;
using Elsa.Expressions.Models;
diff --git a/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverterFactory.cs b/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverterFactory.cs
index 39d6de4a8..e85731a5d 100644
--- a/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverterFactory.cs
+++ b/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverterFactory.cs
@@ -5,10 +5,14 @@ using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.Management.Serialization.Converters;
+///
+/// Creates instances of .
+///
public class ActivityJsonConverterFactory : JsonConverterFactory
{
private readonly IServiceProvider _serviceProvider;
+ ///
public ActivityJsonConverterFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
@@ -16,7 +20,9 @@ public class ActivityJsonConverterFactory : JsonConverterFactory
// Notice that this factory only creates converters when the type to convert is IActivity.
// The ActivityJsonConverter will create concrete activity objects, which then uses regular serialization
+ ///
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(IActivity);
+ ///
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => ActivatorUtilities.CreateInstance(_serviceProvider);
}
\ No newline at end of file