如何在 .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 类型,因为它会限制您的表现力(正如您所发现的那样)。

改为使用IActionResultActionResult&lt;T&gt; 记录默认(即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[] PasswordHashByte[] PasswordSalt 属性(我希望...),显然这两个属性必须从不 暴露;但在用于编辑用户的用户 DTO 中,您可能需要不同的 成员,例如 NewPasswordConfirmPassword - 它们根本不映射到 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&lt;BookCreateDTO&gt; 中 - 您应该能够删除其中任何一个而不影响 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

在 net core 2.2 中隐藏招摇的不良响应示例模型

Httpclient在.net core3.1上成功运行,但更新到.net 5中连接超时(System.Net.Sockets.SocketException (10060)) C#

我如何在 ASP.Net Core 2.1 mvc 应用程序中包含 System.Identitymodel 4.0