记录一个我认为是Net6 Aspnetcore 框架的一个Bug

Bug描述

在 Net6 的apsnecore项目中, 如果我们(满足以下所有条件)

  • api的返回类型是IAsyncEnumerable<T>,
  • 且我们返回的是JsonResult对象, 或者返回的是ObjectResult且要求的返回协商数据类型是json,
  • 且我们用的是System.Text.Json来序列化(模式是它),
  • 且我们的响应用要求的编码是utf-8

那么在业务方法中抛出的任何OperationCanceledException或者继承自OperationCanceledException的任何子类异常都会被框架吃掉.

Bug重现

如果我们有这样一段代码, 然后结果就是客户端和服务端都不会收到或者记录任何错误和异常.

[HttpGet("/asyncEnumerable-cancel")]
public ActionResult<IAsyncEnumerable<int>> TestAsync()
{
async IAsyncEnumerable<int> asyncEnumerable()
{
await Task.Delay(100); yield return 1; throw new OperationCanceledException();
// 或者Client 主动取消请求后 用this.HttpContext.RequestAborted.ThrowIfCancellationRequested() 或者任何地方抛出的task或operation cancel exception.
}
return this.Ok(asyncEnumerable());
}

测试代码

curl --location --request GET 'http://localhost:5000/asyncEnumerable-cancel'
# response code is 200
curl --location --request GET 'http://localhost:5000/asyncEnumerable-cancel' --header 'Accept-Charset: utf-16'
# response code is 500

显然这不是一个合理的 Behavior.

  • 不同的编码响应结果不一样
  • 明明抛出异常了, 但是utf-8还能收到200 ok的response http code

产生这个Bug的代码

SystemTextJsonOutputFormatter 对应的是用 return this.Ok(object)返回的Case

SystemTextJsonResultExecutor 对应的是用 return new JsonResult(object)返回的case

当然, 其他的实现方式或者关联代码是否也有这个Bug我就没有验证了. 以及产生这个Bug的原因就不多说了. 可以看看这2个文件的commit logs.

//核心代码就是这么点. try-catch吞掉了这个Exception

if (selectedEncoding.CodePage == Encoding.UTF8.CodePage)
{
try
{
await JsonSerializer.SerializeAsync(responseStream, context.Object, objectType, SerializerOptions, httpContext.RequestAborted);
await responseStream.FlushAsync(httpContext.RequestAborted);
}
catch (OperationCanceledException) { }
}

目前状况

昨天在 dotnet/aspnetcore/issues提交了一个issues, 等待官方的跟进.

如何手动修复这个Bug

如果是return new JsonResult(object), 我们可以用一个自己修复的SystemTextJsonResultExecutor替换框架自身的.

框架自身的是这么注册的: services.TryAddSingleton<IActionResultExecutor<JsonResult>, SystemTextJsonResultExecutor>();

如果你用的是return this.Ok(object)方式, 那么可以照着下面的代码来,

第一步, 首先从SystemTextJsonOutputFormatter copy 代码到你的本地.

然后修改构造函数并吧导致这个Bug的try-catch结构删掉即可.

// 构造函数中改动代码
public HookSystemTextJsonOutputFormatter(JsonSerializerOptions jsonSerializerOptions)
{
SerializerOptions = jsonSerializerOptions; SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json").CopyAsReadOnly());
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/json").CopyAsReadOnly());
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/*+json").CopyAsReadOnly());
} // WriteResponseBodyAsync 方法中改动代码
var responseStream = httpContext.Response.Body;
if (selectedEncoding.CodePage == Encoding.UTF8.CodePage)
{
await JsonSerializer.SerializeAsync(responseStream, context.Object, objectType, SerializerOptions, httpContext.RequestAborted);
await responseStream.FlushAsync(httpContext.RequestAborted);
}

第二步, 用我们自己改造过的SystemTextJsonOutputFormatter替换系统自己的

//用IConfigureOptions方式替换我们的自带SystemTextJsonOutputFormatter.
public class MvcCoreMvcOptionsSetupWithFixedSystemTextJsonOutputFormatter : IConfigureOptions<MvcOptions>
{
private readonly IOptions<JsonOptions> jsonOptions; public MvcCoreMvcOptionsSetupWithFixedSystemTextJsonOutputFormatter(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions;
} public void Configure(MvcOptions options)
{
options.OutputFormatters.RemoveType<SystemTextJsonOutputFormatter>();//删除系统自己的
options.OutputFormatters.Add(HookSystemTextJsonOutputFormatter.CreateFormatter(this.jsonOptions.Value));//替换为我们自己的
}
}

// 然后在Startup.ConfigureServices的最后应用我们的更改

services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetupWithFixedSystemTextJsonOutputFormatter>());

后记

Ok, 到这里就结束了, 如果后续官方修复了这个bug, 那我们只要删除上面增加的代码即可.

开始写的时候本想多介绍一些关于ActionResult(JsonResult, ObjectResult), ObjectResult的内容格式协商, 以及在ObjectResult上的一些设计. 临到头了打不动字了, 也不想翻源代码了, 最重要的还是懒. 哈哈.

所以这个任务就交给搜索引擎吧... 搜索了一下有不少讲这个的, 啊哈哈.

[Net 6 AspNetCore Bug] 解决返回IAsyncEnumerable<T>类型时抛出的OperationCanceledException会被AspNetCore 框架吞掉的Bug的更多相关文章

  1. 【VS调试】C#读写Windows 7注册表时抛出“不允许所请求的注册表访问权”的解决办法

    原文:[VS调试]C#读写Windows 7注册表时抛出"不允许所请求的注册表访问权"的解决办法 项目 - 属性 - 安全性,"使用ClickOnce",修改a ...

  2. JMS之——ActiveMQ时抛出的错误Could not connect to broker URL-使用线程池解决高并发连接

    转载请注明出处:http://blog.csdn.net/l1028386804/article/details/69046395 解决使用activemq时抛出的异常:javax.j ms.JMSE ...

  3. Android ADT插件更新后程序运行时抛出java.lang.VerifyError异常解决办法

    当我把Eclipse中的 Android ADT插件从21.1.0更新到22.0.1之后,安装后运行程序抛出java.lang.VerifyError异常. 经过调查,终于找到了一个有效的解决办法: ...

  4. 执行Socket socket = new Socket(ip, port);时抛出个异常:android.os.NetworkOnMainThreadException解决办法

    首先,确认你的android版本是4.0之后再用此方法解决,因为在4.0之后在主线程里面执行Http请求才会报这个错,也许是怕Http请求时间太长造成程序假死的情况吧.Android在4.0之前的版本 ...

  5. mixare的measureText方法在频繁调用时抛出“referencetable overflow max 1024”的解决方式

    这几天在搞基于位置的AR应用,採用了github上两款开源项目: mixare android-argument-reality-framework 这两个项目实现机制大致同样.我选取的是androi ...

  6. C#解决关闭多线程的form主窗体时抛出ObjectDisposedException 异常

    一.现象: 我在主窗体新建线程,使用子线程来处理接收到的数据,并且更新窗体显示内容,但关闭主窗体程序之后就程序就报错,如下所示: 二.分析问题: 由于新建线程的处理函数里边是一直死循环处理数据,虽然窗 ...

  7. django在启动时抛出Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 解决办法

    1.适用场景 在启动某个服务的时候,比如python中django启动的时候8000端口被占用,导致无法启动服务. 2.解决办法 通过命令行找出端口对应的PID进程 C:\Users\micha> ...

  8. CAD调试时抛出“正试图在 os 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码”异常的解决方法

    这些天重装了电脑Win10系统,安装了CAD2012和VS2012,准备进行软件开发.在调试程序的时候,CAD没有进入界面就抛出 “正试图在 os 加载程序锁内执行托管代码.不要尝试在 DllMain ...

  9. 解决input为number类型时maxlength无效的问题

    使用input数字number类型的时候maxlength无效,假设需要控制输入数量为18,可以用以下方式: 无效: <input type="text"  maxlengt ...

随机推荐

  1. bzoj1341 名次排序问题rank sorting(dp,考虑到对未来的贡献)

    QWQ啊 这个题可以说是我目前碰到过的最难理解的dp之一了. 题目大意: 已知参赛选手的得分,你的任务是按照得分从高到底给出选手的排名.遗憾的是,保存选手信息的数据结构只支持 一种操作,即将一个选手从 ...

  2. Java(8)详解Random使用

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201556.html 博客主页:https://www.cnblogs.com/testero ...

  3. dubbo注册中心占位符无法解析问题

    dubbo注册中心占位符无法解析问题 1.背景 最近搞了2个老项目,想把他们融合到一起.这俩项目情况简介如下: 项目一:基于SpringMVC + dubbo,配置读取本地properties文件,少 ...

  4. Vue3学习(八)之 Vue CLI多环境配置

    一.前言 这里相对于之前就没那么麻烦了,通俗点说就是使用配置文件来管理多环境,实现环境的切换. 二.实现切换 1.增加开发和生产配置文件 在web的根目录下,创建开发环境切换配置文件.env.dev, ...

  5. Beta阶段第二次会议

    时间:2020.5.18 工作进展 姓名 工作 难度 完成度 ltx 1.在开小程序开发文档,学习相关知识 轻 85% xyq 1.完成活动场地申请可视化代码(耗时半天) 中 100% lm 1.设计 ...

  6. spring cloud ribbon的使用

    上节我们学会了如何搭建一个eureka server服务,本节我们使用ribbon来实现服务间的调用. 前置条件: 1.创建几个工程 eureka-server             |- 服务注册 ...

  7. Noip模拟61 2021.9.25

    T1 交通 考场上想了一个$NPC$.应该吧,是要求出图里面的所有可行的不重复欧拉路 无数种做法都无法解出,时间也都耗在这个上面的,于是就考的挺惨的 以后要是觉得当前思路不可做,就试着换一换思路,千万 ...

  8. QEvent

    QEvent类是所有事件类的基类,每一个对象都包含事件参数.Qt的主事件循环(QCoreApplication::exec())从事件队列中接收本地窗口系统的事件,并将它们翻译成QEvent,将这些事 ...

  9. Python课程笔记(十)

    不陌生,之前学习一个开源SpringBoot项目,Mysql5.5更换到5.7搞得头疼. 数据库连接的坑之前写的IDEA系列连接会遇到的问题.课程代码 今天上课就主要学习了python如何连接mysq ...

  10. 用C++实现的数独解题程序 SudokuSolver 2.6 的新功能及相关分析

    SudokuSolver 2.6 的新功能及相关分析 SudokuSolver 2.6 的命令清单如下: H:\Read\num\Release>sudoku.exe Order please: ...