Refactor API client to handle activities as JsonObjects

This commit is contained in:
Sipke Schoorstra 2023-07-16 15:08:13 +02:00
parent f698028488
commit ae4dce9119
23 changed files with 416 additions and 485 deletions

View file

@ -1,30 +0,0 @@
using Elsa.Api.Client.Activities;
using Elsa.Api.Client.Contracts;
namespace Elsa.Api.Client.Abstractions;
/// <summary>
/// Provides a base class for <see cref="IActivityTypeResolver"/> implementations.
/// </summary>
public abstract class ActivityTypeResolverBase : IActivityTypeResolver
{
/// <inheritdoc />
public virtual double Priority => 0;
/// <summary>
/// Returns a value indicating whether this provider supports the specified activity type.
/// </summary>
/// <param name="context">The <see cref="ActivityTypeResolverContext"/>.</param>
/// <returns>A value indicating whether this provider supports the specified activity type.</returns>
protected virtual bool GetSupportsType(ActivityTypeResolverContext context) => false;
/// <summary>
/// Resolves the activity type.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
protected virtual Type ResolveType(ActivityTypeResolverContext context) => typeof(Activity);
bool IActivityTypeResolver.GetSupportsType(ActivityTypeResolverContext context) => GetSupportsType(context);
Type IActivityTypeResolver.ResolveType(ActivityTypeResolverContext context) => ResolveType(context);
}

View file

@ -1,63 +0,0 @@
using Elsa.Api.Client.Extensions;
namespace Elsa.Api.Client.Activities;
/// <summary>
/// Represents an activity.
/// </summary>
public class Activity : Dictionary<string, object>
{
/// <summary>
/// Gets or sets the ID of this activity.
/// </summary>
public string Id
{
get => this.TryGetValue<string>("id")!;
set => this["id"] = value;
}
/// <summary>
/// Gets or sets the name of this activity.
/// </summary>
public string? Name
{
get => this.TryGetValue<string>("name")!;
set => this["name"] = value!;
}
/// <summary>
/// Gets or sets the type of this activity.
/// </summary>
public string Type
{
get => this.TryGetValue<string>("type")!;
set => this["type"] = value;
}
/// <summary>
/// Gets or sets the version of this activity.
/// </summary>
public int Version
{
get => this.TryGetValue<int>("version");
set => this["version"] = value;
}
/// <summary>
/// Gets or sets the metadata of this activity.
/// </summary>
public IDictionary<string, object> Metadata
{
get => this.TryGetValue<IDictionary<string, object>>("metadata", () => new Dictionary<string, object>())!;
set => this["metadata"] = value;
}
/// <summary>
/// Gets or sets custom properties of this activity.
/// </summary>
public IDictionary<string, object> CustomProperties
{
get => this.TryGetValue<IDictionary<string, object>>("customProperties", () => new Dictionary<string, object>())!;
set => this["customProperties"] = value;
}
}

View file

@ -1,19 +0,0 @@
using Elsa.Api.Client.Extensions;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Activities;
/// <summary>
/// Represents a flow switch activity.
/// </summary>
public class FlowSwitch : Activity
{
/// <summary>
/// Gets or sets the cases.
/// </summary>
public ICollection<Case>? Cases
{
get => this.TryGetValue<ICollection<Case>>("cases");
set => this["cases"] = value!;
}
}

View file

@ -1,19 +0,0 @@
using Elsa.Api.Client.Extensions;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Activities;
/// <summary>
/// Represents a flowchart activity.
/// </summary>
public class Flowchart : Container
{
/// <summary>
/// Gets or sets the connections between activities.
/// </summary>
public ICollection<Connection> Connections
{
get => this.TryGetValue<ICollection<Connection>>("connections", () => new List<Connection>())!;
set => this["connections"] = value;
}
}

View file

@ -1,9 +0,0 @@
namespace Elsa.Api.Client.Activities;
/// <summary>
/// Represents a trigger.
/// </summary>
public class Trigger : Activity
{
}

View file

@ -1,20 +0,0 @@
using Elsa.Api.Client.Abstractions;
using Elsa.Api.Client.Activities;
using Elsa.Api.Client.Contracts;
namespace Elsa.Api.Client.ActivityProviders;
/// <summary>
/// Provides a default implementation of <see cref="IActivityTypeResolver"/> that always resolves the <see cref="Activity"/> type.
/// </summary>
public class DefaultActivityTypeResolver : ActivityTypeResolverBase
{
/// <inheritdoc />
public override double Priority => -1;
/// <inheritdoc />
protected override bool GetSupportsType(ActivityTypeResolverContext context) => true;
/// <inheritdoc />
protected override Type ResolveType(ActivityTypeResolverContext context) => typeof(Activity);
}

View file

@ -1,17 +0,0 @@
using Elsa.Api.Client.Abstractions;
using Elsa.Api.Client.Activities;
using Elsa.Api.Client.Contracts;
namespace Elsa.Api.Client.ActivityProviders;
/// <summary>
/// Constructs a <see cref="Flowchart"/> activity.
/// </summary>
public class FlowSwitchTypeResolver : ActivityTypeResolverBase
{
/// <inheritdoc />
protected override bool GetSupportsType(ActivityTypeResolverContext context) => context.ActivityTypeName == "Elsa.FlowSwitch";
/// <inheritdoc />
protected override Type ResolveType(ActivityTypeResolverContext context) => typeof(FlowSwitch);
}

View file

@ -1,17 +0,0 @@
using Elsa.Api.Client.Abstractions;
using Elsa.Api.Client.Activities;
using Elsa.Api.Client.Contracts;
namespace Elsa.Api.Client.ActivityProviders;
/// <summary>
/// Constructs a <see cref="Flowchart"/> activity.
/// </summary>
public class FlowchartTypeResolver : ActivityTypeResolverBase
{
/// <inheritdoc />
protected override bool GetSupportsType(ActivityTypeResolverContext context) => context.ActivityTypeName == "Elsa.Flowchart";
/// <inheritdoc />
protected override Type ResolveType(ActivityTypeResolverContext context) => typeof(Flowchart);
}

View file

@ -1,34 +0,0 @@
using Elsa.Api.Client.Activities;
namespace Elsa.Api.Client.Contracts;
/// <summary>
/// Resolves an activity type.
/// </summary>
public interface IActivityTypeResolver
{
/// <summary>
/// Gets the priority of this activity provider. Activity providers with a higher priority are considered first.
/// </summary>
double Priority { get; }
/// <summary>
/// Returns a value indicating whether this activity provider supports the specified activity type.
/// </summary>
/// <param name="context">The <see cref="ActivityTypeResolverContext"/>.</param>
/// <returns>A value indicating whether this activity provider supports the specified activity type.</returns>
bool GetSupportsType(ActivityTypeResolverContext context);
/// <summary>
/// Creates an instance of <see cref="Activity"/> for the specified activity type.
/// </summary>
/// <param name="context">The <see cref="ActivityTypeResolverContext"/>.</param>
/// <returns>The resolved type.</returns>
Type ResolveType(ActivityTypeResolverContext context);
}
/// <summary>
/// Provides context for an <see cref="IActivityTypeResolver"/>.
/// </summary>
/// <param name="ActivityTypeName">The activity type.</param>
public record ActivityTypeResolverContext(string ActivityTypeName);

View file

@ -1,14 +0,0 @@
namespace Elsa.Api.Client.Contracts;
/// <summary>
/// Resolves the .NET type of an activity type name.
/// </summary>
public interface IActivityTypeService
{
/// <summary>
/// Resolves the .NET type of the specified activity type name.
/// </summary>
/// <param name="activityTypeName">The activity type name.</param>
/// <returns>Returns the .NET type of the specified activity type name.</returns>
Type ResolveType(string activityTypeName);
}

View file

@ -1,54 +0,0 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Api.Client.Activities;
using Elsa.Api.Client.Contracts;
namespace Elsa.Api.Client.Converters;
/// <summary>
/// A JSON converter that serializes <see cref="Activity"/>.
/// </summary>
public class ActivityJsonConverter : JsonConverter<Activity>
{
private readonly IActivityTypeService _activityTypeService;
/// <inheritdoc />
public ActivityJsonConverter(IActivityTypeService activityTypeService)
{
_activityTypeService = activityTypeService;
}
/// <inheritdoc />
public override Activity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (!JsonDocument.TryParseValue(ref reader, out var doc))
throw new JsonException("Failed to parse JsonDocument");
var activityRoot = doc.RootElement;
var activityTypeName = activityRoot.GetProperty("type").GetString()!;
var activityType = _activityTypeService.ResolveType(activityTypeName);
var newOptions = new JsonSerializerOptions();
var activity = (Activity)JsonSerializer.Deserialize(activityRoot.GetRawText(), activityType, newOptions)!;
return activity;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Activity value, JsonSerializerOptions options)
{
var newOptions = new JsonSerializerOptions(options)
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
writer.WriteStartObject();
foreach (var prop in value.Where(kvp => kvp.Value != null))
{
writer.WritePropertyName(prop.Key);
JsonSerializer.Serialize(writer, prop.Value, prop.Value.GetType(), newOptions);
}
writer.WriteEndObject();
}
}

View file

@ -1,28 +0,0 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Api.Client.Activities;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Api.Client.Converters;
/// <summary>
/// Creates instances of <see cref="ActivityJsonConverter"/>.
/// </summary>
public class ActivityJsonConverterFactory : JsonConverterFactory
{
private readonly IServiceProvider _serviceProvider;
/// <inheritdoc />
public ActivityJsonConverterFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
// 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
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Activity);
/// <inheritdoc />
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => ActivatorUtilities.CreateInstance<ActivityJsonConverter>(_serviceProvider);
}

View file

@ -1,108 +1,114 @@
using Elsa.Api.Client.Activities;
using System.Diagnostics;
using System.Text.Json.Nodes;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides extension methods for <see cref="Activity"/>.
/// Provides extension methods for <see cref="JsonObject"/>.
/// </summary>
public static class ActivityExtensions
{
/// <summary>
/// Sets the designer metadata for the specified activity.
/// Gets the type name of the specified activity.
/// </summary>
public static void SetDesignerMetadata(this Activity activity, ActivityDesignerMetadata designerMetadata)
{
var metadata = activity.Metadata;
metadata["designer"] = designerMetadata;
activity.Metadata = metadata;
}
public static string GetTypeName(this JsonObject activity) => activity.GetProperty<string>("type")!;
/// <summary>
/// Gets the ID of the specified activity.
/// </summary>
public static string GetId(this JsonObject activity) => activity.GetProperty<string>("id")!;
/// <summary>
/// Sets the ID of the specified activity.
/// </summary>
public static void SetId(this JsonObject activity, string value) => activity.SetProperty(JsonValue.Create(value), "id");
/// <summary>
/// Gets the name of the specified activity.
/// </summary>
public static string? GetName(this JsonObject activity) => activity.GetProperty<string>("name");
/// <summary>
/// Sets the name of the specified activity.
/// </summary>
public static void SetName(this JsonObject activity, string? value) => activity.SetProperty(JsonValue.Create(value), "name");
/// <summary>
/// Gets the designer metadata for the specified activity.
/// </summary>
public static ActivityDesignerMetadata GetDesignerMetadata(this Activity activity)
{
var metadata = activity.Metadata;
var designerMetadata = metadata.TryGetValue("designer", () => new ActivityDesignerMetadata())!;
metadata["designer"] = designerMetadata;
activity.Metadata = metadata;
return designerMetadata;
}
public static JsonObject? GetMetadata(this JsonObject activity) => activity.GetProperty("metadata")?.AsObject();
/// <summary>
/// Sets the designer metadata for the specified activity.
/// </summary>
public static void SetDesignerMetadata(this JsonObject activity, ActivityDesignerMetadata designerMetadata) => activity.SetProperty(designerMetadata.SerializeToNode(), "metadata", "designer");
/// <summary>
/// Gets the designer metadata for the specified activity.
/// </summary>
public static ActivityDesignerMetadata GetDesignerMetadata(this JsonObject activity) => activity.GetProperty<ActivityDesignerMetadata>("metadata", "designer") ?? new ActivityDesignerMetadata();
/// <summary>
/// Gets the display text for the specified activity.
/// </summary>
public static string? GetDisplayText(this Activity activity)
{
var metadata = activity.Metadata;
return metadata.TryGetValue<string>("displayText");
}
public static string? GetDisplayText(this JsonObject activity) => activity.GetProperty("metadata", "displayText")?.GetValue<string>();
/// <summary>
/// Sets the display text for the specified activity.
/// </summary>
public static void SetDisplayText(this Activity activity, string value)
public static void SetDisplayText(this JsonObject activity, string? value)
{
var metadata = activity.Metadata;
metadata["displayText"] = value;
activity.Metadata = metadata;
activity.SetProperty(JsonValue.Create(value), "metadata", "displayText");
}
/// <summary>
/// Gets the description for the specified activity.
/// </summary>
public static string? GetDescription(this Activity activity)
{
var metadata = activity.Metadata;
return metadata.TryGetValue<string>("description");
}
public static string? GetDescription(this JsonObject activity) => activity.GetProperty("metadata", "description")?.GetValue<string>();
/// <summary>
/// Sets the description for the specified activity.
/// </summary>
public static void SetDescription(this Activity activity, string value)
{
var metadata = activity.Metadata;
metadata["description"] = value;
activity.Metadata = metadata;
}
public static void SetDescription(this JsonObject activity, string value) => activity.SetProperty(JsonValue.Create(value), "metadata", "description");
/// <summary>
/// Gets a value indicating whether the description for the specified activity should be shown.
/// </summary>
public static bool? GetShowDescription(this Activity activity)
{
var metadata = activity.Metadata;
return metadata.TryGetValue<bool>("showDescription");
}
public static bool? GetShowDescription(this JsonObject activity) => activity.GetProperty<bool>("metadata", "showDescription");
/// <summary>
/// Sets a value indicating whether the description for the specified activity should be shown.
/// </summary>
public static void SetShowDescription(this Activity activity, bool value)
{
var metadata = activity.Metadata;
metadata["showDescription"] = value;
activity.Metadata = metadata;
}
public static void SetShowDescription(this JsonObject activity, bool value) => activity.SetProperty(JsonValue.Create(value), "metadata", "showDescription");
/// <summary>
/// Gets a value indicating whether the specified activity can trigger the workflow.
/// </summary>
public static bool? GetCanStartWorkflow(this Activity activity)
{
var properties = activity.CustomProperties;
return properties.TryGetValue<bool>("canStartWorkflow");
}
public static bool? GetCanStartWorkflow(this JsonObject activity) => activity.GetProperty<bool>("customProperties", "canStartWorkflow");
/// <summary>
/// Sets a value indicating whether the specified activity can trigger the workflow.
/// </summary>
public static void SetCanStartWorkflow(this Activity activity, bool value)
{
var properties = activity.CustomProperties;
properties["canStartWorkflow"] = value;
activity.CustomProperties = properties;
}
public static void SetCanStartWorkflow(this JsonObject activity, bool value) => activity.SetProperty(JsonValue.Create(value), "customProperties", "canStartWorkflow");
/// <summary>
/// Gets the activities in the specified flowchart.
/// </summary>
public static IEnumerable<JsonObject> GetActivities(this JsonObject flowchart) => flowchart.GetProperty("activities")?.AsArray().AsEnumerable().Cast<JsonObject>() ?? Array.Empty<JsonObject>();
/// <summary>
/// Sets the activities in the specified flowchart.
/// </summary>
public static void SetActivities(this JsonObject flowchart, JsonArray activities) => flowchart.SetProperty(activities, "activities");
/// <summary>
/// Gets the connections in the specified flowchart.
/// </summary>
public static IEnumerable<Connection> GetConnections(this JsonObject flowchart) => flowchart.GetProperty<ICollection<Connection>>("connections") ?? new List<Connection>();
/// <summary>
/// Sets the connections in the specified flowchart.
/// </summary>
public static void SetConnections(this JsonObject flowchart, IEnumerable<Connection> connections) => flowchart.SetProperty(JsonValue.Create(connections), "connections");
}

View file

@ -1,6 +1,5 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Api.Client.ActivityProviders;
using Elsa.Api.Client.Contracts;
using Elsa.Api.Client.Converters;
using Elsa.Api.Client.Options;
@ -49,10 +48,6 @@ public static class DependencyInjectionExtensions
/// </summary>
public static IServiceCollection AddActivityTypeService(this IServiceCollection services)
{
services.AddSingleton<IActivityTypeService, DefaultActivityTypeService>();
services.AddActivityTypeResolver<DefaultActivityTypeResolver>();
services.AddActivityTypeResolver<FlowchartTypeResolver>();
services.AddActivityTypeResolver<FlowSwitchTypeResolver>();
return services;
}
@ -67,14 +62,6 @@ public static class DependencyInjectionExtensions
services.AddRefitClient<T>(settings)
.ConfigureHttpClient(ConfigureElsaApiHttpClient);
}
/// <summary>
/// Adds an activity type resolver to the service collection.
/// </summary>
public static IServiceCollection AddActivityTypeResolver<T>(this IServiceCollection services) where T : class, IActivityTypeResolver
{
return services.AddSingleton<IActivityTypeResolver, T>();
}
private static void ConfigureElsaApiHttpClient(IServiceProvider serviceProvider, HttpClient httpClient)
{
@ -91,7 +78,6 @@ public static class DependencyInjectionExtensions
serializerOptions.Converters.Add(new JsonStringEnumConverter());
serializerOptions.Converters.Add(new VersionOptionsJsonConverter());
serializerOptions.Converters.Add(new ActivityJsonConverterFactory(serviceProvider));
serializerOptions.Converters.Add(new ExpressionJsonConverterFactory());
var settings = new RefitSettings

View file

@ -0,0 +1,186 @@
using System.Text.Json;
using System.Text.Json.Nodes;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides extension methods for <see cref="JsonObject"/>.
/// </summary>
public static class JsonObjectExtensions
{
/// <summary>
/// Serializes the specified value to a <see cref="JsonObject"/>.
/// </summary>
/// <param name="value">The value to serialize.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <returns>A <see cref="JsonObject"/> representing the specified value.</returns>
public static JsonNode SerializeToNode(this object value, JsonSerializerOptions? options = default)
{
options ??= new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
return JsonSerializer.SerializeToNode(value, options)!;
}
/// <summary>
/// Serializes the specified value to a <see cref="JsonArray"/>.
/// </summary>
/// <param name="value">The value to serialize.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <returns>A <see cref="JsonObject"/> representing the specified value.</returns>
public static JsonArray SerializeToArray(this object value, JsonSerializerOptions? options = default)
{
options ??= new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
return JsonSerializer.SerializeToNode(value, options)!.AsArray();
}
/// <summary>
/// Serializes the specified value to a <see cref="JsonArray"/>.
/// </summary>
/// <param name="value">The value to serialize.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <returns>A <see cref="JsonObject"/> representing the specified value.</returns>
public static JsonArray SerializeToArray<T>(this IEnumerable<T> value, JsonSerializerOptions? options = default)
{
options ??= new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
return JsonSerializer.SerializeToNode(value, options)!.AsArray();
}
/// <summary>
/// Deserializes the specified <see cref="JsonNode"/> to the specified type.
/// </summary>
/// <param name="value">The <see cref="JsonNode"/> to deserialize.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <returns>The deserialized value.</returns>
public static T Deserialize<T>(this JsonNode value, JsonSerializerOptions? options = default)
{
options ??= new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
if (value is JsonObject jsonObject)
return JsonSerializer.Deserialize<T>(jsonObject, options)!;
if (value is JsonArray jsonArray)
return JsonSerializer.Deserialize<T>(jsonArray, options)!;
if (value is JsonValue jsonValue)
return jsonValue.GetValue<T>();
throw new NotSupportedException($"Cannot deserialize {value.GetType()} to {typeof(T)}.");
}
/// <summary>
/// Sets the property value of the specified model.
/// </summary>
/// <param name="model">The model to set the property value on.</param>
/// <param name="value">The value to set.</param>
/// <param name="path">The path to the property.</param>
public static void SetProperty(this JsonObject model, JsonNode? value, params string[] path)
{
model = GetPropertyContainer(model, path);
model[path.Last()] = value?.SerializeToNode();
}
/// <summary>
/// Sets the property value of the specified model.
/// </summary>
/// <param name="model">The model to set the property value on.</param>
/// <param name="value">The value to set.</param>
/// <param name="path">The path to the property.</param>
public static void SetProperty(this JsonObject model, JsonArray? value, params string[] path)
{
model = GetPropertyContainer(model, path);
model[path.Last()] = value?.SerializeToNode();
}
/// <summary>
/// Sets the property value of the specified model.
/// </summary>
/// <param name="model">The model to set the property value on.</param>
/// <param name="value">The value to set.</param>
/// <param name="path">The path to the property.</param>
public static void SetProperty(this JsonObject model, IEnumerable<JsonNode> value, params string[] path)
{
model = GetPropertyContainer(model, path);
model[path.Last()] = new JsonArray(value.Select(x => x.SerializeToNode()).ToArray());
}
/// <summary>
/// Gets the property value of the specified model.
/// </summary>
/// <param name="model">The model to get the property value from.</param>
/// <param name="path">The path to the property.</param>
/// <returns>The property value.</returns>
public static JsonNode? GetProperty(this JsonObject model, params string[] path)
{
var currentModel = model;
foreach (var prop in path.SkipLast(1))
{
if (currentModel[prop] is not JsonObject value)
return default;
currentModel = value;
}
return currentModel[path.Last()];
}
/// <summary>
/// Gets the property value of the specified model.
/// </summary>
/// <param name="model">The model to get the property value from.</param>
/// <param name="path">The path to the property.</param>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <returns>The property value.</returns>
public static T? GetProperty<T>(this JsonObject model, params string[] path)
{
var property = GetProperty(model, path);
return property != null ? property.Deserialize<T>() : default;
}
/// <summary>
/// Gets the property value of the specified model.
/// </summary>
/// <param name="model">The model to get the property value from.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use when deserializing.</param>
/// <param name="path">The path to the property.</param>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <returns>The property value.</returns>
public static T? GetProperty<T>(this JsonObject model, JsonSerializerOptions options, params string[] path)
{
var property = GetProperty(model, path);
return property != null ? property.Deserialize<T>(options) : default;
}
/// <summary>
/// Returns the property container of the specified model.
/// </summary>
/// <param name="model">The model to set the property value on.</param>
/// <param name="path">The path to the property.</param>
private static JsonObject GetPropertyContainer(this JsonObject model, params string[] path)
{
foreach (var prop in path.SkipLast(1))
{
var property = model[prop] as JsonObject ?? new JsonObject();
model[prop] = property;
model = property;
}
return model;
}
}

View file

@ -3,6 +3,7 @@ using System.ComponentModel;
using System.Dynamic;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace Elsa.Api.Client.Extensions;
@ -52,17 +53,20 @@ public static class ObjectConverter
var underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
var underlyingSourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
if (value is JsonElement jsonNumber && jsonNumber.ValueKind == JsonValueKind.Number && underlyingTargetType == typeof(string))
if (value is JsonElement { ValueKind: JsonValueKind.Number } jsonNumber && underlyingTargetType == typeof(string))
return jsonNumber.ToString().ConvertTo(underlyingTargetType);
if (value is JsonElement jsonObject)
if (value is JsonElement jsonElement)
{
if (jsonObject.ValueKind == JsonValueKind.String && underlyingTargetType != typeof(string))
return jsonObject.GetString().ConvertTo(underlyingTargetType);
if (jsonElement.ValueKind == JsonValueKind.String && underlyingTargetType != typeof(string))
return jsonElement.GetString().ConvertTo(underlyingTargetType);
return jsonObject.Deserialize(targetType, options);
return jsonElement.Deserialize(targetType, options);
}
if (value is JsonNode jsonNode)
return jsonNode.Deserialize(targetType, options);
if (underlyingSourceType == typeof(string) && !underlyingTargetType.IsPrimitive && underlyingTargetType != typeof(object))
{
var stringValue = (string)value;
@ -116,7 +120,7 @@ public static class ObjectConverter
if (targetTypeConverter.CanConvertFrom(underlyingSourceType))
return targetTypeConverter.IsValid(value)
? targetTypeConverter.ConvertFrom(null, CultureInfo.InvariantCulture, value)
? targetTypeConverter.ConvertFrom(null!, CultureInfo.InvariantCulture, value)
: targetType.GetDefaultValue();
var sourceTypeConverter = TypeDescriptor.GetConverter(underlyingSourceType);

View file

@ -1,3 +1,8 @@
namespace Elsa.Api.Client.Resources.StorageDrivers.Models;
/// <summary>
/// Represents a storage driver descriptor.
/// </summary>
/// <param name="TypeName">The type name of the storage driver.</param>
/// <param name="DisplayName">The display name of the storage driver.</param>
public record StorageDriverDescriptor(string TypeName, string DisplayName);

View file

@ -1,4 +1,7 @@
using Elsa.Api.Client.Activities;
//using Elsa.Api.Client.Activities;
using System.Text.Json;
using System.Text.Json.Nodes;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
@ -76,7 +79,7 @@ public class WorkflowDefinition : VersionedEntity
/// <summary>
/// The root activity of the workflow.
/// </summary>
public Activity Root { get; set; } = default!;
public JsonObject Root { get; set; } = default!;
/// <summary>
/// An option to use the workflow as a readonly workflow.

View file

@ -1,4 +1,6 @@
using Elsa.Api.Client.Activities;
using System.Text.Json;
using System.Text.Json.Nodes;
//using Elsa.Api.Client.Activities;
using JetBrains.Annotations;
namespace Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
@ -9,25 +11,88 @@ namespace Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
[PublicAPI]
public class WorkflowDefinitionModel
{
/// <summary>
/// Gets or sets the version ID of the workflow definition.
/// </summary>
public string Id { get; set; } = default!;
/// <summary>
/// Gets or sets the definition ID of the workflow definition.
/// </summary>
public string DefinitionId { get; set; } = default!;
/// <summary>
/// Gets or sets the name of the workflow definition.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Gets or sets the description of the workflow definition.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Gets or sets the time at which the workflow definition was created.
/// </summary>
public DateTimeOffset CreatedAt { get; set; }
/// <summary>
/// Gets or sets the version of the workflow definition.
/// </summary>
public int Version { get; set; }
/// <summary>
/// Gets or sets the version of the tool that created the workflow definition.
/// </summary>
public Version? ToolVersion { get; set; }
/// <summary>
/// Gets or sets the variables of the workflow definition.
/// </summary>
public ICollection<VariableDefinition>? Variables { get; set; }
/// <summary>
/// Gets or sets the inputs of the workflow definition.
/// </summary>
public ICollection<InputDefinition>? Inputs { get; set; }
/// <summary>
/// Gets or sets the outputs of the workflow definition.
/// </summary>
public ICollection<OutputDefinition>? Outputs { get; set; }
/// <summary>
/// Gets or sets the outcomes of the workflow definition.
/// </summary>
public ICollection<string>? Outcomes { get; set; }
/// <summary>
/// Gets or sets the custom properties associated with the workflow definition.
/// </summary>
public IDictionary<string, object>? CustomProperties { get; set; }
/// <summary>
/// Gets or sets whether the workflow definition is read-only.
/// </summary>
public bool IsReadonly { get; set; }
/// <summary>
/// Gets or sets whether this is the latest version of the workflow definition.
/// </summary>
public bool IsLatest { get; set; }
/// <summary>
/// Gets or sets whether this is the published version of the workflow definition.
/// </summary>
public bool IsPublished { get; set; }
/// <summary>The type of <c>IWorkflowActivationStrategy</c> to apply when new instances are requested to be created.</summary>
/// <summary>
/// Gets or sets the <see cref="WorkflowOptions"/> of the workflow definition.
/// </summary>
public WorkflowOptions? Options { get; set; }
/// <summary></summary>
public Activity? Root { get; set; }
/// <summary>
/// Gets or sets the root activity of the workflow definition.
/// </summary>
public JsonObject? Root { get; set; }
}

View file

@ -1,35 +1,35 @@
using Elsa.Api.Client.Activities;
using Elsa.Api.Client.Contracts;
namespace Elsa.Api.Client.Services;
/// <summary>
/// Provides a default implementation of <see cref="IActivityTypeService"/> that creates a <see cref="Activity"/>.
/// </summary>
public class DefaultActivityTypeService : IActivityTypeService
{
private readonly IEnumerable<IActivityTypeResolver> _activityProviders;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultActivityTypeService"/> class.
/// </summary>
/// <param name="activityProviders">The <see cref="IActivityTypeResolver"/>s.</param>
public DefaultActivityTypeService(IEnumerable<IActivityTypeResolver> activityProviders)
{
_activityProviders = activityProviders.OrderByDescending(x => x.Priority);
}
/// <inheritdoc />
public Type ResolveType(string activityType)
{
var context = new ActivityTypeResolverContext(activityType);
var activityProvider = GetActivityProvider(context);
if (activityProvider == null)
throw new Exception($"No activity provider found for activity type '{activityType}'.");
return activityProvider.ResolveType(context);
}
private IActivityTypeResolver? GetActivityProvider(ActivityTypeResolverContext context) => _activityProviders.FirstOrDefault(provider => provider.GetSupportsType(context));
}
// using Elsa.Api.Client.Activities;
// using Elsa.Api.Client.Contracts;
//
// namespace Elsa.Api.Client.Services;
//
// /// <summary>
// /// Provides a default implementation of <see cref="IActivityTypeService"/> that creates a <see cref="Activity"/>.
// /// </summary>
// public class DefaultActivityTypeService : IActivityTypeService
// {
// private readonly IEnumerable<IActivityTypeResolver> _activityProviders;
//
// /// <summary>
// /// Initializes a new instance of the <see cref="DefaultActivityTypeService"/> class.
// /// </summary>
// /// <param name="activityProviders">The <see cref="IActivityTypeResolver"/>s.</param>
// public DefaultActivityTypeService(IEnumerable<IActivityTypeResolver> activityProviders)
// {
// _activityProviders = activityProviders.OrderByDescending(x => x.Priority);
// }
//
// /// <inheritdoc />
// public Type ResolveType(string activityType)
// {
// var context = new ActivityTypeResolverContext(activityType);
// var activityProvider = GetActivityProvider(context);
//
// if (activityProvider == null)
// throw new Exception($"No activity provider found for activity type '{activityType}'.");
//
// return activityProvider.ResolveType(context);
// }
//
// private IActivityTypeResolver? GetActivityProvider(ActivityTypeResolverContext context) => _activityProviders.FirstOrDefault(provider => provider.GetSupportsType(context));
// }

View file

@ -41,10 +41,10 @@ public class ActivityDesignerMetadata
/// <summary>
/// Gets or sets the position of the activity.
/// </summary>
public Position Position { get; set; } = default!;
public Position Position { get; set; } = new();
/// <summary>
/// Gets or sets the size of the activity.
/// </summary>
public Size Size { get; set; } = default!;
public Size Size { get; set; } = new();
}

View file

@ -1,29 +1,29 @@
using Elsa.Api.Client.Activities;
using Elsa.Api.Client.Extensions;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
namespace Elsa.Api.Client.Shared.Models;
/// <summary>
/// Represents a container activity.
/// </summary>
public class Container : Activity
{
/// <summary>
/// Gets or sets the activities contained in this container.
/// </summary>
public ICollection<Activity> Activities
{
get => this.TryGetValue<ICollection<Activity>>("activities", () => new List<Activity>())!;
set => this["activities"] = value;
}
/// <summary>
/// Gets or sets the variables in this container.
/// </summary>
public ICollection<Variable> Variables
{
get => this.TryGetValue<ICollection<Variable>>("variables", () => new List<Variable>())!;
set => this["variables"] = value;
}
}
// using Elsa.Api.Client.Activities;
// using Elsa.Api.Client.Extensions;
// using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
//
// namespace Elsa.Api.Client.Shared.Models;
//
// /// <summary>
// /// Represents a container activity.
// /// </summary>
// public class Container : Activity
// {
// /// <summary>
// /// Gets or sets the activities contained in this container.
// /// </summary>
// public ICollection<Activity> Activities
// {
// get => this.TryGetValue<ICollection<Activity>>("activities", () => new List<Activity>())!;
// set => this["activities"] = value;
// }
//
// /// <summary>
// /// Gets or sets the variables in this container.
// /// </summary>
// public ICollection<Variable> Variables
// {
// get => this.TryGetValue<ICollection<Variable>>("variables", () => new List<Variable>())!;
// set => this["variables"] = value;
// }
// }

View file

@ -1,4 +1,4 @@
using Elsa.Api.Client.Activities;
using System.Text.Json.Nodes;
namespace Elsa.Api.Client.Shared.Models;
@ -15,5 +15,5 @@ public class HttpStatusCodeCase
/// <summary>
/// The activity to execute when the HTTP status code matches.
/// </summary>
public Activity? Activity { get; set; }
public JsonObject? Activity { get; set; }
}