65.9K
CodeProject 正在变化。 阅读更多。
Home

处理基于 WCF 的应用程序中的 SOAP 异常

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2013年8月30日

CPOL

15分钟阅读

viewsIcon

40039

downloadIcon

419

使用自定义异常处理程序组件

摘要

本文档提供了关于在 WCF 应用程序中处理外部 Web 服务抛出的 SOAP 异常的一种方法的见解。对于 WCF 应用程序来说,处理来自外部 SOAP 或 ASMX Web 服务的这些异常是具有挑战性的,因为你无法控制它们。

通常,在分布式应用程序中,远程发生在外部系统上的异常必须穿过进程或机器边界,才能到达客户端应用程序。在 SOA(面向服务架构)的情况下,这将是服务边界,它可能存在于同一进程或不同进程以及/或机器边界中。除了这些分发边界之外,还可能存在平台和技术边界,从而需要可互操作的通信。服务提供定义明确的边界,并使用契约中定义的序列化消息进行通信。

下图显示了一个 .NET 分布式应用程序的示例架构

图 1 - .Net 分布式应用程序架构图

2 SOAP 故障 – 概述

为了将异常从服务报告给调用者,异常也必须被序列化为标准格式并包装在消息中。SOAP 故障是一种用于在分布式应用程序之间传输异常的标准格式,并且独立于任何特定于技术的异常,如 CLR、Java 或 C++ 异常。它提供了有关故障内容的详细信息,例如错误代码、错误消息等,并指导平台和技术应如何处理它们。SOAP 规范定义了一种标准的 XML 格式,通常包括头部和正文部分。SOAP 规范有两个版本 – SOAP 1.1 和 SOAP 1.2。下表提供了 SOAP 1.1 和 SOAP 1.2 故障之间的区别。尽管元素名称不同,但这些版本的内容相似。

SOAP 1.1 SOAP 1.2 描述
faultCode 代码 必需。标准代码,提供有关错误的更多信息。SOAP 1.1 中预定义的错误代码值包括
  • VersionMismatch — SOAP 包装器元素中定义了无效的命名空间。SOAP 包装器必须符合 *http://schemas.xmlsoap.org/soap/envelope* 命名空间。
  • MustUnderstand — 处理方未理解 SOAP 头部条目。
  • Client — 消息格式不正确或缺少信息。
  • Server — 服务器出现问题,导致消息未能处理。
faultString 原因 必需。对错误的字符串解释。
faultactor 角色 可选。描述故障来源的 URI
detail 详细信息 可选。有关异常的更多信息
节点 可选。描述导致失败的节点的 URI

3 为什么需要异常处理组件?

WCF 将 SOAP 故障消息封装到 FaultException(*System.ServiceModel* 命名空间)中。为了确保客户端仅了解异常的必要详细信息,一个好的方法是通过我们自己的自定义异常来处理。当您

  • 需要一种通用的方法来处理来自不同接口系统的异常
  • 了解来自远程 Web 服务的错误消息或异常的类型
  • 不想突然结束会话并导致客户端出错
  • 需要灵活地自行决定如何处理异常,即转换错误消息或按原样显示它们

在典型的分布式系统中,外部方或接口系统会发布客户端在响应各种测试用例时可能遇到的错误。这些错误必须首先分为

  • 业务异常
    • 这些是由于业务案例失败而产生的预期异常。大多数情况下,这些异常都可以不言自明。
    • 例如,在查询不存在的数据时,某些服务可能会抛出“数据不存在”之类的异常。
    • 技术异常
    • 这些是技术错误,不允许业务案例的执行。这些异常可能不足以让用户了解出了什么问题。因此,可以选择按原样传递它们,或者将它们转换为更有意义的消息。
    • 示例包括执行环境问题、编程错误等。

异常抛出的方式完全取决于远程 Web 服务的设计。它们可以选择在简单的业务案例失败时采取异常路径,如上面的示例所示。

此异常处理组件的设计同时考虑了业务异常和技术异常。设计具有足够的扩展性,可以在稍后配置一组参数。以下各节详细介绍了此组件的设计。

作为此组件一部分提供的代码示例通常具有与 .Net 分布式应用程序的架构图相同的架构,如图 1 所示。正在讨论的系统通常与两个外部系统进行交互,这两个系统又公开了 WCF 应用程序所消耗的 Web 服务。

4 架构

下图的 UML 图描绘了一个 .NET 分布式应用程序的逻辑架构,该架构用于演示此异常处理组件的用法。

图 2 - 逻辑架构图

正在讨论的异常处理组件将是基础设施架构层的一部分。作为此组件用法一部分提供的示例代码实现了系统的此逻辑架构。下表提供了每个层及其在 Visual Studio 解决方案中相应代码工件(项目)的映射的简要说明。

图层 描述 醒悟
演示 包含系统的所有表示层和用户界面相关方面以及客户端处理。 WinFormApplicationInfrastructure.Wcf
Application 包含所有业务逻辑和服务器端进程。 WCFService.ServiceContractsWCFService.MessageContractsWCFService.DataContracts
WCFService.FaultContractsWCFService.ServiceImplementationWCFService.Service
InterceptorWCFService.BusinessLogicServiceModel
定义域 包含所有业务领域实体对象。对象是数据的业务模型(领域)表示。 WCFService.BusinessEntities
资源访问 包含访问相关数据存储以及外部服务的所有组件。 WCFService.ServiceAgentsDataAccess
Data 包含所有系统数据存储。 IntegrationSystemDb
基础结构 包含提供基础设施服务的所有框架组件。 WCFSoapFaultHandler
集成 包含与外部系统通信所需的组件。 未实现

5 逻辑架构层的实现

下图显示了上一节中讨论的各种架构层中的项目快照。这些项目构成了作为异常处理组件一部分交付的代码工件。

5.1 表示层

表示层通常包含用于与用户交互的表单应用程序以及 Infrastructure.Wcf 项目,该项目使我们能够在客户端自定义服务或终结点行为。

图 3 - 表示层

5.2 应用层

应用层提供所有业务逻辑执行和服务器端处理。WCF 服务充当此层的网关。

5.2.1 服务层

服务层将业务逻辑公开为服务。它是业务逻辑层的抽象。服务使用消息契约与域层和表示层进行交互。WSSF(Web 服务软件工厂)用于建模服务。

下图显示了用于建模 WCF 服务的服务契约、消息契约、数据和故障契约、服务拦截器和服务模型的快照。

图 4 - 服务层

5.3.1 业务逻辑层

业务操作提供业务逻辑执行。下图显示了业务逻辑层中涉及的类。

图 5 - 业务逻辑层

5.3 域层

域层通常包含业务实体。在我们考虑的系统中,没有创建业务实体。尽管如此,该解决方案仍提供了可以将来使用的相应层。

图 6 - 域层

5.4 资源访问层

此层通常包含用于与外部系统通信的服务代理和数据访问层(如果有)。在我们考虑的系统中,服务代理保存了相应外部系统的 Web 服务的代理。

图 7 - 资源访问层

5.5 基础设施层

此层包含提供架构服务的所有框架组件。异常处理组件 WCFSoapFaultHandler 将属于此层。

图 8 - 基础设施层

5.6 数据层

数据层包含实际的数据存储。下图显示了为正在讨论的系统创建的数据库项目的快照。它包含创建数据库所需的所有必需数据库脚本和对象。

图 9 - 数据层

6 数据模型

下图描绘了为该组件设计的物理数据模型。

图 10 - 数据模型

此数据模型涉及 3 个表。

  1. IntegrationSystem

    此表通常保存有关客户端应用程序与之交互并需要处理来自这些系统的异常的各种外部接口系统的详细信息。

  2. IntegrationSystemMessage

    此表包含有关异常的重要详细信息,例如 SOAP 故障的唯一错误代码以及用于处理异常的以下可配置参数。

    1. CmspMessageId – 自定义消息 ID,替换原始异常消息。
    2. Code – SOAP 故障中的错误代码,用于匹配一个或多个特定异常。
    3. Match – 此参数指定是否将异常详细信息与以下选项中的任何一个进行匹配
      1. Exact – SOAP 故障中的错误代码与此表中的 Code 列之间的一对一映射。
      2. Range – 表中的 Code 列将包含一系列错误代码值(例如:25 到 72,表示为‘25_72’)。
      3. All – 对于一个外部系统,可能只有一条记录包含此选项。如果您想为该外部接口系统所有异常应用相同的配置和模式,这将非常有用。
  3. IntegrationSystemMessageDisplayType

    此表保存配置详细信息,以决定如何将异常消息显示给最终用户。它指定是否需要转换故障。它还详细说明了转换的模式。它可以具有以下值。

    1. IntegrationMessage - 不转换 SOAP 故障中的错误消息。
    2. CmspMessage – SOAP 故障中的错误消息被自定义消息完全替换。
    3. CmspMessageWithIntegrationMessage – 对 SOAP 故障中的错误消息进行部分转换,以便自定义消息也包含原始错误消息。
    4. CmspMessageWithIntegrationMessageAndCode – 对 SOAP 故障中的错误消息进行部分转换,以便自定义消息也包含原始错误消息和错误代码。

7 组件的详细设计

异常处理组件旨在处理 SOAP 故障。

图 11 - 异常处理组件设计

通常,当外部 Web 服务抛出 SOAP 异常时,WCF 服务可以选择多种方式来处理异常。它可以由

  • 定义故障契约 - 推荐,当类型已知时。
  • 定义自定义异常 - 推荐,当类型未知时。

正在讨论的异常处理程序选择定义自定义异常。

一旦处理程序组件接收到异常,它就会在 SOAP 故障 XML 中查找其详细信息。根据 SOAP 故障版本,获取异常详细信息会更容易或更复杂。

例如,您可以轻松地从 SOAP 故障 XML 中的 code 和 Reason 节点查找错误代码,前提是异常信息存在于这些节点中。有时,可能需要深入挖掘 SOAP 故障的 detail 部分才能获取有关异常的实际信息。不幸的是,没有简单的方法可以从 FaultException 访问 detail 元素。必须使用 MessageFault 类来与故障消息进行交互并检索其详细信息。

由于 SOAP 故障 XML 的结构取决于外部系统的 SOAP 故障配置,因此可能需要提前了解其结构。一旦确定了模式,就可以使用异常处理组件的不同重载来从 SOAP 故障 XML 的相应节点读取异常信息。

一旦从 SOAP 故障 XML 读取了异常的详细信息,就会在数据库表中查找错误代码,以查看此异常是否需要转换为业务异常。正在使用存储过程来查找数据库表中的此数据。根据在数据库端进行的配置,可以将异常自定义为不同的业务消息,或选择按原样保留。

异常处理组件最终会抛出一个新的自定义异常‘IntegrationException’,其中包含所有必需的转换详细信息(如适用)。上面的类图显示了自定义 IntegrationException 的设计。客户端可以检查异常类型并采取适当的操作向最终用户显示消息。

上面的类图还显示了一个名为‘FaultDetailNodes’的类。创建此类的目的是保存 SOAP 故障的详细信息,特别是 detail 节点。可以通过配置此类,为每个集成点指定要查找的故障详细信息节点的名称、节点顺序等。

8 在 WCF 拦截器中处理 SOAP 异常

异常通常使用应用程序中的 try-catch 块来处理。一种更健壮的处理来自外部 Web 服务的此类 SOAP 异常的方法是在 WCF 拦截器层进行处理。可以为每个集成点创建拦截器行为,以便在发送服务请求之前和接收响应之后检查或修改消息。

在我们考虑的系统中,有两个外部系统 Bis1 和 Bis2,WCF 服务与之交互。这些外部系统公开由 WCF 服务消耗的 Web 服务。作为说明,这些 Web 服务被设计为抛出 SOAP 故障。Bis2 系统设计用于创建 SOAP 故障,其中异常信息存在于 detail 节点中,而 Bis1 系统设计用于抛出简单的 SOAP 故障,而 detail 节点中没有异常信息。

如图 4 - 服务层所示,在 Service Interceptor 项目中创建了两个拦截器行为,每个都对应一个外部系统。

这些服务拦截器配置在 web.config 中,以拦截相应的服务调用。下面展示了这些拦截器是如何配置的示例。

为了明确控制应用程序在生成 SOAP 故障时的行为,并执行 WCF 自定义异常处理和屏蔽,创建了 ExtendedServiceErrorHandler 类,它实现了 IErrorHandler 接口。

下面展示了使用异常处理组件在 WCF 拦截器中处理 SOAP 异常的示例。

namespace WCFService.ServiceInterceptor
{
    /// <summary>
    /// behavior to intercept integration service calls
    /// </summary>
    public class Bis2ServiceInspectorBehavior : 
           BehaviorExtensionElement, IEndpointBehavior, IClientMessageInspector
    {
        private const string ErrorCodeNode = "ErrorCode";
        private const string ErrorMessageNode = "Message";  
        /// <summary>
        /// Read only property to obtain the type of behaviour
        /// </summary>
        public override Type BehaviorType
        {

            get { return typeof(FileNetServiceInspectorBehavior); }
        }

        /// <summary>
        ///  Creates a behavior extension based on the current configuration settings.
        ///  Overridden from base to return the custom behaviour
        /// </summary>
        /// <returns>Tyhe custom behaviour object.</returns>
        protected override object CreateBehavior()
        {
            return new FileNetServiceInspectorBehavior();
        }

        #region IEndpointBehavior Members

        /// <summary>
        /// Implement to pass data at runtime to bindings to support custom behavior.
        /// </summary>
        /// <param name="endpoint">The endpoint to modify.</param>
        /// <param name="bindingParameters">The objects that
        /// binding elements require to support the behavior.</param>
        void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, 
               System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            // No implementation required
            return;
        }
        /// <summary>
        /// Implements a modification or extension of the client across an endpoint.
        /// </summary>
        /// <param name="endpoint">The endpoint that is to be customized.</param>
        /// <param name="clientRuntime">The client runtime to be customized.</param>
        void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(this);
        }

        /// <summary>
        /// Implements a modification or extension of the service across an endpoint.
        /// </summary>
        /// <param name="endpoint">The endpoint that exposes the contract.</param>
        /// <param name="endpointDispatcher">The endpoint dispatcher to be modified or extended.</param>
        void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        { }
        /// <summary>
        /// Implement to confirm that the endpoint meets some intended criteria.
        /// </summary>
        /// <param name="endpoint">The endpoint to validate.</param>
        void IEndpointBehavior.Validate(ServiceEndpoint endpoint)
        { }


        #endregion
                
        #region IClientMessageInspector Members

        /// <summary>
        /// Enables inspection or modification of a message after a reply message
        /// is received but prior to passing it back to the client application.
        /// </summary>
        /// <param name="reply">The message to be transformed
        /// into types and handed back to the client application.</param>
        /// <param name="correlationState">Correlation state data.</param>
        void IClientMessageInspector.AfterReceiveReply(
             ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            if (reply.IsFault)
            {
                //Create detail Nodes
                List<FaultDetailNodes> faultDetailNodesList = new List<FaultDetailNodes>();
                faultDetailNodesList.Add(new FaultDetailNodes(ErrorMessageNode, false, 1));
                faultDetailNodesList.Add(new FaultDetailNodes(ErrorCodeNode, true, 2));

                //Create message fault from Message,
                //create fault exception from message fault and handle the exception
                MessageFault fault = MessageFault.CreateFault(reply, Int32.MaxValue);
                FaultException faultException = new FaultException(fault);
                IntegrationExceptionHandler.HandleSoapFault11(faultException, 
                          Enums.IntegrationSystem.Bis2, faultDetailNodesList);                
            }
        }

        /// <summary>
        /// Enables inspection or modification of a message before a request message is sent to a service.
        /// </summary>
        /// <param name="request">The message to be sent to the service.</param>
        /// <param name="channel">The WCF client object channel.</param>
        /// <returns>
        /// The object that is returned as the <paramref name="correlationState
        /// "/>argument of the <see cref="M:System.ServiceModel.Dispatcher.
        /// IClientMessageInspector.AfterReceiveReply(System.ServiceModel.Channels.Message@,
        /// System.Object)"/> method. This is null if no correlation state is used.
        /// The best practice is to make this a <see cref="T:System.Guid"/>
        /// to ensure that no two <paramref name="correlationState"/> objects are the same.
        /// </returns>
        object IClientMessageInspector.BeforeSendRequest(ref 
          System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {            
            return null;
        }      
        
        #endregion

    }
 }

AfterReceiveReply 方法中的代码片段从消息故障创建故障异常,并使用异常处理组件来处理 soap 故障异常。

9 在客户端处理自定义异常

通过使用此异常处理组件,所有来自外部 Web 服务的 SOAP 异常将在到达客户端之前转换为自定义异常‘IntegrationException。根据自定义异常中的可用配置详细信息,向用户显示适当的错误消息。这些异常的配置详细信息存储在异常对象的 IDictionary 集合中。

下面展示了一个代码片段示例,说明如何将这些异常详细信息转换为面向用户的适当错误消息。

/// <summary>
/// Handles the exception.
/// </summary>
/// <param name="ex">The Exception.</param>
private static void HandleException(Exception ex)
{
     DialogResult result = DialogResult.None;
     if (ex.GetType() == typeof(IntegrationException))
     {
        IntegrationException integrationException = (IntegrationException)ex;
        int messageDisplayType = 
          Convert.ToInt32(GetExceptionData(integrationException.Data, Constant.MessageDisplayType));

        switch (messageDisplayType)
        {
            case (int)Enums.IntegrationSystemMessageDisplayType.IntegrationMessage:
                result = CustomMessageBox.Show(GetExceptionData(integrationException.Data, Constant.ErrorMessageNode));
                return;
            case (int)Enums.IntegrationSystemMessageDisplayType.CmspMessage:
                result = CustomMessageBox.Show(GetExceptionData(integrationException.Data, Constant.CmspMessageId));
                return;
            case (int)Enums.IntegrationSystemMessageDisplayType.CmspMessageWithIntegrationMessage:
                result = CustomMessageBox.Show(GetExceptionData(integrationException.Data, Constant.CmspMessageId),
                                             GetExceptionData(integrationException.Data, Constant.ErrorMessageNode));
                return;
            case (int)Enums.IntegrationSystemMessageDisplayType.CmspMessageWithIntegrationMessageAndCode:
                result = CustomMessageBox.Show(GetExceptionData(integrationException.Data, Constant.CmspMessageId),
                                             GetExceptionData(integrationException.Data, Constant.ErrorCodeNode),
                                             GetExceptionData(integrationException.Data, Constant.ErrorMessageNode));
                return;
            default:
                break;
        }
    }
    else //An unhandled exception like any other exception in the application
        throw ex;
}

10 附录

随此组件提供的代码库将包含以下项目:

10.1 数据库组件

一个名为‘IntegrationSystemDb’的 SQL Server 2008 数据库项目,包含以下数据库组件。

  1. 表创建脚本 - 包含 3 个表(如数据模型中所述)的 CREATE table 脚本。
  2. DB Seed 脚本 – 添加数据模型中涉及的 3 个表的示例数据的脚本。
  3. 存储过程 – 包含异常处理组件使用的存储过程。

您可以选择部署数据库项目以获取最终的部署脚本。

10.2 WCF 服务组件

代码库包含一个 WCF 实现项目 WCFService。此文件夹反过来包含以下源代码:

  • 服务接口
  • 它包含服务、服务契约、服务拦截器、数据契约、故障契约、消息契约和服务模型。Web 服务软件工厂已用于创建服务契约。
  • 业务逻辑

    它包含提供业务逻辑执行的业务操作类。

  • 资源访问
  • 它包含与外部系统通信的服务代理。

10.3 SOAP 异常处理组件

它包含该组件和附加库。Common 项目包含所有通用库,DataAccess 项目包含数据实体。

10.4 外部系统 Web 服务

代码库中包含两个 ASP.NET Web 服务项目 ExternalSystemWSExternalSystem2WS,以演示如何使用异常处理组件来处理这些 Web 服务抛出的 SOAP 异常。

10.5 表示层组件

它包含用于用户界面的 Windows 窗体应用程序以及 Infrastructure.Wcf 项目,用于在客户端拦截 WCF 调用。*该示例 Windows 窗体应用程序演示了此组件的用法。此窗体应用程序发出 WCF 服务调用,该调用进而消耗抛出 SOAP 故障异常的外部系统 Web 服务。*

在典型的分布式环境中,异常处理组件将作为应用程序服务器中的基础设施服务的一部分。SOAP 异常在 WCF 拦截器层捕获。拦截器依次调用异常处理组件来处理此类异常。代码示例中详细介绍了 WCF 拦截器如何调用异常处理组件。

在此示例 Windows 应用程序中,消息存储在资源文件中。窗体应用程序还包含自定义消息框和 MessageUtil 的其他服务,用于从资源文件中处理消息。

示例 Windows 窗体应用程序的类图如下所示。

图 12 - 用户界面类图

摘要

本文涵盖了在 WCF 应用程序中处理外部服务抛出的 SOAP 异常的方法。感谢阅读本文。请花时间发表您的评论/建议。

参考文献

© . All rights reserved.