一、考虑到安全因素,为了避免将服务端的异常发送给客户端。默认情况下,服务端出现异常会对异常屏蔽处理后,再发送到客户端。所以客户端捕捉到的异常都是同一个FaultException异常。
例如在服务端直接产生一个空引用异常,客户端捕获到的是上述异常。
服务端:
class Program { static void Main(string[] args) { ServiceHost host = new ServiceHost(typeof(SayHello)); host.AddServiceEndpoint(typeof(ISayHello), new WSHttpBinding(), "http://localhost:4216"); host.Opened += delegate { Console.WriteLine("Service Start!"); }; host.Open(); Console.ReadLine(); } } [ServiceContract] public interface ISayHello { [OperationContract] void Say(); } public class SayHello : ISayHello { public void Say() { string name = null; Console.Write("Hello {0}", name.Length); } }
客户端:
class Program { static void Main(string[] args) { ISayHello ClientChannel = ChannelFactory<ISayHello>.CreateChannel(new WSHttpBinding(), new EndpointAddress("http://localhost:4216")); try { ClientChannel.Say(); } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); } } }
二、可通过配置ServiceDebugBehavior将IncludeExceptionDetailInFaults,将其置为true。则服务端会将异常原封不动的传递到客户端,该配置默认为false。
[ServiceBehavior(IncludeExceptionDetailInFaults=true)] public class SayHello : ISayHello { public void Say() { string name = null; Console.Write("Hello {0}", name.Length); } }
客户端捕捉到空引用异常。
三、自定义异常信息
1.直接通过FaultException构造函数,构造异常。
[ServiceBehavior(IncludeExceptionDetailInFaults=true)] public class SayHello : ISayHello { public void Say() { throw new FaultException("自定义异常"); } }
2.通过FaultException<TDetail>构造异常。
TDetail是一个可序列化的数据结构,如下面定义的myException。需要注意的是在操作契约上需要加一个错误契约, [FaultContract(typeof(myException))]。
[ServiceContract] public interface ISayHello { [OperationContract] [FaultContract(typeof(myException))] void Say(); } [ServiceBehavior(IncludeExceptionDetailInFaults=true)] public class SayHello : ISayHello { public void Say() { myException ex = new myException { Message = "自定义异常", OperatorMethodName = "SayHello:Say()" }; throw new FaultException<myException>(ex, ex.Message); } } [DataContract] public class myException { [DataMember] public string Message; [DataMember] public string OperatorMethodName; }
客户端:
class Program { static void Main(string[] args) { ISayHello ClientChannel = ChannelFactory<ISayHello>.CreateChannel(new WSHttpBinding(), new EndpointAddress("http://localhost:4216")); try { ClientChannel.Say(); } catch (Exception ex) { myException myex = (ex as FaultException<myException>).Detail; Console.WriteLine(myex.Message); Console.WriteLine(myex.OperatorMethodName); } } }
使用错误契约需要注意的地方:
(1)不能再同一个操作方法上声明相同的细节类型。如下所示:
[ServiceContract] public interface ISayHello { [OperationContract] [FaultContract(typeof(myException))] [FaultContract(typeof(myException))] void Say(); }
(2)错误契约上的细节类型不能等效。
[ServiceContract] public interface ISayHello { [OperationContract] [FaultContract(typeof(myException),Name="PersonalException",Namespace="www.cnblogs.cn/lh218")] [FaultContract(typeof(myError), Name = "PersonalException", Namespace = "www.cnblogs.cn/lh218")] void Say(); }
四、可以指定异常的序列化方式
默认的情况下异常消息还DataContractSerializer序列化的。可以通过XmlSerializerFormat的SupportFaults,指定使用XmlSerializer序列化异常消息。
[ServiceContract] public interface ISayHello { [OperationContract] [FaultContract(typeof(myException))] [XmlSerializerFormat(SupportFaults=true)] void Say(); }
五、错误消息的结构
由5部分组成
(1)FaultCode,可以看出是对异常消息的分类。结点内部的Value表示错误类型,SubCode表示错误子代码。
(2)FaultReason表示错误原因,内部有一个FaultReasonText集合,FaultReasonText支持全球化语言描述。
(3)FaultNode元素,表示在SOAP消息路由过程中产生异常的结点。
(4)FaultRole表示处理消息时所担当的角色。
(5)FaultDetail 即之前描述的错误细节。
下面代码讲创建一个错误消息:
using System.Xml; using System.Xml.Serialization; using System.ServiceModel; using System.ServiceModel.Description; using System.Runtime.Serialization; using System.ServiceModel.Channels; namespace ConsoleApplication4 { class Program { static void Main(string[] args) { FaultCode subCode = new FaultCode("lh218", "www.cnblogs.com/lh218"); FaultCode code = new FaultCode("myFault", subCode); var text1=new FaultReasonText("异常消息","zh-CN"); var text2=new FaultReasonText("Exception Message","en-US"); FaultReason reason=new FaultReason(new FaultReasonText[]{text1,text2}); myException exDetail = new myException { Message = "自定义异常细节", OperatorMethodName = "myException" }; MessageFault fault = MessageFault.CreateFault(code, reason, exDetail, new DataContractSerializer(typeof(myException)), "lhActor","lhNode"); Message msg = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, fault, "http://myaction"); Write(msg, @"D://1.txt"); Console.ReadLine(); } public static void Write(Message msg,string path) { using (XmlTextWriter writer = new XmlTextWriter(path,Encoding.UTF8)) { writer.Formatting = Formatting.Indented; msg.WriteMessage(writer); } } } }
消息为:
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope"> <s:Header> <a:Action s:mustUnderstand="1">http://myaction</a:Action> </s:Header> <s:Body> <s:Fault> <s:Code> <s:Value>s:myFault</s:Value> <s:Subcode> <s:Value xmlns:a="www.cnblogs.com/lh218">a:lh218</s:Value> </s:Subcode> </s:Code> <s:Reason> <s:Text xml:lang="zh-CN">异常消息</s:Text> <s:Text xml:lang="en-US">Exception Message</s:Text> </s:Reason> <s:Node>lhNode</s:Node> <s:Role>lhActor</s:Role> <s:Detail> <PersonalException xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="www.cnblogs.com/lh218"> <Message>自定义异常细节</Message> <OperatorMethodName>myException</OperatorMethodName> </PersonalException> </s:Detail> </s:Fault> </s:Body> </s:Envelope>
六、异常与消息之间的转换关系
如上图所示,MessageFault是FaultException和Message直接的中转对象。
先看Server端:
FaultException ==》MessageFault转换方法:FaultException对象可以调用CreateMessageFault创建MessageFault对象。
public virtual MessageFault CreateMessageFault();
MessageFault ==》 Message:Message调用CreateMessage创建,传入MessageFault参数。
public static Message CreateMessage(MessageVersion version, MessageFault fault, string action);
Client端:
Message==>MessageFault:MessageFault类型内部的CreateFault方法可以提取异常消息中的MessageFault
public static MessageFault CreateFault(Message message, int maxBufferSize);
MessageFault ==》FaultException:FaultException类型提供两个静态方法创建FaultException。
public static FaultException CreateFault(MessageFault messageFault, params Type[] faultDetailTypes);
public static FaultException CreateFault(MessageFault messageFault, string action, params Type[] faultDetailTypes);