通过自定义异常冒泡报错

Error reporting through custom exception bubbling

我有一系列 C# applications/services 通过 WCF 进行通信,还有一个 MVC Web 应用程序有时可以沿着这条服务链发起操作。

我想要的是一种 安全 方式来报告沿着这个链发生的 有用 错误,一旦网络应用程序用户发起了一个操作这导致了一个例外。我当前的解决方案是尽快捕获任何异常,然后使用最能识别导致问题的自定义消息创建 CustomException。此 CustomException 将通过 try/catch 块向上传播服务链(使用 [FaultContract (typeof(SerialisedCustomException)] 在服务边界处序列化)。在任何时候捕获异常时,如果它是 CustomException 类型,它将被重新抛出。否则将当场创建 CustomException,如上所述,然后进一步抛出。

作为一个用例,我们假设登录到我的 Web 应用程序 A 的用户执行一个操作,该操作告诉服务 B 告诉服务 C 在 C 的文件系统上创建一个文件。 (实际操作比这更复杂,而且不止一件事会出错,但我认为这是一个足够的例子)

如果在此过程中出现任何问题,我希望用户能够以一种可以解决问题的方式得到通知。

通过 safe,我只想向用户显示我专门创建的错误(因此我使用了 CustomException),而不是其他任何内容(如果是系统异常,说 UnauthorizedAccessException,被抛出,我将把它包装在我自己的 CustomException 中,只向最终用户显示我自己的 CustomException 的消息。我不希望向用户显示任何可能暴露实现细节的异常消息。

通过有用,我想向用户展示适当的消息和建议,例如'make sure you have the right permissions etc.',而不是到 'Service B says: Internal Server Error'.(这些将在 CustomException 首次抛出时设置,并且不会在进一步 catch/throws 上更改。如果启用调试,我将还显示堆栈跟踪,但在生产中我只会显示我的安全自定义消息)。

我的方法的问题是,我在我的服务方法周围写了越来越多的 try/catch 块,在它们调用的方法中等等,最奇怪的是我觉得这不是正确的方法,还有更好、更 efficient/less 臃肿的解决方案。

有什么方法可以解决这个问题,同时避免将所有内容包装在 try/catch 块中的开销吗?

编辑:添加了报告错误而不是说“出了点问题,我们正在调查”的理由。

我的网络应用程序可以在较低级别的服务上发起复杂的(和可定制的)操作。这些可能会因多种原因而失败,这些原因可以通过调整该操作的自定义来解决。

举个例子,假设我有一个将用户输入的源文件复制到用户输入的目的地(在机器 C 上,或在机器 C 可访问的网络驱动器上)的操作。由于各种原因(文件未找到、网络故障、权限不足、磁盘空间不足 space 等),此操作可能会失败,但一些原因可以通过用户更改该自定义操作来解决,只要他们能够首先看到失败的原因。

根据我的经验,异常对于向最终用户传达信息很少有用。 'make sure you have the right permissions etc.' 听起来更像是一种业务规则类型的处理,而不是异常类型。

我也不经常遇到需要通过层传播异常的情况。在某些情况下,这可能会有用,但大多数情况下,它们可以在被捕获的站点记录下来。

而且 caught 子句通常很少使用,也就是说,Web 应用程序只有一个全局异常处理程序的情况并不少见,仅此而已。

异常的主要目的是告诉支持人员出了什么问题。这主要是 a) 基础设施条件(服务器关闭、数据库关闭等)或 b) 代码中的错误。其他所有内容通常都属于业务错误,并按此处理。

在某些情况下,上述方法还不够,例如,如果您正在使用您无法控制的服务或库,并且此 server/library 使用异常来传达结果一个手术。但这很少见。

我设计异常处理程序的方式是始终从单个全局处理程序(每个应用程序)开始。然后,如果在测试期间发现异常,我会调查它们并尝试对其进行分类。这个异常是因为错误吗?然后我修复了这个错误。这个例外是因为基础设施条件吗?然后可能需要记录它,向客户显示适当的消息并可能通知支持人员。在这种情况下,可以保证自定义错误处理程序,但是通常一个通用 "we have a error we are looking into it" 消息和支持通知就足够了,在这种情况下,也不需要自定义错误处理程序。请注意,在所描述的情况下,最终用户除了等待之外无法真正对问题做任何事情。最后,如果异常是由于业务条件引起的,那么我会看看是否可以重新设计代码路径,以便不会因业务类型错误而引发异常。如果是我自己的代码,这不是问题(我不会遇到这种情况),如果是 library/service,那么是的,可能需要将调用包装到 try/catch 中。

作为上述方法的结果,我的代码只有很少的异常处理程序,我喜欢这种方式 - 如果发生灾难,通常会立即清楚堆栈跟踪精确定位的确切位置,并且没有混乱的嵌套要考虑的例外情况。

注意:对于图书馆作者来说,考虑因素可能略有不同

你不需要尝试...捕获所有的东西,你只需要捕获预期的异常将它们包装在 CustomException 中并向它们添加有用的消息,否则它们应该在调用者(或调用者)中被捕获调用者等)方法。 此外,您不需要捕获 CustomException 并重新抛出它。即使你没有抓住它,它也会冒泡。唯一的问题是 WFC 服务仅抛出 FaultExceptions。您只能在最外层调用中捕获这些异常。

要获得有用的异常,您可以简单地提供异常的详细消息,为 CustomException 指定 ErrorCode 属性 或定义嵌套异常,如继承 CustomException 的 CustomException1 和 CustomException2。在 UI 级别,您可以从该错误代码或异常类型中解决用户友好的错误消息。

例如:

Web 应用程序 A:

void UserAction(string input) {
    try {
        serviceB.RespondToAction(input);
    }
    catch (FaultException e) {
       if (e.InnerException is CustomException) {
          DisplayMessageToUser(ResolveExceptionMessage((CustomException)e.InnerException));
       }
    }
    catch (CustomException e) {
          DisplayMessageToUser(ResolveExceptionMessage(e));         
    }
}

服务 B:

void RespondToAction(string input) {
    try {

        SomeOperation(input);
    }
    catch (FormatException e) {
        throw new CustomException("Invalid input format.", e);
    }
    // Note you do not catch CustomException here...

    // Note you do not use try-catch here...
    serviceC.CreateFile();
}

服务 C:

void IOOperation2() {
     try {
         IOOperation();
     }
     catch (IOException e) {
         throw new CustomException("Failed to create file.", e);
     }
     catch (SecurityException) {
         throw new CustomException("Failed to create file.", e);
     }
     // Note you do not catch an ExecutionEngineException here...
}