Reference variable with Id instead of Name to prevent issues with updating name (#3814)

* Implemented Id property for variable for memory reference

* Removed debugger

* Fixed the issue with memory block reference of variable

* Fixed infinite recursive calling issue

* Handle variables with ID already containing

* Update JS variable getters based on declared variables

* Prevent Export from including composite roots

* Update JS evaluator with support for reading and writing variables in scope

* Fix polymorphic converter with support for arrays

---------

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
This commit is contained in:
gurkanguran 2023-03-22 22:50:26 +01:00 committed by GitHub
parent 8289b03f79
commit e4789cf85b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 206 additions and 132 deletions

View file

@ -248,6 +248,7 @@ export namespace Components {
}
interface ElsaVariablePickerInput {
"inputContext": ActivityInputContext;
"workflowDefinition": WorkflowDefinition;
}
interface ElsaVariablesEditor {
"variables"?: Array<Variable>;
@ -1134,6 +1135,7 @@ declare namespace LocalJSX {
}
interface ElsaVariablePickerInput {
"inputContext"?: ActivityInputContext;
"workflowDefinition"?: WorkflowDefinition;
}
interface ElsaVariablesEditor {
"onVariablesChanged"?: (event: ElsaVariablesEditorCustomEvent<Array<Variable>>) => void;

View file

@ -4,13 +4,15 @@ import {ActivityInputContext} from "../../services/activity-input-driver";
import {getPropertyValue} from "../../utils";
import {FormEntry} from "../shared/forms/form-entry";
import WorkflowDefinitionTunnel from "../../modules/workflow-definitions/state";
import {WorkflowDefinition} from "../../modules/workflow-definitions/models/entities";
@Component({
tag: 'elsa-variable-picker-input',
shadow: false
})
export class VariablePickerInput {
@Prop() public inputContext: ActivityInputContext;
@Prop() inputContext: ActivityInputContext;
@Prop() workflowDefinition: WorkflowDefinition; // Injected by WorkflowDefinitionTunnel
public render() {
const inputContext = this.inputContext;
@ -36,9 +38,9 @@ export class VariablePickerInput {
<select id={fieldId} name={fieldName} onChange={e => this.onChange(e)}>
{variables.map((variable: Variable) => {
const variableName = variable?.name;
const variableId = variable?.id;
const isSelected = variableName == currentValue?.name;
const json = variable ? JSON.stringify(variable) : '';
return <option value={variableName} selected={isSelected} data-variable={json}>{variableName}</option>;
return <option value={variableId} selected={isSelected}>{variableName}</option>;
})}
</select>
</FormEntry>
@ -49,8 +51,10 @@ export class VariablePickerInput {
private onChange = (e: Event) => {
const inputElement = e.target as HTMLSelectElement;
const json = inputElement.selectedOptions[0].dataset.variable;
const variable = inputElement.selectedIndex <= 0 ? null : JSON.parse(json);
const variableId = inputElement.value;
const variable = this.workflowDefinition.variables.find(x => x.id == variableId);
this.inputContext.inputChanged(variable, SyntaxNames.Literal);
}
}
WorkflowDefinitionTunnel.injectProps(VariablePickerInput, ['workflowDefinition']);

View file

@ -36,6 +36,7 @@ export interface Workflow extends Activity {
}
export interface Variable {
id: string;
name: string;
typeName: string;
isArray: boolean;

View file

@ -278,8 +278,7 @@ export class ActivityPropertiesEditor {
const activity = this.activity;
const propertyName = outputDescriptor.name;
const camelCasePropertyName = camelCase(propertyName);
const outputTargetValuePair = outputTargetValue.split(':');
const kind = outputTargetValuePair[0];
const outputTargetValuePair = outputTargetValue.split('::');
const outputTargetId = outputTargetValuePair[1];
const property: ActivityOutput = {
@ -303,9 +302,7 @@ export class ActivityPropertiesEditor {
newId: activityId,
originalId: activityId,
activity,
activityDescriptor,
// propertyName: propertyName,
// propertyDescriptor: propertyDescriptor
activityDescriptor
});
}
@ -356,8 +353,9 @@ export class ActivityPropertiesEditor {
const key = `${activityId}`;
const outputTargetOptions: Array<any> = [null];
if (variables.length > 0)
outputTargetOptions.push({label: 'Variables', items: [...variables.map(x => ({value: x.name, name: x.name}))], kind: 'variable'});
if (variables.length > 0) {
outputTargetOptions.push({label: 'Variables', items: [...variables.map(x => ({value: x.id, name: x.name}))], kind: 'variable'});
}
if (outputDefinitions.length > 0)
outputTargetOptions.push({label: 'Outputs', items: [...outputDefinitions.map(x => ({value: x.name, name: x.name}))], kind: 'output'});
@ -388,7 +386,7 @@ export class ActivityPropertiesEditor {
<optgroup label={outputTarget.label}>
{items.map(item => {
const isSelected = propertyValue?.memoryReference?.id == item.value;
return <option value={`${item.kind}:${item.value}`} selected={isSelected}>{item.name}</option>;
return <option value={`${outputTarget.kind}::${item.value}`} selected={isSelected}>{item.name}</option>;
})}
</optgroup>);
})}
@ -413,7 +411,6 @@ export class ActivityPropertiesEditor {
<CheckboxFormEntry fieldId="RunAsynchronously" label="Execute asynchronously" hint="When enabled, this activity will execute asynchronously and suspend workflow execution until the activity is finished.">
<input type="checkbox" name="RunAsynchronously" id="RunAsynchronously" value={"true"} checked={runAsynchronously} onChange={e => this.onRunAsynchronouslyChanged(e)}/>
</CheckboxFormEntry>
</div>
};
}

View file

@ -22,7 +22,7 @@ export class VariableEditorDialogContent {
}
render() {
const variable: Variable = this.variable ?? {name: '', typeName: 'Object', isArray: false};
const variable: Variable = this.variable ?? {id: '', name: '', typeName: 'Object', isArray: false};
const variableTypeName = variable.typeName;
const availableTypes: Array<VariableDescriptor> = descriptorsStore.variableDescriptors;
const groupedVariableTypes = groupBy(availableTypes, x => x.category);

View file

@ -109,7 +109,7 @@ export class VariablesEditor {
private onAddVariableClick = async () => {
const newVariableName = this.generateNewVariableName();
const variable: Variable = {name: newVariableName, typeName: 'Object', value: null, isArray: false};
const variable: Variable = {id: '', name: newVariableName, typeName: 'Object', value: null, isArray: false};
this.modalDialogInstance = this.modalDialogService.show(() => <elsa-variable-editor-dialog-content variable={variable}/>, {actions: [this.saveAction]})
};

View file

@ -59,6 +59,16 @@ public class ExpressionExecutionContext
/// </summary>
public MemoryBlock GetBlock(MemoryBlockReference blockReference) => GetBlockInternal(blockReference) ?? throw new Exception($"Failed to retrieve memory block with reference {blockReference.Id}");
/// <summary>
/// Returns the <see cref="MemoryBlock"/> pointed to by the specified memory block reference.
/// </summary>
public bool TryGetBlock(MemoryBlockReference blockReference, out MemoryBlock block)
{
var b = GetBlockInternal(blockReference);
block = b ?? default!;
return b != null;
}
/// <summary>
/// Returns the value of the memory block pointed to by the specified memory block reference.
/// </summary>
@ -69,6 +79,21 @@ public class ExpressionExecutionContext
/// </summary>
public object? Get(MemoryBlockReference blockReference) => GetBlock(blockReference).Value;
/// <summary>
/// Returns the value of the memory block pointed to by the specified memory block reference.
/// </summary>
public bool TryGet(MemoryBlockReference blockReference, out object? value)
{
if (TryGetBlock(blockReference, out var block))
{
value = block.Value;
return true;
}
value = default;
return false;
}
/// <summary>
/// Returns the value of the memory block pointed to by the specified memory block reference.
/// </summary>

View file

@ -54,6 +54,11 @@ public abstract class MemoryBlockReference
/// </summary>
public T? Get<T>(ExpressionExecutionContext context) => Get(context).ConvertTo<T>();
/// <summary>
/// Returns the value of the memory block.
/// </summary>
public bool TryGet(ExpressionExecutionContext context, out object? value) => context.TryGet(this, out value);
/// <summary>
/// Sets the value of the memory block.
/// </summary>

View file

@ -7,6 +7,7 @@ using Elsa.JavaScript.Contracts;
using Elsa.JavaScript.Notifications;
using Elsa.JavaScript.Options;
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Core.Models;
using Humanizer;
using Jint;
using Microsoft.Extensions.Options;
@ -60,8 +61,8 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
engine.SetValue("setCorrelationId", (Action<string?>)(value => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId = value));
engine.SetValue("getCorrelationId", (Func<string?>)(() => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId));
engine.SetValue("setCorrelationId", (Action<string?>)(value => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId = value));
engine.SetValue("setVariable", (Action<string, object>)((name, value) => context.SetVariable(name, value)));
engine.SetValue("getVariable", (Func<string, object?>)(name => context.GetVariable(name)));
engine.SetValue("setVariable", (Action<string, object>)((id, value) => context.SetVariable(id, value)));
engine.SetValue("getVariable", (Func<string, object?>)(id => context.GetVariable(id)));
engine.SetValue("getInput", (Func<string, object?>)(name => context.GetWorkflowExecutionContext().Input.GetValue(name)));
// Create variable & input setters and getters for each variable.
@ -89,16 +90,58 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
private static void CreateMemoryBlockAccessors(Engine engine, ExpressionExecutionContext context)
{
var variablesDictionary = context.ReadAndFlattenMemoryBlocks();
var variableNames = GetVariableNamesInScope(context).ToList();
foreach (var variable in variablesDictionary)
foreach (var variableName in variableNames)
{
var pascalName = variable.Key.Pascalize();
engine.SetValue($"get{pascalName}", (Func<object?>)(() => context.GetVariable(variable.Key)));
engine.SetValue($"set{pascalName}", (Action<object?>)(value => context.SetVariable(variable.Key, value)));
var pascalName = variableName.Pascalize();
engine.SetValue($"get{pascalName}", (Func<object?>)(() => GetVariableInScope(context, variableName)));
engine.SetValue($"set{pascalName}", (Action<object?>)(value => SetVariableInScope(context, variableName, value)));
}
}
private static IEnumerable<string> GetVariableNamesInScope(ExpressionExecutionContext context) => EnumerateVariablesInScope(context).Select(x => x.Name).Distinct();
private static object GetVariableInScope(ExpressionExecutionContext context, string variableName)
{
var q = from variable in EnumerateVariablesInScope(context)
where variable.Name == variableName
where variable.TryGet(context, out _)
select variable.Get(context);
var value = q.FirstOrDefault();
return value!;
}
private static void SetVariableInScope(ExpressionExecutionContext context, string variableName, object? value)
{
var q = from v in EnumerateVariablesInScope(context)
where v.Name == variableName
where v.TryGet(context, out _)
select v;
var variable = q.FirstOrDefault();
variable?.Set(context, value);
}
private static IEnumerable<Variable> EnumerateVariablesInScope(ExpressionExecutionContext context)
{
var currentScope = context;
while (currentScope != null)
{
if (!currentScope.TryGetActivityExecutionContext(out var activityExecutionContext))
break;
var variables = activityExecutionContext.Variables;
foreach (var variable in variables)
yield return variable;
currentScope = currentScope.ParentContext;
}
}
private static object ExecuteExpressionAndGetResult(Engine engine, string expression)
{
var result = engine.Evaluate(expression);

View file

@ -2,7 +2,9 @@ using System.Text.Json;
using Elsa.Abstractions;
using Elsa.Common.Models;
using Elsa.Workflows.Api.Models;
using Elsa.Workflows.Core.Contracts;
using Elsa.Workflows.Core.Serialization;
using Elsa.Workflows.Core.Serialization.Converters;
using Elsa.Workflows.Management.Contracts;
using Elsa.Workflows.Management.Mappers;
using Elsa.Workflows.Runtime.Contracts;
@ -43,7 +45,6 @@ public class Export : ElsaEndpoint<Request>
/// <inheritdoc />
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
{
var serializerOptions = _serializerOptionsProvider.CreateApiOptions();
var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest;
var definition = (await _store.FindManyAsync(new WorkflowDefinitionFilter { DefinitionId = request.DefinitionId, VersionOptions = versionOptions }, cancellationToken: cancellationToken)).FirstOrDefault();
@ -73,6 +74,11 @@ public class Export : ElsaEndpoint<Request>
definition.IsPublished,
workflow.Root);
var serializerOptions = _serializerOptionsProvider.CreateApiOptions();
// Exclude composite activities from being serialized.
serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory());
var binaryJson = JsonSerializer.SerializeToUtf8Bytes(model, serializerOptions);
var hasWorkflowName = !string.IsNullOrWhiteSpace(definition.Name);
var workflowName = hasWorkflowName ? definition.Name!.Trim() : definition.DefinitionId;

View file

@ -30,7 +30,7 @@ internal class Get : ElsaEndpoint<Request>
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
{
var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest;
var filter = new WorkflowDefinitionFilter
{
DefinitionId = request.DefinitionId,
@ -49,10 +49,10 @@ internal class Get : ElsaEndpoint<Request>
var mapper = new WorkflowDefinitionMapper();
var response = await mapper.FromEntityAsync(definition, cancellationToken);
var serializerOptions = _serializerOptionsProvider.CreateApiOptions();
// If the root of composite activities is not requested, exclude them from being serialized.
if(!request.IncludeCompositeRoot)
serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory<IActivity>());
if (!request.IncludeCompositeRoot)
serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory());
await HttpContext.Response.WriteAsJsonAsync(response, serializerOptions, cancellationToken);
}

View file

@ -64,7 +64,12 @@ public class WorkflowBuilder : IWorkflowBuilder
/// <inheritdoc />
public Variable<T> WithVariable<T>(string name, T value)
{
var variable = value != null ? new Variable<T>(name, value) : new Variable<T>(name);
var variable = new Variable<T>
{
Name = name,
Value = value
};
Variables.Add(variable);
return variable;
}

View file

@ -99,7 +99,7 @@ public static class ActivityExecutionContextExtensions
public static Variable SetVariable(this ActivityExecutionContext context, string name, object? value, Type? storageDriverType = default, Action<MemoryBlock>? configure = default) =>
context.ExpressionExecutionContext.SetVariable(name, value, storageDriverType, configure);
public static T? GetVariable<T>(this ActivityExecutionContext context, string name) => context.ExpressionExecutionContext.GetVariable<T?>(name);
public static T? GetVariable<T>(this ActivityExecutionContext context, string id) => context.ExpressionExecutionContext.GetVariable<T?>(id);
/// <summary>
/// Returns a dictionary of variable keys and their values across scopes.

View file

@ -29,6 +29,8 @@ public static class ExpressionExecutionContextExtensions
public static WorkflowExecutionContext GetWorkflowExecutionContext(this ExpressionExecutionContext context) => (WorkflowExecutionContext)context.TransientProperties[WorkflowExecutionContextKey];
public static ActivityExecutionContext GetActivityExecutionContext(this ExpressionExecutionContext context) => (ActivityExecutionContext)context.TransientProperties[ActivityExecutionContextKey];
public static bool TryGetActivityExecutionContext(this ExpressionExecutionContext context, out ActivityExecutionContext activityExecutionContext) => context.TransientProperties.TryGetValue(ActivityExecutionContextKey, out activityExecutionContext!);
public static IDictionary<string, object> GetInput(this ExpressionExecutionContext context) => (IDictionary<string, object>)context.TransientProperties[InputKey];
public static T? GetInput<T>(this ExpressionExecutionContext context, string key) => context.GetInput(key).ConvertTo<T>();
public static object? GetInput(this ExpressionExecutionContext context, string key) => context.GetInput().TryGetValue(key, out var value) ? value : default;
@ -36,15 +38,13 @@ public static class ExpressionExecutionContextExtensions
public static T? Get<T>(this ExpressionExecutionContext context, Input<T>? input) => input != null ? context.GetBlock(input.MemoryBlockReference).Value.ConvertTo<T>() : default;
public static T? Get<T>(this ExpressionExecutionContext context, Output output) => context.GetBlock(output.MemoryBlockReference).Value.ConvertTo<T>();
public static object? Get(this ExpressionExecutionContext context, Output output) => context.GetBlock(output.MemoryBlockReference).Value;
public static T? GetVariable<T>(this ExpressionExecutionContext context, string name) => (T?)context.GetVariable(name);
public static T? GetVariable<T>(this ExpressionExecutionContext context) => context.GetVariable(typeof(T).Name).ConvertTo<T>();
public static object? GetVariable(this ExpressionExecutionContext context, string name) => new Variable(name).Get(context);
public static Variable SetVariable<T>(this ExpressionExecutionContext context, T? value, Type? storageDriverType = default) => context.SetVariable(typeof(T).Name, value, storageDriverType);
public static Variable SetVariable<T>(this ExpressionExecutionContext context, string name, T? value, Type? storageDriverType = default) => context.SetVariable(name, (object?)value, storageDriverType);
public static T? GetVariable<T>(this ExpressionExecutionContext context, string id) => (T?)context.GetVariable(id);
public static object? GetVariable(this ExpressionExecutionContext context, string id) => new Variable(id).Get(context);
public static Variable SetVariable<T>(this ExpressionExecutionContext context, string id, T? value, Type? storageDriverType = default) => context.SetVariable(id, (object?)value, storageDriverType, default);
public static Variable SetVariable(this ExpressionExecutionContext context, string name, object? value, Type? storageDriverType, Action<MemoryBlock>? configure = default)
public static Variable SetVariable(this ExpressionExecutionContext context, string id, object? value, Type? storageDriverType, Action<MemoryBlock>? configure = default)
{
var variable = new Variable(name, value)
var variable = new Variable(id, value)
{
StorageDriverType = storageDriverType
};

View file

@ -95,50 +95,4 @@ public static class WorkflowExecutionContextExtensions
workflowExecutionContext.Scheduler.Schedule(workItem);
workflowExecutionContext.AddCompletionCallback(owner, activityNode, completionCallback);
}
/// <summary>
/// Gets the specified workflow variable by name.
/// </summary>
public static T? GetVariable<T>(this WorkflowExecutionContext workflowExecutionContext, string name) => (T?)workflowExecutionContext.GetVariable(name);
/// <summary>
/// Gets the specified workflow variable by name, where the name is implied by the type name.
/// </summary>
public static T? GetVariable<T>(this WorkflowExecutionContext workflowExecutionContext) => (T?)workflowExecutionContext.GetVariable(typeof(T).Name);
/// <summary>
/// Gets the specified workflow variable by name.
/// </summary>
public static object? GetVariable(this WorkflowExecutionContext workflowExecutionContext, string name)
{
var variable = workflowExecutionContext.Workflow.Variables.FirstOrDefault(x => x.Name == name);
return variable?.Get(workflowExecutionContext.MemoryRegister);
}
/// <summary>
/// Sets the specified workflow variable by name, where the name is implied by the type name.
/// </summary>
public static Variable SetVariable<T>(this WorkflowExecutionContext workflowExecutionContext, T? value) => workflowExecutionContext.SetVariable(typeof(T).Name, value);
/// <summary>
/// Sets the specified workflow variable by name.
/// </summary>
public static Variable SetVariable<T>(this WorkflowExecutionContext workflowExecutionContext, string name, T? value) => workflowExecutionContext.SetVariable(name, (object?)value);
/// <summary>
/// Sets the specified workflow variable by name.
/// </summary>
public static Variable SetVariable(this WorkflowExecutionContext workflowExecutionContext, string name, object? value)
{
var variable = workflowExecutionContext.Workflow.Variables.FirstOrDefault(x => x.Name == name);
if (variable == null)
{
variable = new Variable(name, value);
workflowExecutionContext.Workflow.Variables.Add(variable);
}
variable.Set(workflowExecutionContext.MemoryRegister, value);
return variable;
}
}

View file

@ -8,23 +8,20 @@ public class Variable : MemoryBlockReference
{
public Variable()
{
Id = Guid.NewGuid().ToString("N");
}
public Variable(string name)
public Variable(string id)
{
Id = name;
Id = id;
}
public Variable(string name, object? value = default) : this(name)
public Variable(string id, object? value = default) : this(id)
{
Value = value;
}
public string Name
{
get => Id;
set => Id = value;
}
public string Name { get; set; }
public object? Value { get; set; }
@ -44,14 +41,6 @@ public class Variable<T> : Variable
{
}
public Variable(string name) : base(name)
{
}
public Variable(string name, T value) : base(name, value ?? default)
{
}
public Variable(T value)
{
Value = value;

View file

@ -2,22 +2,23 @@ using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Workflows.Core.Attributes;
using Elsa.Workflows.Core.Contracts;
namespace Elsa.Workflows.Core.Serialization.Converters;
/// <summary>
/// Ignores properties with the <see cref="JsonIgnoreCompositeRootAttribute"/> attribute.
/// </summary>
public class JsonIgnoreCompositeRootConverter<T> : JsonConverter<T>
public class JsonIgnoreCompositeRootConverter : JsonConverter<IActivity>
{
/// <inheritdoc />
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
public override IActivity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
public override void Write(Utf8JsonWriter writer, IActivity value, JsonSerializerOptions options)
{
writer.WriteStartObject();
@ -39,19 +40,4 @@ public class JsonIgnoreCompositeRootConverter<T> : JsonConverter<T>
writer.WriteEndObject();
}
}
/// <summary>
/// A <see cref="JsonConverterFactory"/> that creates <see cref="JsonIgnoreCompositeRootConverter{T}"/> instances.
/// </summary>
public class JsonIgnoreCompositeRootConverterFactory<T> : JsonConverterFactory
{
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeof(T).IsAssignableFrom(typeToConvert);
/// <inheritdoc />
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
return new JsonIgnoreCompositeRootConverter<T>();
}
}

View file

@ -0,0 +1,20 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Workflows.Core.Contracts;
namespace Elsa.Workflows.Core.Serialization.Converters;
/// <summary>
/// A <see cref="JsonConverterFactory"/> that creates <see cref="JsonIgnoreCompositeRootConverter"/> instances.
/// </summary>
public class JsonIgnoreCompositeRootConverterFactory : JsonConverterFactory
{
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeof(IActivity).IsAssignableFrom(typeToConvert);
/// <inheritdoc />
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
return new JsonIgnoreCompositeRootConverter();
}
}

View file

@ -47,12 +47,23 @@ public class PolymorphicObjectConverter : JsonConverter<object>
if(jsonObject.TryGetProperty(ItemsPropertyName, out var items))
{
var array = JsonSerializer.Deserialize(items.GetRawText(), targetType, newOptions)!;
var elementType = targetType.GetElementType()!;
var array = Array.CreateInstance(elementType, items.GetArrayLength());
var index = 0;
newOptions.Converters.Add(this);
foreach (var element in items.EnumerateArray())
{
var deserializedElement = JsonSerializer.Deserialize(element.GetRawText(), elementType, newOptions)!;
array.SetValue(deserializedElement, index++);
}
return array;
}
var json = jsonObject.GetRawText();
return JsonSerializer.Deserialize(json, targetType, newOptions)!;
var result = JsonSerializer.Deserialize(json, targetType, newOptions)!;
return result;
}
private static object ReadPrimitive(ref Utf8JsonReader reader, JsonSerializerOptions options)

View file

@ -53,6 +53,7 @@ public class VariableConverter : JsonConverter<Variable>
var variableGenericType = typeof(Variable<>).MakeGenericType(type);
var variable = (Variable)Activator.CreateInstance(variableGenericType)!;
variable.Id = source.Id ?? Guid.NewGuid().ToString("N"); // Temporarily assign a new ID if the source doesn't have one.
variable.Name = source.Name;
source.Value.TryConvertTo(type)
@ -73,7 +74,7 @@ public class VariableConverter : JsonConverter<Variable>
var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName();
var serializedValue = value.Format();
return new VariableModel(source.Name, valueTypeAlias, serializedValue, storageDriverTypeName);
return new VariableModel(source.Id, source.Name, valueTypeAlias, serializedValue, storageDriverTypeName);
}
private class VariableModel
@ -83,14 +84,16 @@ public class VariableConverter : JsonConverter<Variable>
{
}
public VariableModel(string name, string typeName, string? value, string? storageDriverTypeName)
public VariableModel(string id, string name, string typeName, string? value, string? storageDriverTypeName)
{
Id = id;
Name = name;
TypeName = typeName;
Value = value;
StorageDriverTypeName = storageDriverTypeName;
}
public string Id { get; set; } = default!;
public string Name { get; set; } = default!;
public string TypeName { get; set; } = default!;
public string? Value { get; set; }

View file

@ -23,7 +23,7 @@ public class SerializerOptionsProvider
public JsonSerializerOptions CreatePersistenceOptions(ReferenceHandler? referenceHandler = default)
{
var options = CreateDefaultOptions(referenceHandler ?? ReferenceHandler.IgnoreCycles);
options.Converters.Add(Create<JsonIgnoreCompositeRootConverterFactory<IActivity>>());
options.Converters.Add(Create<JsonIgnoreCompositeRootConverterFactory>());
return options;
}

View file

@ -87,7 +87,7 @@ public class IdentityGraphService : IIdentityGraphService
var seed = 0;
foreach (var variable in variables)
variable.Id = variable.Name != null! ? variable.Name : $"{activity.Id}:variable-{++seed}";
variable.Id = variable.Id != null! ? variable.Id : $"{activity.Id}:variable-{++seed}";
}
private string CreateId(ActivityNode activityNode, IDictionary<string, int> identityCounters, ICollection<ActivityNode> allNodes)

View file

@ -42,8 +42,10 @@ public class WorkflowDefinitionActivity : Composite, IInitializable
var evaluatedExpression = input != null ? context.Get(input.MemoryBlockReference()) : default;
// Create a local scope variable for each input property.
var variable = new Variable(inputDescriptor.Name)
var variable = new Variable
{
Id = inputDescriptor.Name,
Name = inputDescriptor.Name,
StorageDriverType = inputDescriptor.StorageDriverType
};
@ -62,6 +64,7 @@ public class WorkflowDefinitionActivity : Composite, IInitializable
// Create a local scope variable for each input property.
var variable = new Variable(inputDescriptor.Name)
{
Name = inputDescriptor.Name,
StorageDriverType = inputDescriptor.StorageDriverType
};

View file

@ -34,6 +34,9 @@ public class VariableDefinitionMapper
var variableGenericType = typeof(Variable<>).MakeGenericType(valueType);
var variable = (Variable)Activator.CreateInstance(variableGenericType)!;
if(!string.IsNullOrEmpty(source.Id))
variable.Id = source.Id;
variable.Name = source.Name;
variable.Value = source.Value.ConvertTo(valueType);
variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : default;
@ -66,7 +69,7 @@ public class VariableDefinitionMapper
var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName();
var serializedValue = value.Format();
return new VariableDefinition(source.Name, valueTypeAlias, isArray, serializedValue, storageDriverTypeName);
return new VariableDefinition(source.Id, source.Name, valueTypeAlias, isArray, serializedValue, storageDriverTypeName);
}
/// <summary>

View file

@ -3,4 +3,4 @@ namespace Elsa.Workflows.Management.Models;
/// <summary>
/// Stores information about a workflow variable.
/// </summary>
public record VariableDefinition(string Name, string TypeName, bool IsArray, string? Value, string? StorageDriverTypeName);
public record VariableDefinition(string Id, string Name, string TypeName, bool IsArray, string? Value, string? StorageDriverTypeName);

View file

@ -108,7 +108,12 @@ public class ActivityJsonConverter : JsonConverter<IActivity>
if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement))
continue;
var variable = new Variable(memoryReferenceIdElement.GetString()!);
var variable = new Variable
{
Id = memoryReferenceIdElement.GetString()!
};
variable.Name = variable.Id;
var output = Activator.CreateInstance(wrappedType, variable)!;
activity.SyntheticProperties[outputName] = output!;

View file

@ -34,7 +34,12 @@ public class OutputJsonConverter<T> : JsonConverter<Output<T>?>
if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement))
return default;
var variable = new Variable(memoryReferenceIdElement.GetString()!);
var variable = new Variable
{
Id = memoryReferenceIdElement.GetString()!
};
variable.Name = variable.Id;
return (Output<T>)Activator.CreateInstance(typeof(Output<T>), variable)!;
}

View file

@ -14,7 +14,7 @@ public class AskName : Composite<string>
Root = new Sequence
{
Variables = new List<Variable> { _name },
Activities = new List<IActivity>()
Activities = new List<IActivity>
{
new WriteLine(context => Prompt.Get(context)),
new ReadLine(_name)

View file

@ -10,7 +10,11 @@ public class BreakWhileForkWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var currentValue = new Variable<int?>("CurrentValue", 0);
var currentValue = new Variable<int?>
{
Name = "CurrentValue",
Value = 0
};
workflow.Root = new Sequence
{

View file

@ -17,7 +17,10 @@ class ForEachWorkflow : WorkflowBase
protected override void Build(IWorkflowBuilder workflow)
{
var currentItem = new Variable<string>("CurrentItem");
var currentItem = new Variable<string>
{
Name = "CurrentItem"
};
workflow.Root = new Sequence
{