使用IRO.Mvc.MvcExceptionHandler处理ASP.NET异常



如果您是c#后端开发人员,则可能迟早需要找到一种统一的方式来处理特殊情况。 尽管即使您对答案中的500代码感到满意,但本文仍将有助于改进您的方法,而不会强制任何内容进行重写。

我们将讨论ASP.NET库,该库使您可以尽可能优雅地解决此问题。 对于那些懒于阅读长篇文章的人-自述文件和库本身在这里这里是一个示例。 可以在nuget.org上获得,只有它能使任何人受益,我才会感到高兴。 因此,让我们继续进行代码。 首先,让我们看一下替代方案。

可能想到的第一件事是创建一个DTO(数据传输对象)以处理异常,在控制器中捕获一个异常(尽管没有必要将其作为异常,可以检查null或类似的东西),在DTO中填写数据并将其发送给客户端。 该方法的代码可能如下所示:

public IActionResult Get() { try { //Code with exception. } catch (Exception ex) { return new JsonResult( new ErrorDto { IsError = true, Message = ex.Message }); } } 

另一个选择是为此使用HTTP状态代码。

 public IActionResult Get() { try { //Code with exception. } catch (Exception ex) { return BadRequest(); } } 

这是一种相当普遍的做法,但有其缺点:使用标准代码之一很难描述情况的本质,这就是为什么即使在同一系统上也可以不同地解释同一代码的原因,并且它也为开发人员提供了很少的调试信息。

在这里,有些甚至可能开始以不同的比例结合这两种方法。 他们会忘记发送DTO的某个地方,将不会发送代码的地方,或者将发送错误的代码,但是通常在某个地方,它将使用错误的json设置进行序列化,并且不会返回所需的内容。

面对上述情况,许多人正在尝试使用app.UseExceptionHandler();解决此问题 通过处理异常。 这是一个很好的尝试,但是它不会让您轻易忘记该问题。 首先,您仍然会面临为例外选择DTO的问题。 其次,这样的处理程序将不允许处理从控制器返回的http错误代码,因为 没有发生异常。 第三,以这种方式解决错误分类问题很不方便,您将必须编写大量代码以将消息,http代码或某些内容附加到每个异常。 第四,您失去了使用AspA DeveloperExceptionPage的机会,这对于调试非常不便。 即使您以某种方式解决了该问题,该项目的所有开发人员也将必须严格遵循规范,针对异常构建错误处理,不要返回其DTO,否则api中的错误可能因方法而异。

选定的异常处理选项


在展示IRO.Mvc.MvcExceptionHandler如何允许您处理异常之前,首先我将描述如何看待理想的异常处理。 为此,我们建立了许多要求:

  1. 它应该是DTO,但我们也不拒绝http代码,因为 对于许多错误,它们仍然非常适合,可以在任何地方使用,也可以在您必须支持的旧项目中使用,并且它们只是通用的。 标准DTO将包括IsError字段(该字段允许在客户端上进行通用错误处理),还应包含ErrorKey字符串错误代码,开发人员仅通过查看即可立即识别出该错误代码,并提供更多信息。 此外,如有必要,您可以添加指向该错误说明的页面链接。
  2. 这一切都在产品中。 在开发模式下,此DTO应该返回堆栈跟踪,请求数据:cookie,标头,参数。 展望未来,本文中描述的中间件甚至返回到生成的DeveloperExceptionPage的链接,该链接使您可以以方便的形式观看异常异常,但稍后会介绍更多。
  3. 开发人员可以将异常,http错误代码和ErrorKey绑定在一起。 这意味着,如果他从控制器发送403代码,那么如果开发人员将特定的ErrorKey附加到该代码,则将返回带有该代码的DTO。 反之亦然,如果发生UnauthorizedAccessException,它将被绑定到http代码和ErrorKey。

这是库中使用的默认格式:

 { "__IsError": true, "ErrorKey": "ClientException", "InfoUrl": "https://iro.com/errors/ClientException" } 

我必须马上说,将数据传输到客户端的形式可以是任何形式,这只是其中一种选择。

IRO.Mvc.MvcExceptionHandler


现在,我将展示如何通过编写IRO.Mvc.MvcExceptionHandler库为自己解决此问题。

就像任何其他中间件一样,我们在Startup类中连接异常处理程序。

 app.UseMvcExceptionHandler((s) => { //Settings... }); 

在要委派的委托内部,我们需要配置中间件。 必须将异常映射(绑定)到http代码和ErrorKey。 以下是最简单的设置选项。

  s.Mapping((builder) => { builder.RegisterAllAssignable<Exception>( httpCode: 500, errorKeyPrefix: "Ex_" ); }); 

正如我向那些不习惯处理异常的最懒惰的核心开发人员所做的承诺一样,无需执行其他任何操作。 此代码将使用代码500将ASP.NET管道中的所有异常绑定到通用DTO,并且异常名称将被写入ErrorKey。

值得理解的是,RegisterAllAssignable方法不仅注册指定类型的异常,还注册其所有后代。 如果仅要将有关特定异常的信息发送给客户端,则完全合理的决定是创建ClientException并仅映射它。 同时,如果为ClientException设置一个http代码,并为其后继SpecialClientException设置另一个http代码,则代码SpecialClientException将用于其所有后代,而忽略ClientException设置。 所有这些都已缓存,因此不会出现性能问题。

您可以针对特定的异常微调并注册您的ErrorKey和http代码:

  s.Mapping((builder) => { //By exception, custom error key. builder.Register<ArgumentNullException>( httpCode: 555, errorKey: "CustomErrorKey" ); //By http code. builder.Register( httpCode: 403, errorKey: "Forbidden" ); //By exception, default ErrorKey and http code. builder.Register<NullReferenceException>(); //Alternative registration method. builder.Register((ErrorInfo) new ErrorInfo() { ErrorKey = "MyError", ExceptionType = typeof(NotImplementedException), HttpCode = 556 }); }); 

除了映射之外,还值得配置中间权重。 您可以指定json序列化设置,您的站点的地址,错误描述页面的链接,通过IsDebug的中间件的操作模式,未处理异常的标准http代码。

  s.ErrorDescriptionUrlHandler = new FormattedErrorDescriptionUrlHandler("https://iro.com/errors/{0}"); s.IsDebug = isDebug; s.DefaultHttpCode = 500; s.JsonSerializerSettings.Formatting = Formatting.Indented; s.Host="https://iro.com"; s.CanBindByHttpCode = true; 

最后一个属性指示是否可以通过http代码绑定DTO。
您还可以指定如何处理带有内部异常的情况,例如TaskCanceledException带有由于.Wait()而记录的内部错误。 例如,这是一个标准解析器,它从此类异常中取出内部异常并已对其进行处理:

  s.InnerExceptionsResolver = InnerExceptionsResolvers.InspectAggregateException; 

如果您需要微调序列化,则可以设置FilterAfterDTO方法。 返回true可禁用标准处理并根据需要序列化errorContext.ErrorDTO。 可以访问HttpContext和错误本身。

  s.FilterAfterDTO = async (errorContext) => { //Custom error handling. Return true if MvcExceptionHandler must ignore current error, //because it was handled. return false; }; 

DeveloperExceptionPage和调试模式的其他优点


我们已经确定了设置,现在让我们确定如何调试所有设置。 在DTO产品中,答案很简单,上面已经显示了答案,现在,我将展示相同的DTO在调试模式下的外观:



如您所见,这里有很多信息,其中包括stackrace和请求数据。 但是,只需跟随DebugUrl字段中的链接并查看错误数据而不会增加负担,这甚至更加方便:



实现此功能相当困难,因为 根本不打算将DeveloperExceptionPage供第三方开发人员使用。 最初,无法在具有不同会话的浏览器中打开链接,重新启动后内容将不再显示。 所有这些都只能通过缓存此Middlever的html响应来解决。 现在,如果您使用共享的专用服务器,则至少可以将异常链接传递给队友。

结论


我希望阅读本文的开发人员为自己找到一个有趣且有用的工具。 对我而言,本文在某种程度上检验了其发展和有关其的文章的有用性。 我有一些现成的更酷的项目,我想告诉Habr社区。

Source: https://habr.com/ru/post/zh-CN469731/


All Articles