我的WCF之旅(10): 如何在WCF进行Exception Handling(转载)


在任何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

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.ServiceModel;

namespaceArtech.ExceptionHandling.Contract
{
[ServiceContract]
publicinterfaceICalculator
{
[OperationContract]
doubleDivide(doublex,doubley);
}

}

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

2. Service:Artech.ExceptionHandling.Service. CalculatorService

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingArtech.ExceptionHandling.Contract;

namespaceArtech.ExceptionHandling.Service
{
publicclassCalculatorService:ICalculator
{
ICalculatorMembers
}

}

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

3. Service Hosting

Configuration:

<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behaviorname="calculatorServiceBehavior">
<serviceMetadatahttpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<servicebehaviorConfiguration="calculatorServiceBehavior"name="Artech.ExceptionHandling.Service.CalculatorService">
<endpointbinding="basicHttpBinding"bindingConfiguration=""contract="Artech.ExceptionHandling.Contract.ICalculator"/>
<host>
<baseAddresses>
<addbaseAddress="http://localhost:8888/Calculator"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

Program

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.ServiceModel;
usingArtech.ExceptionHandling.Service;

namespaceArtech.ExceptionHandling.Hosting
{
classProgram
{
staticvoidMain(string[]args)
{
using(ServiceHostcalculatorHost=newServiceHost(typeof(CalculatorService)))
{
calculatorHost.Opened
+=delegate
{
Console.WriteLine(
"TheCalculatorservicehasbeguntolistenviatheaddress:{0}",calculatorHost.BaseAddresses[0]);
}
;
calculatorHost.Open();
Console.Read();
}

}

}

}

4. Client

Configuration:

<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
<system.serviceModel>
<client>
<endpointaddress=http://localhost:8888/Calculatorbinding="basicHttpBinding"contract="Artech.ExceptionHandling.Contract.ICalculator"
name
="defualtEndpoint"/>
</client>
</system.serviceModel>
</configuration>

Program

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingArtech.ExceptionHandling.Contract;
usingSystem.ServiceModel;

namespaceArtech.ExceptionHandling.Client
{
classProgram
{
staticvoidMain(string[]args)
{
ChannelFactory
<ICalculator>calculatorFactory=newChannelFactory<ICalculator>("defualtEndpoint");
ICalculatorcalculator
=calculatorFactory.CreateChannel();
try
{
Console.WriteLine(
"TrytoinvokeDividemethod");
Console.WriteLine(
"x/y={2}whenx={0}andy={1}",2,0,calculator.Divide(2,0));
}

catch(Exceptionex)
{
Console.WriteLine(
"AnExceptionisthrown.\n\tExceptionType:{0}\n\tErrorMessage:{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:

"Theserverwasunabletoprocesstherequestduetoaninternalerror.Formoreinformationabouttheerror,eitherturnonIncludeExceptionDetailInFaults(eitherfromServiceBehaviorAttributeorfromthe<serviceDebug>configurationbehavior)ontheserverinordertosendtheexceptioninformationbacktotheclient,orturnontracingaspertheMicrosoft.NETFramework3.0SDKdocumentationandinspecttheservertracelogs."

二、基于ServiceDebug的Exception Handling

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

<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behaviorname="calculatorServiceBehavior">
<serviceMetadatahttpGetEnabled="true"/>
<serviceDebugincludeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<servicebehaviorConfiguration="calculatorServiceBehavior"name="Artech.ExceptionHandling.Service.CalculatorService">
<endpointbinding="basicHttpBinding"bindingConfiguration=""contract="Artech.ExceptionHandling.Contract.ICalculator"/>
<host>
<baseAddresses>
<addbaseAddress="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中:

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.Runtime.Serialization;

namespaceArtech.ExceptionHandling.Contract
{
[DataContract]
publicclassMathError
{
privatestring_operation;
privatestring_errorMessage;

publicMathError(stringoperation,stringerrorMessage)
{
this._operation=operation;
this._errorMessage=errorMessage;
}


[DataMember]
publicstringOperation
{
get{return_operation;}
set{_operation=value;}
}


[DataMember]
publicstringErrorMessage
{
get{return_errorMessage;}
set{_errorMessage=value;}
}

}

}

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

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

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.ServiceModel;

namespaceArtech.ExceptionHandling.Contract
{
[ServiceContract]
publicinterfaceICalculator
{
[OperationContract]
[FaultContract(
typeof(MathError))]
doubleDivide(doublex,doubley);
}

}

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

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

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingArtech.ExceptionHandling.Contract;
usingSystem.ServiceModel;

namespaceArtech.ExceptionHandling.Service
{
publicclassCalculatorService:ICalculator
{
ICalculatorMembers
}

}

在被除数为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都在其中,下面的整个内容:

<?xmlversion="1.0"encoding="utf-8"?>
<xs:schemaelementFormDefault="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:complexTypename="MathError">
<xs:sequence>
<xs:elementminOccurs="0"name="ErrorMessage"nillable="true"type="xs:string"/>
<xs:elementminOccurs="0"name="Operation"nillable="true"type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:elementname="MathError"nillable="true"type="tns:MathError"/>
</xs:schema>

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

staticvoidMain(string[]args)
{
ChannelFactory
<ICalculator>calculatorFactory=newChannelFactory<ICalculator>("defualtEndpoint");
ICalculatorcalculator
=calculatorFactory.CreateChannel();
try
{
Console.WriteLine(
"TrytoinvokeDividemethod");
Console.WriteLine(
"x/y={2}whenx={0}andy={1}",2,0,calculator.Divide(2,0));
}

catch(FaultException<MathError>ex)
{
MathErrorerror
=ex.Detail;
Console.WriteLine(
"AnFaultisthrown.\n\tFaultcode:{0}\n\tFaultReason:{1}\n\tOperation:{2}\n\tMessage:{3}",ex.Code,ex.Reason,error.Operation,error.ErrorMessage);
}


catch(Exceptionex)
{
Console.WriteLine(
"AnExceptionisthrown.\n\tExceptionType:{0}\n\tErrorMessage:{1}",ex.GetType(),ex.Message);
}

Console.Read();
}

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

优质内容筛选与推荐>>
1、2015-8-3 暑假过了一半了
2、HTTP ERROR
3、请编程遍历页面上所有TextBox控件并给它赋值为string.Empty?
4、【Linux命令】grep命令
5、Doorman


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn