【WCF】错误协定声明

作者: 李禾

更新时间:2022-03-26 13:10:34

3448 阅读

在上一篇烂文中,老周给大伙伴们介绍了 IErrorHandler 接口的使用,今天,老周补充一个错误处理的知识点——错误协定。

错误协定与IErrorHandler接口不同,大伙伴们应该记得,上回我们是把自己实现IErrorHandler接口的类型添加到ChannelDispatcher中的,也就是说,IErrorHandler处理的是通道层的错误,它可以捕捉到多个服务操作上发生的错误。而错误协定是面向协定层的,它是通过 FaultContractAttribute 来声明的,这个特性在上一篇文章中我们用过,它应用的目标是方法。故而这个特性是针对某个服务操作而定义的,主要指明在某个服务操作可能会引发的  FaultException<TDetail> 异常,其中的TDetail泛型参数的类型是在FaultContract特性中指定。

在上一篇烂文中提到过,如果是像string、int这些基本类型,是可以直接用的,如果是自定义的类型,则应该将其声明为数据协定,然后在FaultContract特性中作声明。

 

咱们来实战一下。首先,咱们定一个服务协定。

    [ServiceContract(Namespace = "zhou-demo", Name = "runner")]
    public interface IDemo
    {
        [OperationContract(Action = "getNum", ReplyAction = "getNumRes")]
        [FaultContract(typeof(ErrorData), Namespace = "zhou-demo/faults", Name = "fault-ct", Action = "fault-back")]
        int GetNumber();
    }

这个协定没什么重要的事情干,无非就是返回一个整数,待会儿咱们让它返回随机整数。

不过大家注意,我在方法上应用了FaultContract特性,它的构造函数是个Type,这个Type用来指定存放错误详细信息的类型,这个类型就是FaultException<TDetail>中的TDetail的类型。其他属性如Action,Namespace、Name这些,不用我多说了吧,都知道干吗用的,你也可以不设置的,它会自动生成一个默认值。

ErrorData是我自己定义的一个数据协定,请看下面代码。

    [DataContract(Namespace = "zhou-demo/fault-data")]
    public class ErrorData
    {
        [DataMember]
        public int ErrorNumber { get; set; }
        [DataMember]
        public string ErrorSource { get; set; }
        [DataMember]
        public string ErrorMessage { get; set; }
        [DataMember]
        public string ErrorType { get; set; }
    }

这个好懂吧,不必介绍了吧。

当服务操作中发生异常时,可以用ErrorData类来封装自定义的错误信息,WCF会把它序列化,然后塞进SOAP消息中发回给客户端,客户端就可以catch到这些错误数据了。

 

接下来,实现服务类。

    class DemoService : IDemo
    {
        public int GetNumber()
        {
            Random rand = new Random();
            int n = rand.Next(2000);
            if(n % 3 == 0)
            {
                ErrorData erdata = new ErrorData
                {
                    ErrorNumber = n,
                    ErrorSource="人品",
                    ErrorType="生物机能错误",
                    ErrorMessage ="运气指数不佳,引发不可修复错误。"
                };
                throw new FaultException<ErrorData>(erdata);
            }
            return n;
        }
    }

这代码没什么技术含量,你如果看不懂,应该写一份30万字的检讨书。随机生成一个整数,如果这个整数可以被3整除,那就抛出异常,否则正常返回这个数值。

 

服务完成,下面该轮到实例化ServiceHost了,有了Host我们才能调用服务。Host的处理很TMD简单,直接指定:基址 + 服务类型 = 完事。

            Uri baseAddress = new Uri("http://localhost:9973");
            ServiceHost host = new ServiceHost(typeof(DemoService), baseAddress);
            host.Open();
            Console.WriteLine("服务已启动。");
             ……
            host.Close();

还记得老周以前说过吧,如果仅仅指定基址,而不定义任何EndPoint的话,Host会自动根据协定个数和基址个数,自动生成默认的终结点。在这个高大上例子中,基址是http方案的,所以生成的终结点默认使用BasicHttpBinding,由于服务类只实现了一个服务协定接口,所以该Host中也就只有一个终结点了。

 

好了,万事具备,只欠东风了,东风就是客户端调用代码。

            Console.WriteLine("请按 Esc 键退出,按任意键调用服务。");
            ConsoleKeyInfo keyinfo;
            while((keyinfo = Console.ReadKey(true)).Key != ConsoleKey.Escape)
            {
                EndpointAddress epaddr = new EndpointAddress(baseAddress);
                BasicHttpBinding binding = new BasicHttpBinding();
                IDemo channel = ChannelFactory<IDemo>.CreateChannel(binding, epaddr);
                try
                {
                    int x = channel.GetNumber();
                    Console.WriteLine($"\n调用结果:{x}。");
                }
                catch(FaultException<ErrorData> ftex)
                {
                    ErrorData detail = ftex.Detail;
                    string msg = $"\n错误数字:{detail.ErrorNumber}\n" +
                        $"错误源:{detail.ErrorSource}\n" +
                        $"错误类型:{detail.ErrorType}\n" +
                        $"错误详情:{detail.ErrorMessage}";
                    Console.WriteLine(msg);
                }
                catch(FaultException fex)
                {
                    Console.WriteLine(fex.Message);
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                finally
                {
                    IClientChannel client = (IClientChannel)channel;
                    client.Close();
                }

为了能够在控制台应用程序中多次调用服务(整数是随机生成,不能保证每次都抛异常),老周做了些处理,按任意键来调用服务,当然不能弄成死循环,为了使循环不死,我留了个出口——按Esc键。

 

如果在服务器上果真引发了FaultException<TDetail>,那么,在客户端就可以捕捉到该异常。服务器引发异常后会数据对象序列化,传到客户端后,会进行反序列化。

 

示例运行结果如下图所示。

 

好了,知识点补完了,老周得准备吃lunch了,今天是国庆长假第二天,下午老周要去朋友H家里看现场水墨绘画,据说有不少妹子在那里。

示例代码下载

 

===================================================

很久没讲故事了,这回就讲一讲老周为什么会进入IT界吧。

其实原因也极其简单,也不是什么远大志向,老周之所以会进入IT领域,就是因为喜欢编程,纯粹就是爱编程,不等于爱做项目,老周很讨厌做项目。

还是孔爷爷说得好,“知之者不如好之者,好之者不如乐之者”,对老周而言,除了卖保险之外,什么行业都可以进,要不是热爱编程,老周是不会选择走IT道路。而编程里面,犹爱.NET。

所以,以后你要是想跟老周讨论编程问题,那就只讨论编程上的事,不要跟我讨论怎么找到好工作,找工作那是你的事,跟我没关系,也不要讨论.net有什么前景之类的话题,那是匹夫才讨论的问题。不过,最近些天,好像又有些不学无术的人,又在搞.NET与Java之争,那些东西在老周眼里,等同于看一场相声表演而已。

反正老周只告诉你一句话:我就是热爱.NET。

如果你喜欢.NET,欢迎你看看老周写的烂文章;如果你不喜欢.NET,那无所谓,你可以无视老周的博客。但是,老周不允许有人在博客评论里面说不文明的言论。心诚则与君交,心不诚则与君绝。

 

版权声明:本文著作权归作者【李禾 】所有,不代表本网站立场。

侵权请联系:root_email@163.com

相关推荐