From 4a7280a1153c5c0da40914a75a6c0cf72c202b5c Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 29 Jul 2025 18:33:27 +0800 Subject: [PATCH 1/3] Client Proxy `IAsyncEnumerable` support. --- .../CSharp/CSharpServiceProxyGenerator.cs | 39 +++++++++++------- .../Client/ClientProxying/ClientProxyBase.cs | 18 +++++++++ .../ObjectToInferredTypesConverter.cs | 40 ++++++++++++++----- 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs index e0dcc14fb1..64947e637f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs @@ -19,7 +19,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase" + $"{Environment.NewLine}" + $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + @@ -44,7 +44,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase;" + $"{Environment.NewLine}" + @@ -53,7 +53,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase" + $"{Environment.NewLine}" + $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + @@ -65,7 +65,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase" + $"{Environment.NewLine}" + $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + @@ -77,7 +77,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase ClassUsingNamespaceList = new() + private static readonly List ClassUsingNamespaceList = new() { "using System;", "using System.Collections.Generic;", @@ -90,7 +90,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase InterfaceUsingNamespaceList = new() + private static readonly List InterfaceUsingNamespaceList = new() { "using System;", "using System.Collections.Generic;", @@ -100,7 +100,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase DtoUsingNamespaceList = new() + private static readonly List DtoUsingNamespaceList = new() { "using System;", "using System.Collections.Generic;", @@ -116,7 +116,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase usingNamespaceList) { - var returnSign = returnTypeName == "void" ? "Task" : $"Task<{returnTypeName}>"; + var isAsyncEnumerable = returnTypeName.StartsWith("IAsyncEnumerable<"); + var asyncEnumerableTypeName = isAsyncEnumerable + ? returnTypeName.Substring("IAsyncEnumerable<".Length, returnTypeName.Length - "IAsyncEnumerable<".Length - 1) + : null; + + var returnSign = isAsyncEnumerable ? returnTypeName : returnTypeName == "void" ? "Task" : $"Task<{returnTypeName}>"; - methodBuilder.AppendLine($"public virtual async {returnSign} {action.Name}()"); + methodBuilder.AppendLine(isAsyncEnumerable + ? $"public virtual {returnSign} {action.Name}()" + : $"public virtual async {returnSign} {action.Name}()"); foreach (var parameter in action.ParametersOnMethod) { @@ -325,9 +332,11 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase(nameof({action.Name}), {args});"); + methodBuilder.AppendLine(isAsyncEnumerable + ? $" return RequestAsyncEnumerable<{asyncEnumerableTypeName}>(nameof({action.Name}), {args});" + : returnTypeName == "void" + ? $" await RequestAsync(nameof({action.Name}), {args});" + : $" return await RequestAsync<{returnTypeName}>(nameof({action.Name}), {args});"); foreach (var parameter in action.ParametersOnMethod) { @@ -543,7 +552,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase : ITransientDependency protected ClientProxyUrlBuilder ClientProxyUrlBuilder => LazyServiceProvider.LazyGetRequiredService(); protected ICurrentApiVersionInfo CurrentApiVersionInfo => LazyServiceProvider.LazyGetRequiredService(); protected ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService(); + protected IOptions? SystemTextJsonSerializerOptions => LazyServiceProvider.LazyGetService>(); protected virtual async Task RequestAsync(string methodName, ClientProxyRequestTypeValue? arguments = null) { @@ -55,6 +58,21 @@ public class ClientProxyBase : ITransientDependency return await RequestAsync(BuildHttpProxyClientProxyContext(methodName, arguments)); } + protected virtual async IAsyncEnumerable RequestAsyncEnumerable(string methodName, ClientProxyRequestTypeValue? arguments = null) + { + var requestContext = BuildHttpProxyClientProxyContext(methodName, arguments); + var responseContent = await RequestAsync(requestContext); + var options = SystemTextJsonSerializerOptions?.Value.JsonSerializerOptions; + var stream = await responseContent.ReadAsStreamAsync(); + var items = options != null + ? System.Text.Json.JsonSerializer.DeserializeAsyncEnumerable(stream, options) + : System.Text.Json.JsonSerializer.DeserializeAsyncEnumerable(stream); + await foreach (var item in items) + { + yield return item!; + } + } + protected virtual ClientProxyRequestContext BuildHttpProxyClientProxyContext(string methodName, ClientProxyRequestTypeValue? arguments = null) { if (arguments == null) diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs index bf017d041e..5daadb865b 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs @@ -9,23 +9,41 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters; /// public class ObjectToInferredTypesConverter : JsonConverter { + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert == typeof(object); + } + public override object Read( ref Utf8JsonReader reader, Type typeToConvert, - JsonSerializerOptions options) => (reader.TokenType switch + JsonSerializerOptions options) { - JsonTokenType.True => true, - JsonTokenType.False => false, - JsonTokenType.Number when reader.TryGetInt64(out long l) => l, - JsonTokenType.Number => reader.GetDouble(), - JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime, - JsonTokenType.String => reader.GetString(), - _ => JsonDocument.ParseValue(ref reader).RootElement.Clone() - })!; + return reader.TokenType switch + { + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.Number when reader.TryGetInt64(out long l) => l, + JsonTokenType.Number => reader.GetDouble(), + JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime, + JsonTokenType.String => reader.GetString()!, + _ => JsonDocument.ParseValue(ref reader).RootElement.Clone() + }; + } public override void Write( Utf8JsonWriter writer, object objectToWrite, - JsonSerializerOptions options) => - JsonSerializer.Serialize(writer, objectToWrite, objectToWrite.GetType(), options); + JsonSerializerOptions options) + { + var runtimeType = objectToWrite.GetType(); + if (runtimeType == typeof(object)) + { + writer.WriteStartObject(); + writer.WriteEndObject(); + return; + } + + JsonSerializer.Serialize(writer, objectToWrite, runtimeType, options); + } } From c10f7628c4505f90b0db5140bfb2ca1b3c54f554 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 29 Jul 2025 21:03:52 +0800 Subject: [PATCH 2/3] Remove `ObjectToInferredTypesConverter` from `SystemTextJsonOutputFormatter`. --- .../AspNetCore/Mvc/AbpMvcOptionsExtensions.cs | 18 +++++++++++++++++- .../ObjectToInferredTypesConverter.cs | 5 ----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 081342b913..cb63a9e6c8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -1,5 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; @@ -14,6 +18,7 @@ using Volo.Abp.AspNetCore.Mvc.Response; using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; using Volo.Abp.Content; +using Volo.Abp.Json.SystemTextJson.JsonConverters; namespace Volo.Abp.AspNetCore.Mvc; @@ -32,6 +37,17 @@ internal static class AbpMvcOptionsExtensions private static void AddFormatters(MvcOptions options) { options.OutputFormatters.Insert(0, new RemoteStreamContentOutputFormatter()); + var systemTextJsonOutputFormatter = options.OutputFormatters + .Where(f => f is SystemTextJsonOutputFormatter) + .Cast().FirstOrDefault(); + + if (systemTextJsonOutputFormatter != null) + { + options.OutputFormatters.Remove(systemTextJsonOutputFormatter); + var jsonOptions = new JsonSerializerOptions(systemTextJsonOutputFormatter.SerializerOptions); + jsonOptions.Converters.RemoveAll(x => x is ObjectToInferredTypesConverter); + options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(jsonOptions)); + } } private static void AddConventions(MvcOptions options, IServiceCollection services) diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs index 5daadb865b..74dbe5bc33 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs @@ -9,11 +9,6 @@ namespace Volo.Abp.Json.SystemTextJson.JsonConverters; /// public class ObjectToInferredTypesConverter : JsonConverter { - public override bool CanConvert(Type typeToConvert) - { - return typeToConvert == typeof(object); - } - public override object Read( ref Utf8JsonReader reader, Type typeToConvert, From 6ecbdff129397d68e81bfce2ab58e03b6ba13b8d Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 29 Jul 2025 21:05:35 +0800 Subject: [PATCH 3/3] Revert the changes made to `ObjectToInferredTypesConverter`. --- .../ObjectToInferredTypesConverter.cs | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs index 74dbe5bc33..bf017d041e 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs @@ -12,33 +12,20 @@ public class ObjectToInferredTypesConverter : JsonConverter public override object Read( ref Utf8JsonReader reader, Type typeToConvert, - JsonSerializerOptions options) + JsonSerializerOptions options) => (reader.TokenType switch { - return reader.TokenType switch - { - JsonTokenType.True => true, - JsonTokenType.False => false, - JsonTokenType.Number when reader.TryGetInt64(out long l) => l, - JsonTokenType.Number => reader.GetDouble(), - JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime, - JsonTokenType.String => reader.GetString()!, - _ => JsonDocument.ParseValue(ref reader).RootElement.Clone() - }; - } + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.Number when reader.TryGetInt64(out long l) => l, + JsonTokenType.Number => reader.GetDouble(), + JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime, + JsonTokenType.String => reader.GetString(), + _ => JsonDocument.ParseValue(ref reader).RootElement.Clone() + })!; public override void Write( Utf8JsonWriter writer, object objectToWrite, - JsonSerializerOptions options) - { - var runtimeType = objectToWrite.GetType(); - if (runtimeType == typeof(object)) - { - writer.WriteStartObject(); - writer.WriteEndObject(); - return; - } - - JsonSerializer.Serialize(writer, objectToWrite, runtimeType, options); - } + JsonSerializerOptions options) => + JsonSerializer.Serialize(writer, objectToWrite, objectToWrite.GetType(), options); }