Add PropertyBag for non-polymorphic data transfer between server and client (#5735)
* Fix issues with workflow deletion, newline, and DB context. Retract published workflows before deletion to avoid exceptions. Added missing newline at EOF for consistency. Removed redundant DisposeAsync call in migration service. * Add PropertyBag for storing custom workflow properties Replaced `CustomProperties` with `PropertyBag` class for more structured property management across workflows. Adjusted serializations, extensions, and middleware for seamless integration with the new `PropertyBag` structure.
This commit is contained in:
parent
218f3b46e4
commit
558486920b
|
|
@ -0,0 +1,41 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Api.Client.Shared.Models;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for the PropertyBag class.
|
||||
/// </summary>
|
||||
public static class PropertyBagExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to retrieve a value from the PropertyBag based on the provided key.
|
||||
/// If the specified key does not exist in the PropertyBag, the method will return the default value obtained from the defaultValue function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to retrieve.</typeparam>
|
||||
/// <param name="propertyBag">The PropertyBag to retrieve the value from.</param>
|
||||
/// <param name="key">The key of the value to retrieve.</param>
|
||||
/// <param name="defaultValue">A function that returns the default value to be returned if the key does not exist in the PropertyBag.</param>
|
||||
/// <returns>The value associated with the key, or the default value if the key does not exist.</returns>
|
||||
public static T TryGetValueOrDefault<T>(this PropertyBag propertyBag, string key, Func<T> defaultValue)
|
||||
{
|
||||
if (!propertyBag.TryGetValue(key, out var value))
|
||||
return defaultValue();
|
||||
|
||||
var json = (string)value;
|
||||
return JsonSerializer.Deserialize<T>(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value in the PropertyBag based on the provided key.
|
||||
/// The value is serialized using JSON.
|
||||
/// </summary>
|
||||
/// <param name="propertyBag">The PropertyBag to set the value in.</param>
|
||||
/// <param name="key">The key to associate with the value.</param>
|
||||
/// <param name="value">The value to store in the PropertyBag.</param>
|
||||
public static void SetValue(this PropertyBag propertyBag, string key, object value)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value);
|
||||
propertyBag[key] = json;
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,11 @@ public class WorkflowDefinition : LinkedEntity
|
|||
/// </summary>
|
||||
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow.
|
||||
/// </summary>
|
||||
public PropertyBag PropertyBag { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The name of the workflow provider that created this workflow, if any.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,11 @@ public class WorkflowDefinitionModel : LinkedEntity
|
|||
/// </summary>
|
||||
public IDictionary<string, object>? CustomProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow.
|
||||
/// </summary>
|
||||
public PropertyBag PropertyBag { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the workflow definition is read-only.
|
||||
/// </summary>
|
||||
|
|
|
|||
20
src/clients/Elsa.Api.Client/Shared/Models/PropertyBag.cs
Normal file
20
src/clients/Elsa.Api.Client/Shared/Models/PropertyBag.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Elsa.Api.Client.Shared.Models;
|
||||
|
||||
/// A dictionary of values that is skipped by polymorphic serialization.
|
||||
public class PropertyBag : Dictionary<string, object>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[JsonConstructor]
|
||||
public PropertyBag() : base(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public PropertyBag(IDictionary<string, object> dictionary) : this()
|
||||
{
|
||||
foreach (var kvp in dictionary)
|
||||
Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,4 +21,5 @@ public class WrappedInput
|
|||
/// Gets or sets the memory reference of this input.
|
||||
/// </summary>
|
||||
public MemoryReference MemoryReference { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
67
src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs
Normal file
67
src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Common.Models;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for the PropertyBag class.
|
||||
/// </summary>
|
||||
public static class PropertyBagExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to retrieve a value from the PropertyBag based on the provided key.
|
||||
/// If the specified key does not exist in the PropertyBag, the method will return the default value obtained from the defaultValue function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to retrieve.</typeparam>
|
||||
/// <param name="propertyBag">The PropertyBag to retrieve the value from.</param>
|
||||
/// <param name="key">The key of the value to retrieve.</param>
|
||||
/// <param name="defaultValue">A function that returns the default value to be returned if the key does not exist in the PropertyBag.</param>
|
||||
/// <param name="options">Optional JSON serializer options.</param>
|
||||
/// <returns>The value associated with the key, or the default value if the key does not exist.</returns>
|
||||
public static T TryGetValueOrDefault<T>(this PropertyBag propertyBag, string key, Func<T> defaultValue, JsonSerializerOptions? options = null)
|
||||
{
|
||||
if (!propertyBag.TryGetValue(key, out var value))
|
||||
return defaultValue();
|
||||
|
||||
var json = (string)value;
|
||||
return JsonSerializer.Deserialize<T>(json, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to retrieve a value from the PropertyBag based on the provided key.
|
||||
/// If the specified key does not exist in the PropertyBag, the method will return the default value obtained from the defaultValue function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to retrieve.</typeparam>
|
||||
/// <param name="propertyBag">The PropertyBag to retrieve the value from.</param>
|
||||
/// <param name="key">The key of the value to retrieve.</param>
|
||||
/// <param name="value">The deserialized value.</param>
|
||||
/// <param name="options">Optional JSON serializer options.</param>
|
||||
/// <returns>True if the value exists, false otherwise.</returns>
|
||||
public static bool TryGetValue<T>(this PropertyBag propertyBag, string key, out T value, JsonSerializerOptions? options = null)
|
||||
{
|
||||
if (!propertyBag.TryGetValue(key, out var v))
|
||||
{
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
var json = (string)v;
|
||||
value = JsonSerializer.Deserialize<T>(json, options);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value in the PropertyBag based on the provided key.
|
||||
/// The value is serialized using JSON.
|
||||
/// </summary>
|
||||
/// <param name="propertyBag">The PropertyBag to set the value in.</param>
|
||||
/// <param name="key">The key to associate with the value.</param>
|
||||
/// <param name="value">The value to store in the PropertyBag.</param>
|
||||
/// /// <param name="options">Optional JSON serializer options.</param>
|
||||
public static void SetValue(this PropertyBag propertyBag, string key, object value, JsonSerializerOptions? options = null)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value);
|
||||
propertyBag[key] = json;
|
||||
}
|
||||
}
|
||||
20
src/modules/Elsa.Common/Models/PropertyBag.cs
Normal file
20
src/modules/Elsa.Common/Models/PropertyBag.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Elsa.Common.Models;
|
||||
|
||||
/// A dictionary of values that is skipped by polymorphic serialization.
|
||||
public class PropertyBag : Dictionary<string, object>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[JsonConstructor]
|
||||
public PropertyBag() : base(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public PropertyBag(IDictionary<string, object> dictionary) : this()
|
||||
{
|
||||
foreach (var kvp in dictionary)
|
||||
Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
|
@ -114,4 +114,4 @@ public struct VersionOptions
|
|||
/// Returns a simple string representation of this <see cref="VersionOptions"/>.
|
||||
/// </summary>
|
||||
public override string ToString() => AllVersions ? "AllVersions" : IsDraft ? "Draft" : IsLatest ? "Latest" : IsPublished ? "Published" : IsLatestOrPublished ? "LatestOrPublished" : IsLatestAndPublished ? "LatestAndPublished" : Version.ToString();
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ public class RunMigrationsHostedService<TDbContext> : IHostedService where TDbCo
|
|||
var dbContextFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<TDbContext>>();
|
||||
await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
await dbContext.Database.MigrateAsync(cancellationToken);
|
||||
await dbContext.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ internal class Configurations : IEntityTypeConfiguration<WorkflowDefinition>, IE
|
|||
builder.Ignore(x => x.Outputs);
|
||||
builder.Ignore(x => x.Outcomes);
|
||||
builder.Ignore(x => x.CustomProperties);
|
||||
builder.Ignore(x => x.PropertyBag);
|
||||
builder.Ignore(x => x.Options);
|
||||
builder.Property<string>("Data");
|
||||
builder.Property<bool?>("UsableAsActivity");
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
|
|||
|
||||
private ValueTask OnSaveAsync(ManagementElsaDbContext managementElsaDbContext, WorkflowDefinition entity, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties);
|
||||
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties, entity.PropertyBag);
|
||||
var json = _payloadSerializer.Serialize(data);
|
||||
|
||||
managementElsaDbContext.Entry(entity).Property("Data").CurrentValue = json;
|
||||
|
|
@ -165,7 +165,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
|
|||
if (entity == null)
|
||||
return ValueTask.CompletedTask;
|
||||
|
||||
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties);
|
||||
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties, entity.PropertyBag);
|
||||
var json = (string?)managementElsaDbContext.Entry(entity).Property("Data").CurrentValue;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
|
|
@ -177,6 +177,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
|
|||
entity.Outputs = data.Outputs;
|
||||
entity.Outcomes = data.Outcomes;
|
||||
entity.CustomProperties = data.CustomProperties;
|
||||
entity.PropertyBag = data.PropertyBag;
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
|
@ -225,7 +226,8 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
|
|||
ICollection<InputDefinition> inputs,
|
||||
ICollection<OutputDefinition> outputs,
|
||||
ICollection<string> outcomes,
|
||||
IDictionary<string, object> customProperties
|
||||
IDictionary<string, object> customProperties,
|
||||
PropertyBag propertyBag
|
||||
)
|
||||
{
|
||||
Options = options;
|
||||
|
|
@ -234,6 +236,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
|
|||
Outputs = outputs;
|
||||
Outcomes = outcomes;
|
||||
CustomProperties = customProperties;
|
||||
PropertyBag = propertyBag;
|
||||
}
|
||||
|
||||
public WorkflowOptions Options { get; set; } = new();
|
||||
|
|
@ -241,6 +244,10 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
|
|||
public ICollection<InputDefinition> Inputs { get; set; } = new List<InputDefinition>();
|
||||
public ICollection<OutputDefinition> Outputs { get; set; } = new List<OutputDefinition>();
|
||||
public ICollection<string> Outcomes { get; set; } = new List<string>();
|
||||
|
||||
[Obsolete("Use PropertyBag instead")]
|
||||
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
public PropertyBag PropertyBag { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,16 +4,16 @@ using Elsa.Workflows.Management.Entities;
|
|||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Adds extension methods to <see cref="WorkflowDefinition"/>.
|
||||
/// </summary>
|
||||
public static class WorkflowDefinitionExtensions
|
||||
public static class WorkflowContextWorkflowDefinitionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the workflow context provider types that are installed on the workflow definition.
|
||||
/// </summary>
|
||||
/// <param name="workflowDefinition">The workflow definition to get the provider types from.</param>
|
||||
/// <returns>The workflow context provider types.</returns>
|
||||
public static IEnumerable<Type> GetWorkflowContextProviderTypes(this WorkflowDefinition workflowDefinition) =>
|
||||
workflowDefinition.CustomProperties.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new List<Type>());
|
||||
public static IEnumerable<Type> GetWorkflowContextProviderTypes(this WorkflowDefinition workflowDefinition)
|
||||
{
|
||||
return workflowDefinition.PropertyBag.GetOrAdd(Constants.WorkflowContextProviderTypesKey, () => new List<Type>());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +1,38 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Expressions.Contracts;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.WorkflowContexts.Contracts;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Pipelines.ActivityExecution;
|
||||
using Elsa.Workflows.Runtime.Middleware.Activities;
|
||||
using Elsa.Workflows.Serialization.Converters;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.WorkflowContexts.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Middleware that loads and save workflow context into the currently executing workflow using installed workflow context providers.
|
||||
/// </summary>
|
||||
public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMiddleware
|
||||
[UsedImplicitly]
|
||||
public class WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory, IWellKnownTypeRegistry wellKnownTypeRegistry) : IActivityExecutionMiddleware
|
||||
{
|
||||
private readonly ActivityMiddlewareDelegate _next;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public WorkflowContextActivityExecutionMiddleware(ActivityMiddlewareDelegate next, IServiceScopeFactory serviceScopeFactory)
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
_next = next;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
PropertyNameCaseInsensitive = true
|
||||
}.WithConverters(new TypeJsonConverter(wellKnownTypeRegistry));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask InvokeAsync(ActivityExecutionContext context)
|
||||
{
|
||||
// Check if the workflow contains any workflow context providers.
|
||||
if (!context.WorkflowExecutionContext.Workflow.CustomProperties.TryGetValue<ICollection<Type>>(Constants.WorkflowContextProviderTypesKey, out var providerTypes))
|
||||
if (!context.WorkflowExecutionContext.Workflow.PropertyBag.TryGetValue<ICollection<Type>>(Constants.WorkflowContextProviderTypesKey, out var providerTypes, _jsonSerializerOptions))
|
||||
{
|
||||
await _next(context);
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (providerTypes.Count == 0)
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -41,12 +42,12 @@ public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMidd
|
|||
// Is the activity configured to load the context?
|
||||
foreach (var providerType in providerTypes)
|
||||
{
|
||||
// Is the activity configured to load the context or is this a background execution?
|
||||
// Is the activity configured to load the context, or is this a background execution?
|
||||
var load = isBackgroundExecution || context.Activity.GetActivityWorkflowContextSettings(providerType).Load;
|
||||
if (!load) continue;
|
||||
|
||||
// Load the context.
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
using var scope = serviceScopeFactory.CreateScope();
|
||||
var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType);
|
||||
var value = await provider.LoadAsync(context.WorkflowExecutionContext);
|
||||
|
||||
|
|
@ -55,7 +56,7 @@ public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMidd
|
|||
}
|
||||
|
||||
// Invoke the next middleware.
|
||||
await _next(context);
|
||||
await next(context);
|
||||
|
||||
// Invoke each workflow context provider to persists the context.
|
||||
foreach (var providerType in providerTypes)
|
||||
|
|
@ -65,7 +66,7 @@ public class WorkflowContextActivityExecutionMiddleware : IActivityExecutionMidd
|
|||
if (!save) continue;
|
||||
|
||||
// Get the loaded value from the workflow execution context.
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
using var scope = serviceScopeFactory.CreateScope();
|
||||
var value = context.WorkflowExecutionContext.GetWorkflowContext(providerType);
|
||||
|
||||
// Save the context.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ public class StaticWorkflowDefinitionLinker(
|
|||
Outputs = workflowDefinitionModel.Outputs,
|
||||
Outcomes = workflowDefinitionModel.Outcomes,
|
||||
CustomProperties = workflowDefinitionModel.CustomProperties,
|
||||
PropertyBag = workflowDefinitionModel.PropertyBag,
|
||||
IsReadonly = workflowDefinitionModel.IsReadonly,
|
||||
IsSystem = workflowDefinitionModel.IsSystem,
|
||||
IsLatest = workflowDefinitionModel.IsLatest,
|
||||
|
|
@ -99,6 +100,7 @@ public class StaticWorkflowDefinitionLinker(
|
|||
Outputs = item.Outputs,
|
||||
Outcomes = item.Outcomes,
|
||||
CustomProperties = item.CustomProperties,
|
||||
PropertyBag = item.PropertyBag,
|
||||
IsReadonly = item.IsReadonly,
|
||||
IsSystem = item.IsSystem,
|
||||
IsLatest = item.IsLatest,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.ComponentModel;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Workflows.Attributes;
|
||||
using Elsa.Workflows.Contracts;
|
||||
|
|
@ -30,6 +31,7 @@ public class Workflow : Composite<object>, ICloneable
|
|||
ICollection<OutputDefinition> outputs,
|
||||
ICollection<string> outcomes,
|
||||
IDictionary<string, object> customProperties,
|
||||
PropertyBag propertyBag,
|
||||
bool isReadonly,
|
||||
bool isSystem)
|
||||
{
|
||||
|
|
@ -38,6 +40,7 @@ public class Workflow : Composite<object>, ICloneable
|
|||
Inputs = inputs;
|
||||
Outputs = outputs;
|
||||
Outcomes = outcomes;
|
||||
PropertyBag = propertyBag;
|
||||
WorkflowMetadata = workflowMetadata;
|
||||
Options = options;
|
||||
Variables = variables;
|
||||
|
|
@ -96,6 +99,11 @@ public class Workflow : Composite<object>, ICloneable
|
|||
/// Gets or sets options for the workflow.
|
||||
/// </summary>
|
||||
public WorkflowOptions Options { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// A bag of properties that can be used by applications and modules to store information that can be shared with tooling.
|
||||
/// </summary>
|
||||
public PropertyBag PropertyBag { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Make workflow definition readonly.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Elsa.Common.Models;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Contracts;
|
||||
|
|
@ -7,25 +8,9 @@ using Elsa.Workflows.Models;
|
|||
namespace Elsa.Workflows.Builders;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class WorkflowBuilder : IWorkflowBuilder
|
||||
public class WorkflowBuilder(IActivityVisitor activityVisitor, IIdentityGraphService identityGraphService, IActivityRegistry activityRegistry, IIdentityGenerator identityGenerator)
|
||||
: IWorkflowBuilder
|
||||
{
|
||||
private readonly IActivityVisitor _activityVisitor;
|
||||
private readonly IIdentityGraphService _identityGraphService;
|
||||
private readonly IActivityRegistry _activityRegistry;
|
||||
private readonly IIdentityGenerator _identityGenerator;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public WorkflowBuilder(IActivityVisitor activityVisitor, IIdentityGraphService identityGraphService, IActivityRegistry activityRegistry, IIdentityGenerator identityGenerator)
|
||||
{
|
||||
_activityVisitor = activityVisitor;
|
||||
_identityGraphService = identityGraphService;
|
||||
_activityRegistry = activityRegistry;
|
||||
_identityGenerator = identityGenerator;
|
||||
Result = new Variable();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? Id { get; set; }
|
||||
|
||||
|
|
@ -63,11 +48,14 @@ public class WorkflowBuilder : IWorkflowBuilder
|
|||
public ICollection<string> Outcomes { get; set; } = new List<string>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Variable? Result { get; set; }
|
||||
public Variable? Result { get; set; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public PropertyBag PropertyBag { get; set; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public WorkflowOptions WorkflowOptions { get; } = new();
|
||||
|
||||
|
|
@ -216,14 +204,14 @@ public class WorkflowBuilder : IWorkflowBuilder
|
|||
/// <inheritdoc />
|
||||
public async Task<Workflow> BuildWorkflowAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var definitionId = DefinitionId ?? _identityGenerator.GenerateId();
|
||||
var id = Id ?? _identityGenerator.GenerateId();
|
||||
var definitionId = DefinitionId ?? identityGenerator.GenerateId();
|
||||
var id = Id ?? identityGenerator.GenerateId();
|
||||
var root = Root ?? new Sequence();
|
||||
var identity = new WorkflowIdentity(definitionId, Version, id);
|
||||
var publication = WorkflowPublication.LatestAndPublished;
|
||||
var name = string.IsNullOrEmpty(Name) ? definitionId : Name;
|
||||
var workflowMetadata = new WorkflowMetadata(name, Description);
|
||||
var workflow = new Workflow(identity, publication, workflowMetadata, WorkflowOptions, root, Variables, Inputs, Outputs, Outcomes, CustomProperties, IsReadonly, IsSystem);
|
||||
var workflow = new Workflow(identity, publication, workflowMetadata, WorkflowOptions, root, Variables, Inputs, Outputs, Outcomes, CustomProperties, PropertyBag, IsReadonly, IsSystem);
|
||||
|
||||
// If a Result variable is defined, install it into the workflow, so we can capture the output into it.
|
||||
if (Result != null)
|
||||
|
|
@ -232,15 +220,15 @@ public class WorkflowBuilder : IWorkflowBuilder
|
|||
workflow.Result = new Output<object>(Result);
|
||||
}
|
||||
|
||||
var graph = await _activityVisitor.VisitAsync(workflow, cancellationToken);
|
||||
var graph = await activityVisitor.VisitAsync(workflow, cancellationToken);
|
||||
var nodes = graph.Flatten().ToList();
|
||||
|
||||
// Register all activity types first. The identity graph service will need to know about all activity types.
|
||||
var distinctActivityTypes = nodes.Select(x => x.Activity.GetType()).Distinct().ToList();
|
||||
await _activityRegistry.RegisterAsync(distinctActivityTypes, cancellationToken);
|
||||
await activityRegistry.RegisterAsync(distinctActivityTypes, cancellationToken);
|
||||
|
||||
// Assign identities to all activities.
|
||||
await _identityGraphService.AssignIdentitiesAsync(nodes);
|
||||
await identityGraphService.AssignIdentitiesAsync(nodes);
|
||||
|
||||
// Give unnamed variables in each variable container a predictable name.
|
||||
var variableContainers = nodes.Where(x => x.Activity is IVariableContainer).Select(x => (IVariableContainer)x.Activity).ToList();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Elsa.Common.Models;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Memory;
|
||||
using Elsa.Workflows.Models;
|
||||
|
|
@ -82,8 +83,14 @@ public interface IWorkflowBuilder
|
|||
/// <summary>
|
||||
/// A set of properties that can be used for storing application-specific information about the workflow being built.
|
||||
/// </summary>
|
||||
[Obsolete("Use PropertyBag instead")]
|
||||
IDictionary<string, object> CustomProperties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A set of properties that can be used for storing application-specific information about the workflow being built.
|
||||
/// </summary>
|
||||
PropertyBag PropertyBag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A fluent method for setting the <see cref="DefinitionId"/> property.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Elsa.Common.Entities;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Workflows.Memory;
|
||||
using Elsa.Workflows.Models;
|
||||
|
||||
|
|
@ -54,11 +55,12 @@ public class WorkflowDefinition : VersionedEntity
|
|||
/// </summary>
|
||||
public ICollection<string> Outcomes { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow.
|
||||
/// </summary>
|
||||
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
/// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow.
|
||||
public PropertyBag PropertyBag { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The name of the workflow provider that created this workflow, if any.
|
||||
/// </summary>
|
||||
|
|
@ -98,6 +100,5 @@ public class WorkflowDefinition : VersionedEntity
|
|||
/// <summary>
|
||||
/// Creates and returns a shallow copy of the workflow definition.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public WorkflowDefinition ShallowClone() => (WorkflowDefinition)MemberwiseClone();
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ public class WorkflowDefinitionMapper
|
|||
source.Outputs,
|
||||
source.Outcomes,
|
||||
source.CustomProperties,
|
||||
source.PropertyBag,
|
||||
source.IsReadonly,
|
||||
source.IsSystem);
|
||||
}
|
||||
|
|
@ -78,6 +79,7 @@ public class WorkflowDefinitionMapper
|
|||
source.Outputs ?? new List<OutputDefinition>(),
|
||||
source.Outcomes ?? new List<string>(),
|
||||
source.CustomProperties ?? new Dictionary<string, object>(),
|
||||
source.PropertyBag ?? new(),
|
||||
source.IsReadonly,
|
||||
source.IsSystem);
|
||||
}
|
||||
|
|
@ -116,6 +118,7 @@ public class WorkflowDefinitionMapper
|
|||
workflowDefinition.Outputs,
|
||||
workflowDefinition.Outcomes,
|
||||
workflowDefinition.CustomProperties,
|
||||
workflowDefinition.PropertyBag,
|
||||
workflowDefinition.IsReadonly,
|
||||
workflowDefinition.IsSystem,
|
||||
workflowDefinition.IsLatest,
|
||||
|
|
@ -147,6 +150,7 @@ public class WorkflowDefinitionMapper
|
|||
workflow.Outputs,
|
||||
workflow.Outcomes,
|
||||
workflow.CustomProperties,
|
||||
workflow.PropertyBag,
|
||||
workflow.IsReadonly,
|
||||
workflow.IsSystem,
|
||||
workflow.Publication.IsLatest,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
using Elsa.Common.Models;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Models;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Workflows.Management.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a serializable workflow definition.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public record WorkflowDefinitionModel(
|
||||
string Id,
|
||||
|
|
@ -20,7 +19,9 @@ public record WorkflowDefinitionModel(
|
|||
ICollection<InputDefinition>? Inputs,
|
||||
ICollection<OutputDefinition>? Outputs,
|
||||
ICollection<string>? Outcomes,
|
||||
[property: Obsolete("Use PropertyBag instead")]
|
||||
IDictionary<string, object>? CustomProperties,
|
||||
PropertyBag? PropertyBag,
|
||||
bool IsReadonly,
|
||||
bool IsSystem,
|
||||
bool IsLatest,
|
||||
|
|
@ -44,7 +45,8 @@ public record WorkflowDefinitionModel(
|
|||
default!,
|
||||
default!,
|
||||
default!,
|
||||
default!,
|
||||
default,
|
||||
default,
|
||||
default!,
|
||||
default!,
|
||||
default!,
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public class WorkflowDefinitionManager : IWorkflowDefinitionManager
|
|||
{
|
||||
if (definitionToDelete.IsPublished)
|
||||
{
|
||||
throw new Exception("Published version cannot be deleted before retracting it");
|
||||
await _workflowPublisher.RetractAsync(definitionToDelete, cancellationToken);
|
||||
}
|
||||
|
||||
await _notificationSender.SendAsync(new WorkflowDefinitionVersionDeleting(definitionToDelete), cancellationToken);
|
||||
|
|
|
|||
Loading…
Reference in a new issue