我的WCF之旅(10):如何在WCF进行Exception Handling-程序员宅基地

技术标签: 网络  runtime  

在任何Application的开发中,对不可预知的异常进行troubleshooting时,异常处理显得尤为重要。对于一般的.NET系统来说,我们简单地借助try/catch可以很容易地实现这一功能。但是对于 一个分布式的环境来说,异常处理就没有那么简单了。按照面向服务的原则,我们把一些可复用的业务逻辑以Service的形式实现,各个Service处于一个自治的环境中,一个Service需要和另一个Service进行交互,只需要获得该Service的描述(Description)就可以了(比如WSDL,Schema和Strategy)。借助标准的、平台无关的通信构架,各个Service之间通过标准的Soap Message进行交互。Service Description、Standard Communication Infrastructure、Soap Message based Communication促使各Service以松耦合的方式结合在一起。但是由于各个Service是自治的,如果一个Service调用另一个Service,在服务提供方抛出的Exception必须被封装在Soap Message中,方能被处于另一方的服务的使用者获得、从而进行合理的处理。下面我们结合一个简单的Sample来简单地介绍我们可以通过哪些方式在WCF中进行Exception Handling。

一、传统的Exception Handling

我们沿用我们一直使用的Calculator的例子和简单的4层构架:


1.    Service Contract- Artech.ExceptionHandling.Contract

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace Artech.ExceptionHandling.Contract
ExpandedBlockStart.gif {
    [ServiceContract]
     public  interface ICalculator
ExpandedSubBlockStart.gif    {
        [OperationContract]
         double Divide( double x,  double y);
    }
}

定义了一个单一的进行除法运算的Operation。

2.    Service:Artech.ExceptionHandling.Service. CalculatorService

using System;
using System.Collections.Generic;
using System.Text;
using Artech.ExceptionHandling.Contract;

namespace Artech.ExceptionHandling.Service
ExpandedBlockStart.gif {
     public  class CalculatorService:ICalculator
ExpandedSubBlockStart.gif    {
ContractedSubBlock.gif         ICalculator Members
    }
}

如果被除数是零,抛出一个DivideByZeroException Exception。

3.    Service Hosting

Configuration:

<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
     < system .serviceModel >
         < behaviors >
             < serviceBehaviors >
                 < behavior  name ="calculatorServiceBehavior" >
                     < serviceMetadata  httpGetEnabled ="true"   />
                 </ behavior >
             </ serviceBehaviors >
         </ behaviors >
         < services >
             < service  behaviorConfiguration ="calculatorServiceBehavior"  name ="Artech.ExceptionHandling.Service.CalculatorService" >
                 < endpoint  binding ="basicHttpBinding"  bindingConfiguration =""  contract ="Artech.ExceptionHandling.Contract.ICalculator"   />
                 < host >
                     < baseAddresses >
                         < add  baseAddress ="http://localhost:8888/Calculator"   />
                     </ baseAddresses >
                 </ host >
             </ service >
         </ services >
     </ system.serviceModel >
</ configuration >

Program

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using Artech.ExceptionHandling.Service;

namespace Artech.ExceptionHandling.Hosting
ExpandedBlockStart.gif {
     class Program
ExpandedSubBlockStart.gif    {
         static  void Main( string[] args)
ExpandedSubBlockStart.gif        {
             using (ServiceHost calculatorHost =  new ServiceHost( typeof(CalculatorService)))
ExpandedSubBlockStart.gif            {
                calculatorHost.Opened +=  delegate
ExpandedSubBlockStart.gif                {
                    Console.WriteLine("The Calculator service has begun to listen via the address:{0}", calculatorHost.BaseAddresses[0]);
                };
                calculatorHost.Open();
                Console.Read();
            }
        }
    }
}

4.    Client

Configuration:

<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
     < system .serviceModel >
           < client >
             < endpoint  address =http://localhost:8888/Calculator  binding ="basicHttpBinding"  contract ="Artech.ExceptionHandling.Contract.ICalculator"
                name
="defualtEndpoint"   />
         </ client >
     </ system.serviceModel >
</ configuration >

Program

using System;
using System.Collections.Generic;
using System.Text;
using Artech.ExceptionHandling.Contract;
using System.ServiceModel;

namespace Artech.ExceptionHandling.Client
ExpandedBlockStart.gif {
     class Program
ExpandedSubBlockStart.gif    {
         static  void Main( string[] args)
ExpandedSubBlockStart.gif        {
            ChannelFactory<ICalculator> calculatorFactory =  new ChannelFactory<ICalculator>("defualtEndpoint");
            ICalculator calculator = calculatorFactory.CreateChannel();
             try
ExpandedSubBlockStart.gif            {
                Console.WriteLine("Try to invoke Divide method");
                Console.WriteLine("x / y =  {2} when x = {0} and y = {1}", 2, 0, calculator.Divide(2,0));
            }
             catch (Exception ex)
ExpandedSubBlockStart.gif            {
                Console.WriteLine("An Exception is thrown.\n\tException Type:{0}\n\tError Message:{1}", ex.GetType(), ex.Message);
            }
            Console.Read();
        }
    }
}

把Service调用放在一个try/catch block中,看看Service端抛出的DivideByZeroException Exception能否被Catch。

我们运行这个程序,看看Client有怎样的输出:


我们发现Client catch住的不是我们Service端真正抛出的DivideByZeroException Exception,而是一个比较General的FaultException。Error message也是很general:

"The server was unable to process the request due to an internal error. For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs."

二、基于ServiceDebug的Exception Handling

很显然Client端Catch住的Exception对我们进行troubleshooting。为了利于我们进行有效的Debug,WCF提供了ServiceDebug Service Behavior。我们通过includeExceptionDetailInFaults属性设为true,那么如果Service抛出Exception,WCF会简单得包装这个Exception并把它置于Soap中Response到Service的访问者。介于此,我修改了Hosting的Configuration:

<? xml version="1.0" encoding="utf-8"  ?>
< configuration >
     < system .serviceModel >
         < behaviors >
             < serviceBehaviors >
                 < behavior  name ="calculatorServiceBehavior" >
                     < serviceMetadata  httpGetEnabled ="true"   />
                     < serviceDebug  includeExceptionDetailInFaults ="true"   />
                 </ behavior >
             </ serviceBehaviors >
         </ behaviors >
         < services >
             < service  behaviorConfiguration ="calculatorServiceBehavior"  name ="Artech.ExceptionHandling.Service.CalculatorService" >
                 < endpoint  binding ="basicHttpBinding"  bindingConfiguration =""  contract ="Artech.ExceptionHandling.Contract.ICalculator"   />
                 < host >
                     < baseAddresses >
                         < add  baseAddress ="http://localhost:8888/Calculator"   />
                     </ baseAddresses >
                 </ host >
             </ service >
         </ services >
     </ system.serviceModel >
</ configuration >

现在再次运行程序,看看现在的运行结果:


可以看到我们我们Catch的是一个FaultException< ExceptionDetail>Type的Exception,不是原来的FaultException。该Exception的Detail属性就是Service抛出的DivideByZeroException Exception。有兴趣的人可以自己测试一下。而且我们在Service端指定的Error Message也被Client获得。这种方式的Exception Handling方式确实比上面一种具有很强的指示性,对我们进行Debug确实很有帮助。但是这种方式确实不能正式用于我们最终发布的版本中,因为它会把Exception所有的信息返回到Client端,很容易泄露一些很敏感的信息。这也正是WCF把这个列入ServiceDebug Service Behavior的原因。

三、基于Fault Contract 的Exception Handling

既然上面通过定制ServiceDebug只能用于Debug阶段。我们必须寻求另外一种Exception Handling的方式。那就是我们现在将要介绍的基于FaultContract的解决方案。我们知道WCF采用一种基于Contract,Contract定义了进行交互的双方进行消息交换所遵循的准则和规范。Service Contract定义了包含了所有Operation的Service的接口,Data Contract定义了交互的数据的结构,而FaultContract实际上定义需要再双方之间进行交互的了异常、错误的表示。我们现在来看看如何来使用基于FaultContract的Exception Handling。

我们首先来定义一个表示Fault的类:MathError。考虑到这个类需要在Service 和Client使用,我把它定义在Artech.ExceptionHandling.Contract中:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;

namespace Artech.ExceptionHandling.Contract
ExpandedBlockStart.gif {
    [DataContract]
    public   class MathError
ExpandedSubBlockStart.gif    {
         private  string _operation;
         private  string _errorMessage;

        public MathError( string operation,  string errorMessage)
ExpandedSubBlockStart.gif       {
            this._operation = operation;
            this._errorMessage = errorMessage;
       }

        [DataMember]
         public  string Operation
ExpandedSubBlockStart.gif        {
ExpandedSubBlockStart.gif             get {  return _operation; }
ExpandedSubBlockStart.gif             set { _operation = value; }
        }

       [DataMember]
         public  string ErrorMessage
ExpandedSubBlockStart.gif        {
ExpandedSubBlockStart.gif             get {  return _errorMessage; }
ExpandedSubBlockStart.gif             set { _errorMessage = value; }
        }
    }
}

在MathError中定义了两个成员:表示出错操作的Operation和出错信息的ErrorMessage。由于该类的对象需要在Endpoint之间传递,所以必须是可序列化的,在WCF中,我们一般用两个不同的Serializer实现Object和XML的Serialization和Deserialization:Datacontract Serializer和XML Serializer。而对于Fault,只能使用前者。

定义了MathError,我们需要通过FaultContract将其运用到Service Contract中制定的Operation上面,我们通过下面的方式来实现:

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace Artech.ExceptionHandling.Contract
ExpandedBlockStart.gif {
    [ServiceContract]
     public  interface ICalculator
ExpandedSubBlockStart.gif    {
        [OperationContract]
        [FaultContract( typeof(MathError))]
         double Divide( double x,  double y);
    }
}

我们在Divide上运用了FaultContract,并指定了封装了Fault对应的类型,那么最终这个基于MathError类型的FaultContract会被写入Service Description中,Client通过获取该Service Description(一般是获取WSDL),它就被识别它,就会将从接收到的Soap中对该Fault的XML Mapping到具体的MathError类型。

接着我们在Service Implementation中以抛出Exception的方式植入这个MathError对象:

using System;
using System.Collections.Generic;
using System.Text;
using Artech.ExceptionHandling.Contract;
using System.ServiceModel;

namespace Artech.ExceptionHandling.Service
ExpandedBlockStart.gif {
     public  class CalculatorService:ICalculator
ExpandedSubBlockStart.gif    {
ContractedSubBlock.gif         ICalculator Members
    }
}

在被除数为0的时候,抛出FaultException<MathError> Exception,并指定具体的MathError对象,以及一个FaultCode(一般指明出错的来源)和FaultReason(出错的原因)。

我们现在先不修改Client的Exception Handling的相关代码,先运行Hosting,看看WSDL中什么特别之处:


通过上面的Screenshot,我们可以看到,在PortType section中的Divide Operation定义了Message为tns:ICalculator_Divide_MathErrorFault_FaultMessage 的<wsdl:fault>节点。通过查看Message Section,我们发现tns:ICalculator_Divide_MathErrorFault_FaultMessage的Element为q1:MathError,该q1:MathError type实际上是被定义在一个XSD中,其Uri为http://localhost:8888/Calculator?xsd=xsd2,我们定义的所有DataContract都在其中,下面的整个内容:

<? xml version="1.0" encoding="utf-8" ?>
< xs:schema  elementFormDefault ="qualified"  targetNamespace ="http://schemas.datacontract.org/2004/07/Artech.ExceptionHandling.Contract"  xmlns:xs ="http://www.w3.org/2001/XMLSchema"  xmlns:tns ="http://schemas.datacontract.org/2004/07/Artech.ExceptionHandling.Contract" >
   < xs:complexType  name ="MathError" >
     < xs:sequence >
       < xs:element  minOccurs ="0"  name ="ErrorMessage"  nillable ="true"  type ="xs:string" />
       < xs:element  minOccurs ="0"  name ="Operation"  nillable ="true"  type ="xs:string" />
     </ xs:sequence >
   </ xs:complexType >
   < xs:element  name ="MathError"  nillable ="true"  type ="tns:MathError" />
</ xs:schema >

弄清楚了Fault在WSDL中表示后,我们来修改我们Client端的代码,来有效地进行Exception Handling:

static  void Main( string[] args)
ExpandedBlockStart.gif         {
            ChannelFactory<ICalculator> calculatorFactory =  new ChannelFactory<ICalculator>("defualtEndpoint");
            ICalculator calculator = calculatorFactory.CreateChannel();
             try
ExpandedSubBlockStart.gif            {
                Console.WriteLine("Try to invoke Divide method");
                Console.WriteLine("x / y =  {2} when x = {0} and y = {1}", 2, 0, calculator.Divide(2, 0));
            }
             catch (FaultException<MathError> ex)
ExpandedSubBlockStart.gif            {
                MathError error = ex.Detail;
                Console.WriteLine("An Fault is thrown.\n\tFault code:{0}\n\tFault Reason:{1}\n\tOperation:{2}\n\tMessage:{3}", ex.Code, ex.Reason, error.Operation, error.ErrorMessage);
            }

             catch (Exception ex)
ExpandedSubBlockStart.gif            {
                Console.WriteLine("An Exception is thrown.\n\tException Type:{0}\n\tError Message:{1}", ex.GetType(), ex.Message);
            }
            Console.Read();
        }

下面是运行后的输出结果:


WCF相关内容:
[原创]我的WCF之旅(1):创建一个简单的WCF程序
[原创]我的WCF之旅(2):Endpoint Overview
[原创]我的WCF之旅(3):在WCF中实现双向通信(Bi-directional Communication)
[原创]我的WCF之旅(4):WCF中的序列化(Serialization)- Part I
[原创]我的WCF之旅(4):WCF中的序列化(Serialization)- Part II
[原创]我的WCF之旅(5):Service Contract中的重载(Overloading)
[原创]我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案
[原创]我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承
[原创]我的WCF之旅(8):WCF中的Session和Instancing Management
[原创]我的WCF之旅(9):如何在WCF中使用tcpTrace来进行Soap Trace
[原创]我的WCF之旅(10): 如何在WCF进行Exception Handling
[原创]我的WCF之旅(11):再谈WCF的双向通讯-基于Http的双向通讯 V.S. 基于TCP的双向通讯

[原创]我的WCF之旅(12):使用MSMQ进行Reliable Messaging
[原创]我的WCF之旅(13):创建基于MSMQ的Responsive Service


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_34309543/article/details/90334699

智能推荐

ubuntu桌面卡死_ubuntu 设置桌面 卡死-程序员宅基地

文章浏览阅读402次。ubuntu桌面卡死问题如何解决之前的操作就是关机、重启现在找到一种新的解决办法:切换到tty模式,执行pkill X;start X;即可补充一点:Ctrl+Atl+F3/F4/F5/F6 进入ttyCtrl+Atl+F2退出tty不过有一点:所有的进程都会被杀死,重新进来之后,啥都没有了,不过好像重启也会这样的。..._ubuntu 设置桌面 卡死

Java面向对象——多态 详解_son son=(parent) parent-程序员宅基地

文章浏览阅读279次。java基础**面向对象——多态** 来,此篇博客来看看Java面向对象最后一篇——多态,也是Java最简单的最后部分了。 这个多态呀。就是事物的多种形态,主要分为静多态和动多态多态 前提:不知道还记得继承和方法重写重载么。 体现:父类引用或者接口引用执行子类的对象。(就是基类的引用引用派生类的对象)我记..._son son=(parent) parent

python字符串类型_Python-数据类型-字符串-程序员宅基地

文章浏览阅读278次。数据类型-字符串字符串是 python最常见的基本数据类型之一,常见的定义方式是一对单引号( '……')或者一对双引号 ("……")创建,多行字符串也可使用三单引号或者三双引号定义。1. 特点不可变类型: 在元素定义之后不能对其进行修改,否则会报错可以进行切片和索引操作: 索引下标从零开始示例a = "hello world"b = 'hello python'print(type(a), typ..._name[:-4:-1]

【渝偲】DSPE-PEG-Galactose;DSPE-PEG-半乳糖;磷脂聚乙二醇半乳糖 长循环脂质体-程序员宅基地

文章浏览阅读680次。中文名称:磷脂-聚乙二醇-半乳糖 半乳糖-聚乙二醇-磷脂简称:DSPE-PEG-Galactose Galactose-PEG-DSPE外观: 根据不同的分子量为半固体或固体分子量(PEG ):1000、2000、3400、5000,其他分子量可以定制。溶剂:溶于部分常规有机溶剂存储条件:-20℃以下冰冻、干燥、避光。长期保存惰性(氩气或者氮气)保护。二硬脂酰磷脂酰乙醇胺-聚乙二醇-半乳糖对脂质体表面进行聚乙二醇修饰后,得到长循环脂质体可以延长脂质体的半衰期和提高它在血液循环中的稳定性、改变脂质体的生物_dspe-peg-gal

Spring Security自定义登录验证(不使用userDetailsService)_spring security 不使用userdetailservice-程序员宅基地

文章浏览阅读1.3w次,点赞2次,收藏16次。一:功能说明实现了自定义登录验证(AuthenticationProvider)二:具体代码1.自定义AuthenticationProvider/** * @author LEI * Created by LEI on 2019/5/30. */@Componentpublic class MyAuthenticationProvider implements A..._spring security 不使用userdetailservice

PropertiesHelper.java_java propertieshelper-程序员宅基地

文章浏览阅读1.3k次。import java.util.HashMap;import java.util.Map;import java.util.Properties;import java.util.StringTokenizer;public final class PropertiesHelper { public static boolean getBoolean(String property_java propertieshelper

随便推点

NVIDIA DALI从入门到放弃之一:概述_nvidia dail-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏19次。NVIDIA DALI从入门到放弃之一:概述NVIDIA DALI从入门到放弃之二:入门示例NVIDIA DALI从入门到放弃之三:Data LoadingNVIDIA DALI从入门到放弃之四:Multiple GPUNVIDIA DALI从入门到放弃之五:Image ProcessingNVIDIA DALI从入门到放弃之六:Geometric TransformsNVIDIA DALI从入门到放弃之七:Sequence ProcessingNVIDIA DALI从入门到放弃之八:PyTo_nvidia dail

csf的安装-程序员宅基地

文章浏览阅读165次。安装CSF 防火墙通常是我们在服务器上所做的第二件事。防火墙的主要目的是为了帮助完成如下任务:1 防止暴力破解密码,自动屏蔽连续登陆失败的IP2 管理网络端口,只开放必要的端口3 免疫小流量的 DDos 和 CC ***。(对于免疫这些的*** 个人感觉也就那么回事)CSF是一个功能完善的防火墙,并且提供 cPanel 插件,便于管理。当然除了 Web GUI..._如何安装csf包

CMake 使用_set( cmake_cxx_flags "${cmake_cxx_flags} -y_gpp")-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏6次。CMake使用_set( cmake_cxx_flags "${cmake_cxx_flags} -y_gpp")

ai换脸可以突破人脸识别吗,有没有技术可以攻破_ai换脸可以人脸识别吗-程序员宅基地

文章浏览阅读177次。在机器学习中,对抗性攻击指的是利用模型的弱点来误导模型做出错误决策的方法。通过对抗性攻击生成的图像或视频可能包含细微的、专门设计的扰动,这些扰动对人眼几乎不可见,但可以使得人脸识别系统无法正确识别。如果AI生成的换脸图像足够逼真,它可能会误导基于图像的人脸识别系统。尽管采取了这些防御措施,没有任何安全系统是绝对安全的,而技术的进步总是在不断地推动攻防之间的较量。通过创建目标人脸的3D模型,并将其用于生成图像或视频,可以生成能够误导2D或3D人脸识别系统的视觉内容。_ai换脸可以人脸识别吗

Webstorm+Nodejs+webpack+vue-cli+Git搭建vue环境_webstorm环境配置-程序员宅基地

文章浏览阅读1.8k次。此笔记归纳整理webstorm搭建vue项目,仅作记录使用。_webstorm环境配置

VS2012中解决方案资源管理器的图标含义-程序员宅基地

文章浏览阅读4.3k次,点赞4次,收藏7次。VS2012的功能比VS2010强大一些,以解决方案资源管理器为例,VS2010里仅提取了文件名信息,如xx.h或xx.c等,而VS2012还提取了全局变量(包括结构体),函数名,宏定义等信息,并且用特定的图标加以表示,更便于阅读和掌握project的数据结构情况。下面整理一些我见过的图标及其含义(可能有误,欢迎各路大神挑错~)...

推荐文章

热门文章

相关标签