【WCF】错误处理(一):FaultException 与 FaultReason 的搭配

这里所说的错误处理主要是指服务代码中抛出的异常,即开发人员主动抛出的错误当然,由于网络问题或者配置不正确,会引发连接超时的错误,但这里老周要说的是,我们在实现服务逻辑时主动抛出的异常,尤其是对客户端传入的参数的验证上面。

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 通信可以了。

  <system.serviceModel>
    <services>
      <service name="TestApp.OrderSvr">
        <endpoint contract="TestApp.IOrder" binding="basicHttpBinding" address="http://127.0.0.1:6035/order"/>
      </service>
    </services>
    <client>
      <endpoint name="epcl" contract="TestApp.IOrder" binding="basicHttpBinding" address="http://127.0.0.1:6035/order"/>
    </client>
  </system.serviceModel>

 

为了使客户端调用起来舒坦,我还封装一个客户端类(你可以通过服务引用来让VS自动生成,这里为了装逼,我就手动写)。

    public class SampleClient : ClientBase<IOrder>, 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 元素。如下

    <behaviors>
      <serviceBehaviors>
        <behavior name="svbhv">
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>

给它个名字(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。 

 

好,今天就聊到这里,本文示例下载地址:点这里下载

 

posted @ 2017-03-06 11:41  东邪独孤  阅读(949)  评论(2编辑  收藏  举报