ASP.NET中Newtonsoft致IAsyncEnumerable异步失效的解决方案咨询
解决方案:针对IAsyncEnumerable或部分控制器切换JSON序列化器
问题原因
全局配置AddNewtonsoftJson()后,Newtonsoft.Json不支持IAsyncEnumerable的异步流式序列化,会先枚举完所有数据再一次性返回,导致接口需要等待所有Task.Delay完成后才响应。而System.Text.Json原生支持IAsyncEnumerable的流式输出,能逐条返回数据。
方案一:按控制器/方法指定序列化器
通过自定义条件格式化器,让带特定标记的控制器用Newtonsoft.Json,其他控制器默认用System.Text.Json。
步骤1:定义标记特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class UseNewtonsoftJsonAttribute : Attribute { }
步骤2:配置服务,设置条件筛选
builder.Services.AddControllers(options => { // 将System.Text.Json设为默认格式化器(放在最前面) var systemTextFormatter = options.OutputFormatters.OfType<SystemTextJsonOutputFormatter>().First(); options.OutputFormatters.Remove(systemTextFormatter); options.OutputFormatters.Insert(0, systemTextFormatter); // 取出Newtonsoft格式化器,设置仅在带有UseNewtonsoftJsonAttribute的控制器/方法中使用 var newtonsoftFormatter = options.OutputFormatters.OfType<NewtonsoftJsonOutputFormatter>().First(); options.OutputFormatters.Remove(newtonsoftFormatter); options.OutputFormatters.Add(new ConditionalOutputFormatter(newtonsoftFormatter, context => { return context.ActionDescriptor.EndpointMetadata.Any(em => em is UseNewtonsoftJsonAttribute); })); }) .AddNewtonsoftJson() // 保留Newtonsoft的注册 .AddJsonOptions(opt => { // 按需配置System.Text.Json的序列化规则 opt.JsonSerializerOptions.PropertyNamingPolicy = null; // 保留原始属性名 });
步骤3:在需要Newtonsoft的控制器上标记
// 这个控制器的所有接口都用Newtonsoft.Json [UseNewtonsoftJson] public class LegacyController : ControllerBase { // ... 你的接口代码 } // 你的IAsyncTest所在的控制器不用加标记,自动用System.Text.Json,支持流式输出 public class TestController : ControllerBase { [HttpGet("IAsyncTest")] public IAsyncEnumerable<TestDTO> GetTestValue() { return _getValues(); } // ... 其他代码 }
方案二:自定义格式化器专门处理IAsyncEnumerable
如果不想拆分控制器,只想让IAsyncEnumerable类型用System.Text.Json流式输出,其他类型继续用Newtonsoft.Json,可以写一个自定义输出格式化器。
步骤1:实现自定义格式化器
public class AsyncEnumerableSystemTextJsonFormatter : OutputFormatter { private readonly JsonSerializerOptions _jsonOptions; public AsyncEnumerableSystemTextJsonFormatter(JsonSerializerOptions jsonOptions) { _jsonOptions = jsonOptions; SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json")); SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); } protected override bool CanWriteType(Type type) { // 仅处理IAsyncEnumerable<T>类型 return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>); } public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { var response = context.HttpContext.Response; var asyncEnumerable = context.Object as IAsyncEnumerable<object>; if (asyncEnumerable == null) return; response.ContentType = "application/json"; await using var jsonWriter = new Utf8JsonWriter(response.Body, new JsonWriterOptions { Indented = _jsonOptions.WriteIndented }); jsonWriter.WriteStartArray(); // 异步枚举并逐条序列化 await foreach (var item in asyncEnumerable) { JsonSerializer.Serialize(jsonWriter, item, item.GetType(), _jsonOptions); } jsonWriter.WriteEndArray(); await jsonWriter.FlushAsync(); } }
步骤2:注册自定义格式化器
builder.Services.AddControllers() .AddNewtonsoftJson() // 保留全局Newtonsoft配置 .AddMvcOptions(options => { // 获取System.Text.Json的默认配置 var jsonOptions = builder.Services.BuildServiceProvider() .GetRequiredService<IOptions<JsonOptions>>().Value.JsonSerializerOptions; // 将自定义格式化器放在最前面,优先处理IAsyncEnumerable类型 options.OutputFormatters.Insert(0, new AsyncEnumerableSystemTextJsonFormatter(jsonOptions)); });
这样,所有返回IAsyncEnumerable的接口都会自动用System.Text.Json流式输出,其他类型的返回结果依然使用Newtonsoft.Json序列化。
内容的提问来源于stack exchange,提问作者oussama benyaala
相关产品推荐
相关产品推荐

