这里所说的错误处理主要是指服务代码中抛出的异常,即开发人员主动抛出的错误当然,由于网络问题或者配置不正确,会引发连接超时的错误,但这里老周要说的是,我们在实现服务逻辑时主动抛出的异常,尤其是对客户端传入的参数的验证上面。
WCF的异常信息一般会通过 FaultException 类来包装。理论和概念性的东西,大家可以去查资料,老周向来不喜欢谈那些,下面咱们通过实例来了解一下 FaultException。
定义服务协定。
[ServiceContract(Namespace = "demo-app")] public interface IOrder { [OperationContract] bool NewOrder(DateTime date, decimal price, long q); }
假设这个服务的功能是用来下单的,当然不是真的实现下单功能,因为那样太复杂,也不是本文的重点,这里老周安排了三个参数,分别表示下单日期,商品单价,以及商品数量。
下面,咱们来实现一下这个协定。
internal class OrderSvr : IOrder { public bool NewOrder(DateTime date, decimal price, long q) { // 验证 if (date < DateTime.Now) { throw new ArgumentException("至少要在今天下单"); } if (price < 0 || q < 0) { throw new ArgumentException("单价或数量不能小于0"); } // 模拟下单 if ((q * price) == 0.00M) { return false; } return true; } }
正如大伙所看到的,我在实现的服务方法中对参数进行了验证,假设日期早于今天就会抛出异常。
下面是配置文件中的配置信息,这是演示,没什么安全性要求,就使用基本的 HTTP 通信可以了。
"TestApp.OrderSvr"> "TestApp.IOrder" binding="basicHttpBinding" address="http://127.0.0.1:6035/order"/> "epcl" contract="TestApp.IOrder" binding="basicHttpBinding" address="http://127.0.0.1:6035/order"/>
为了使客户端调用起来舒坦,我还封装一个客户端类(你可以通过服务引用来让VS自动生成,这里为了装逼,我就手动写)。
public class SampleClient : ClientBase, IOrder { public SampleClient() : base("epcl") { } public bool NewOrder(DateTime date, decimal price, long q) { return Channel.NewOrder(date, price, q); } }
好D,服务的大致功能就是这样,下面我们来调用一下,顺便,咱们传递不符合要求的参数值,看看客户端能否获取到服务器上抛出的异常。
SampleClient client = new SampleClient(); try { bool r = client.NewOrder(date, price, q); MessageBox.Show(r ? "下单成功。" : "下单失败。"); } catch(FaultException ftex) { MessageBox.Show(ftex.Reason.GetMatchingTranslation().Text); } finally { client.Close(); }
WCF的异常使用 FaultException 来封装,然后通过 SOAP 消息发回给客户端,所以此处咱们应当捕捉 FaultException 类型的异常。
测试参数如下图所示。
日期晚于今天,符合,但是数量小于0,不符合。故,调用服务后,会看到如下面的高清无码图片所示的异常信息。
噢,your god,大伙发现,没有出现我们想看到的自定义异常信息。
依据上面的提示,可以开启 IncludeExceptionDetailInFaults 选项,有两种方式可以开启该选项,大伙任选其一即可。
1、使用 ServiceBehaviorAttribute,这个特性要应用到实现服务协定的类上,比如,咱们这个示例,服务类是OrderSvr,可以这样来开启:
[ServiceBehavior(IncludeExceptionDetailInFaults = true)] internal class OrderSvr : IOrder ……
2、如果你不想用代码来设置,可以用配置文件,这样可以方便修改。方法是定义一个service behavior,并添加 serviceDebug 元素。如下
"svbhv"> "true"/>
给它个名字(name),这样方便service元素去引用。
<service name="TestApp.OrderSvr" behaviorConfiguration="svbhv"> ……
修改后,再次运行示例,然后输入不正确的参数,就能收到自定义的异常信息了。
大伙是不是很是兴奋,终于看到自定义的异常信息了。
================================================
问题似乎已经解决,不过,IncludeExceptionDetailInFaults 选项一般只是在调试阶段开启,正式上线的服务不应该开启,主要为了避免被别有用心的人调戏。所以,开启 IncludeExceptionDetailInFaults 选项还不是最终方案。那还有啥法子呢?
不急,广告回来揭晓……
在服务代码中抛出异常时可以选用 FaultException 类,而且,把一个 FaultReason 对象传给异常。FaultReason 是干吗的?它可以用来封装我们的自定义错误信息,为什么要用它呢,因为它帅?不是,因为一个 FaultReason 实例可以包含一个或N个 FaultReasonText 对象。
FaultReasonText不仅能设置错误描述文本,而且可以与区域/语言关联。比如,你的WCF服务是一个跨生物领域的应用,不仅人可以用,鸟儿、花儿、草儿都可以用,这样你需要返回多种语言版本的错误论处,比如中文的,韩文的,英文的,鸟语的,火星文的,等等。这样,每个 FaultReasonText 就可以封装一个版本的信息。
比如这样:
FaultReasonText txt1 = new FaultReasonText("我喜欢你", "zh-CN"); FaultReasonText txt2 = new FaultReasonText("I like you", "en-US"); FaultReason reason = new FaultReason(new FaultReasonText[] { txt1, txt2 });
随后,你用这个 FaultReason 对象来 new 一个FaultException实例。
throw new FaultException(reason);
现在,咱们对服务代码进行修改。
public bool NewOrder(DateTime date, decimal price, long q) { // 验证 if (date < DateTime.Now) { FaultReasonText text = new FaultReasonText("至少要在今天下单"); FaultReason reason = new FaultReason(text); throw new FaultException(reason); } if (price < 0 || q < 0) { FaultReason reason = new FaultReason("单价或数量不能小于0"); throw new FaultException(reason); } // 模拟下单 if ((q * price) == 0.00M) { return false; } return true; }
这里我们只要中文版本就可以了,所以不用弄那么多 FaultReasonText。
在客户端,捕捉到FaultException异常后,要从它的Reason属性中获得 FaultReason 对象,再调用 GetMatchingTranslation 方法来获得错误文本。如果调用的是无参数版本,就按当前语言来获取,你的系统是中文版的,就默认获取中文版错误信息。如果你的系统是鸟文版的,就默认获取鸟文版的错误信息。
catch(FaultException ftex) { MessageBox.Show(ftex.Reason.GetMatchingTranslation().Text); }
这样修改后,就算 includeExceptionDetailInFaults 选项的值为 false,也能获取到服务器上抛出的错误信息。
顺便提一句,如果WCF示例不能运行,请以管理员身份运行 VS。
好,今天就聊到这里,本文示例下载地址:点这里下载