如何在 .NET Core 上隐藏 System.Exception 错误?
Posted
技术标签:
【中文标题】如何在 .NET Core 上隐藏 System.Exception 错误?【英文标题】:How can I hide System.Exception errors on .NET Core? 【发布时间】:2022-01-03 06:48:43 【问题描述】:我现在尝试使用 .NET Web API 改进自己,并尝试在 Swagger 中返回自定义错误。但是当返回这个自定义错误时,我可以看到错误在哪一行。我该怎么做才能防止这种情况发生?
public async Task<BookCreateDTO> CreateBook(BookCreateDTO bookCreateDto)
if (await _context.Books.AnyAsync(x => x.Name == bookCreateDto.Name))
throw new BookExistException("Book already exist");
var book= _mapper.Map<Book>(bookCreateDto);
_context.Books.Add(book);
await _context.SaveChangesAsync();
return book;
我应该怎么做才能在 Swagger 响应中仅看到此异常消息? 感谢您的帮助。
【问题讨论】:
异常应该是异常的:不要为非异常错误抛出异常。 【参考方案1】:异常应该是异常的:不要为非异常错误抛出异常。
我不建议在 C# 操作方法返回类型中指定 Web 服务的响应 DTO 类型,因为它会限制您的表现力(正如您所发现的那样)。
改为使用IActionResult
或ActionResult<T>
记录默认(即HTTP 2xx)响应类型,然后在[ProducesResponseType]
属性中列出错误DTO 类型及其相应的HTTP 状态代码。
这也意味着每个响应状态代码应该只与一个单个 DTO 类型相关联。
虽然 Swagger 的表达力不足以让您说“如果响应状态是 HTTP 200,那么响应正文/DTO 是 one-of DtoFoo
, DtoBar
, DtoQux
",在实践中,精心设计的 Web 服务 API不应该表现出这种响应 DTO 多态性。
如果不知道,那么客户端应该如何仅从 HTTP 标头中知道类型是什么? (好吧,您可以将完整的 DTO 类型名称放在自定义 HTTP 响应标头中,但这会带来其他问题...)
对于错误情况,将错误添加到ModelState
(如果可能,使用Key
)和let ASP.NET Core handle the rest for you 和ProblemDetails
。
如果您确实抛出了异常,那么 ASP.NET Core can be configured to automatically render it as a ProblemDetails
- 或者它可以显示 DeveloperExceptionPage
- 或完全其他的东西。
我注意到不在控制器内为非异常异常抛出异常的一个很好的理由是,您的日志框架可能会选择在 ASP.NET Core 的管道中记录有关未处理异常的更多详细信息,这将导致您的日志中出现无用的无关条目,从而使您更难找到需要修复的“真实”异常。
使用 [ProducesResponseType]
记录使用的 DTO 及其相应的 HTTP 状态代码:这在使用 Swagger/NSwag 生成在线文档和客户端库时非常有用。
另外:不要将 EF 实体类型用作 DTO 或 ViewModel。
原因 1:当响应(带有 EF 实体对象)被序列化时,具有延迟加载属性的实体将导致您的整个数据库对象图被序列化(因为 JSON 序列化程序将遍历每个对象)。
原因 2:安全!如果您直接接受 EF 实体作为输入请求正文 DTO 或 html 表单模型,则用户/访问者可以任意设置属性,例如例如,POST /users
和 accessLevel: 'superAdmin'
。虽然您可以排除或限制可以通过请求设置对象的哪些属性,但它只会增加项目的维护工作量(因为它是程序中的另一个非本地、手动编写的列表或定义,您需要确保保留在- 与其他一切同步。
原因 3:自我记录意图:实体类型用于进程内状态,而不是作为通信合同。
原因 4:实体类型的成员永远不会完全在 DTO 中公开。
例如,您的User
实体将具有Byte[] PasswordHash
和Byte[] PasswordSalt
属性(我希望...),显然这两个属性必须从不 暴露;但在用于编辑用户的用户 DTO 中,您可能需要不同的 成员,例如 NewPassword
和 ConfirmPassword
- 它们根本不映射到 DB 列。
原因 5:在原因 4 的相关说明中,使用实体类作为 DTO 会自动将 Web 服务 API 的精确设计绑定到数据库模型。
假设有一天您绝对需要对数据库设计进行更改:也许有人告诉您业务需求发生了变化;这是正常的,并且一直在发生。
假设 DB 设计更改是从每个客户只允许 1 个地址(因为街道地址与客户存储在同一个表中)到允许客户有多个地址(即街道地址列移动到不同的表)...
...所以您更改数据库、运行迁移脚本并部署到生产环境 - 但突然间,您的所有 Web 服务客户端都停止工作,因为他们都认为您的 Customer
对象具有内联街道地址字段,但现在它们不见了(因为您的 Customer
EF 实体类型不再有街道地址列,这在 CustomerAddress
实体类中结束了)。
如果您一直使用专门用于Customer
对象的专用 DTO 类型,那么在更新应用程序设计的过程中,您会注意到由于 C# 编译时类型,构建会更快地中断(而不是不可避免地更晚!) - 签入您的 DTO 到实体(以及实体到 DTO)的映射代码 - 这是一个好处。
但是主要的好处是它允许您完全抽象出您的底层数据库设计 - 因此,在我们的示例中,如果您的远程客户端依赖于内联的客户地址信息,那么您的客户 DTO 仍然可以模拟通过将 first 客户地址内联到原始客户 DTO 中,当它向远程客户端呈现其 JSON/XML/Protobuf 响应时,旧设计。这样可以节省时间、麻烦、精力、金钱、压力、投诉、解雇、不必要的殴打、严重的身体伤害和预约牙科保健员。
无论如何,我已经修改了您发布的代码以遵循上述指导:
我添加了[ProducesResponseType]
属性。
我认为指定默认响应类型 BookCreateDTO
两次是多余的(在 [ProducesResponseType]
和 ActionResult<BookCreateDTO>
中 - 您应该能够删除其中任何一个而不影响 Swagger 输出。
为了安全起见,我添加了明确的[FromBody]
。
如果“未使用书名”检查失败,它会在 ASP.NET 的 stock BadRequest
响应中返回模型验证消息,该响应呈现为 IETF RFC 7807 响应,aka @ 987654353@ 而不是抛出异常,然后希望您将 ASP.NET Core 管道(在 Configure()
中)配置为将其作为 ProblemDetails
处理,而不是调用调试器或使用DeveloperExceptionPage
。
请注意,在名称冲突的情况下,我们希望返回 HTTP 409 Conflict 而不是 HTTP 400 Bad Request,因此 conflictResult.StatusCode = 409;
将被覆盖。
最终响应是通过 AutoMapper 和 Ok()
从新的 BookCreateDTO
实例生成的,而不是序列化您的 Book
实体对象。
[ProducesResponseType(typeof(BookCreateDTO), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)]
public async Task< ActionResult<BookCreateDTO> > CreateBook( [FromBody] BookCreateDTO bookCreateDto )
// Does a book with the same name exist? If so, then return HTTP 409 Conflict.
if( await _context.Books.AnyAsync(x => x.Name == bookCreateDto.Name) )
this.ModelState.Add( nameof(BookCreateDTO.Name), "Book already exists" );
BadRequestObjectResult conflictResult = this.BadRequest( this.ModelState );
// `BadRequestObjectResult` is HTTP 400 by default, change it to HTTP 409:
conflictResult.StatusCode = 409;
return conflictResult;
Book addedBook;
addedBook = this.mapper.Map<Book>( bookCreateDto );
_ = this.context.Books.Add( book );
_ = await this.context.SaveChangesAsync();
BookCreateDTO responseDto = this.mapper.Map<BookCreateDTO >( addedBook );
return this.Ok( responseDto );
【讨论】:
以上是关于如何在 .NET Core 上隐藏 System.Exception 错误?的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET Core Web API - 如何在中间件管道中隐藏 DbContext 事务?
如何在 .Net Core 中替换 System.Net.IPAddress.Any 并监听所有网络适配器
Asp.net Core 如何记录 System.AccessViolationException
Httpclient在.net core3.1上成功运行,但更新到.net 5中连接超时(System.Net.Sockets.SocketException (10060)) C#