diff --git a/src/clients/Elsa.Api.Client/Converters/TypeJsonConverter.cs b/src/clients/Elsa.Api.Client/Converters/TypeJsonConverter.cs new file mode 100644 index 000000000..60602c7bc --- /dev/null +++ b/src/clients/Elsa.Api.Client/Converters/TypeJsonConverter.cs @@ -0,0 +1,56 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Elsa.Api.Client.Extensions; +using JetBrains.Annotations; + +namespace Elsa.Api.Client.Converters; + +/// +/// Converts objects to and from their assembly-qualified name. +/// +[UsedImplicitly] +public class TypeJsonConverter : JsonConverter +{ + /// + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert == typeof(Type) || typeToConvert.FullName == "System.RuntimeType"; + } + + /// + public override Type? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var typeName = reader.GetString()!; + + // Handle collection types. + if (typeName.EndsWith("[]")) + { + var elementTypeName = typeName[..^"[]".Length]; + var elementType = Type.GetType(elementTypeName)!; + return typeof(List<>).MakeGenericType(elementType); + } + + return Type.GetType(typeName); + } + + /// + public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options) + { + // Handle collection types. + if (value is { IsGenericType: true, GenericTypeArguments.Length: 1 }) + { + var elementType = value.GenericTypeArguments.First(); + var typedEnumerable = typeof(IEnumerable<>).MakeGenericType(elementType); + + if (typedEnumerable.IsAssignableFrom(value)) + { + var elementTypeName = value.GetSimpleAssemblyQualifiedName(); + JsonSerializer.Serialize(writer, $"{elementTypeName}[]", options); + return; + } + } + + var typeName = value.GetSimpleAssemblyQualifiedName(); + JsonSerializer.Serialize(writer, typeName, options); + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs index 1dec341fd..e7bed4507 100644 --- a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs @@ -124,7 +124,7 @@ public static class DependencyInjectionExtensions /// An options object that can be used to configure the HTTP client builder. public static IServiceCollection AddApi(this IServiceCollection services, Type apiType, ElsaClientBuilderOptions? httpClientBuilderOptions = default) { - var builder = services.AddRefitClient(apiType, _ => CreateRefitSettings(), apiType.Name).ConfigureHttpClient(ConfigureElsaApiHttpClient); + var builder = services.AddRefitClient(apiType, sp => CreateRefitSettings(sp, httpClientBuilderOptions?.ConfigureJsonSerializerOptions), apiType.Name).ConfigureHttpClient(ConfigureElsaApiHttpClient); httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder); httpClientBuilderOptions?.ConfigureRetryPolicy?.Invoke(builder); return services; @@ -139,7 +139,7 @@ public static class DependencyInjectionExtensions public static void AddApiWithoutRetryPolicy(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class { var builder = services - .AddRefitClient(_ => CreateRefitSettings(), typeof(T).Name) + .AddRefitClient(sp => CreateRefitSettings(sp), typeof(T).Name) .ConfigureHttpClient(ConfigureElsaApiHttpClient); httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder); } @@ -156,7 +156,7 @@ public static class DependencyInjectionExtensions /// Creates an API client for the specified API type. public static T CreateApi(this IServiceProvider serviceProvider, HttpClient httpClient) where T : class { - return RestService.For(httpClient, CreateRefitSettings()); + return RestService.For(httpClient, CreateRefitSettings(serviceProvider)); } private static void ConfigureElsaApiHttpClient(IServiceProvider serviceProvider, HttpClient httpClient) diff --git a/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs b/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs index ec47e8710..8a87220a3 100644 --- a/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs +++ b/src/clients/Elsa.Api.Client/Helpers/RefitSettingsHelper.cs @@ -10,17 +10,12 @@ namespace Elsa.Api.Client; /// public static class RefitSettingsHelper { - private static JsonSerializerOptions? _jsonSerializerOptions; - /// /// Creates a instance configured for Elsa. /// - public static RefitSettings CreateRefitSettings() + public static RefitSettings CreateRefitSettings(IServiceProvider serviceProvider, Action? configureJsonSerializerOptions = null) { - var settings = new RefitSettings - { - ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions()) - }; + var settings = new RefitSettings { ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions(serviceProvider, configureJsonSerializerOptions)) }; return settings; } @@ -28,11 +23,8 @@ public static class RefitSettingsHelper /// /// Creates a instance configured for Elsa. /// - public static JsonSerializerOptions CreateJsonSerializerOptions() + public static JsonSerializerOptions CreateJsonSerializerOptions(IServiceProvider serviceProvider, Action? configureJsonSerializerOptions = null) { - if (_jsonSerializerOptions != null) - return _jsonSerializerOptions; - var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, @@ -40,7 +32,10 @@ public static class RefitSettingsHelper options.Converters.Add(new JsonStringEnumConverter()); options.Converters.Add(new VersionOptionsJsonConverter()); + options.Converters.Add(new TypeJsonConverter()); - return _jsonSerializerOptions = options; + configureJsonSerializerOptions?.Invoke(serviceProvider, options); + + return options; } } \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs b/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs index a7312eb60..52b0b1b77 100644 --- a/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs +++ b/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using Elsa.Api.Client.HttpMessageHandlers; using Microsoft.Extensions.DependencyInjection; using Polly; @@ -39,4 +40,9 @@ public class ElsaClientBuilderOptions /// Gets or sets a delegate that can be used to configure the retry policy. /// public Action? ConfigureRetryPolicy { get; set; } = builder => builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)))); + + /// + /// Gets or sets a delegate that can be used to configure the JSON serializer options. + /// + public Action? ConfigureJsonSerializerOptions { get; set; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs index 3442a9c11..dee636927 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs @@ -9,7 +9,7 @@ namespace Elsa.Workflows.Serialization.Converters; /// /// Serializes objects to a simple alias representing the type. /// -[PublicAPI] +[UsedImplicitly] public class TypeJsonConverter : JsonConverter { private readonly IWellKnownTypeRegistry _wellKnownTypeRegistry;