zoukankan      html  css  js  c++  java
  • 不引用服务而使用WCF,手动编写客户端代理类

    不引用服务而使用WCF,手动编写客户端代理类

    2013-08-09 16:45:24 东邪独孤 阅读数 21823更多

    分类专栏: WCF 传说中的WCF系列

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    本文链接:https://blog.csdn.net/tcjiaan/article/details/9832601

    前面我写过一个用WCF开发的聊天程序,大家可以翻看前面的博文。

    在那个聊天程序中,我是不引用服务而直接使用WCF。之前没有跟大家说这一知识点,对于初学者朋友来说,可能不知道怎么回事。

    我们之所以说WCF比一般的Web Service要强大得多,是因为它要比一般的Web服务要灵活得多,而且它不仅仅能在IIS服务器上运行,其实它可以用很多种方法来运行,哪怕一个控制台应用程序。

    现在,大家可以回忆一下前面我写的《传说中的WCF》,我上面的例子绝大多数都是控制台应用程序类型的。我们应当把WCF理解为一种通信技术,而不只是服务。前面的例子中我是告诉大家,完成服务器端后,就在客户端项目中添加服务引用,这样就生成了客户端代理类,我们就可以像平时使用一般类型一样使用了。

    其实按照我们前面所讲的方法,也足以完成许多实际任务了。大家是否还想拓展一下呢? 有朋友肯定会问了:再拓展会不会变得很难? 放心吧,不会很难,相信我,老周从来不会讲大家都看不懂的东西的。

    我们现在不妨尝试一下,在客户端不添加服务引用,而是由我们自己来编写调用服务的代理类。要做到这一点,首先我们要明确的,其实我们所编写的服务协定,在服务器和客户端都需要用到,如果大家查看过添加服务引用时由工具生成的代码,会发现其实它在客户端也生成了服务协定的代码。所以,在我们手动编写调用服务的代码时,也需要这样,因此有两种方法可以在服务器和客户端之间共用服务协定,一是把代码复制一下粘贴到客户端中,另一种方法,我们可以新建一个类库,然后把服务协定写到这个类库中,最后在服务器端和客户端都引用这个类库即可。举个例子,假如有以下定义的协定:

    
     
    1. [ServiceContract]

    2. public interface ITest

    3. {

    4. [OperationContract]

    5. int Add(int a, int b);

    6.  
    7. [OperationContract]

    8. int GetRandmon();

    9.  
    10. [OperationContract]

    11. int Multiply(int a, int b);

    12. }

    然后,我们在服务器端实现协定,注意:接口在服务器端实现即可,客户端不需要。

    
     
    1. // 实现服务

    2. public class MyService : CommonLib.ITest

    3. {

    4. Random m_rand = null;

    5.  
    6. // 构造函数

    7. public MyService()

    8. {

    9. m_rand = new Random();

    10. }

    11.  
    12. public int Add(int a, int b)

    13. {

    14. return a + b;

    15. }

    16.  
    17. public int GetRandmon()

    18. {

    19. return m_rand.Next();

    20. }

    21.  
    22. public int Multiply(int a, int b)

    23. {

    24. return a * b;

    25. }

    26. }


    接着,和以前一样,创建服务主机,并侦听客户端调用。

    
     
    1. static void Main(string[] args)

    2. {

    3. ServiceHost host = new ServiceHost(typeof(MyService));

    4. // HTTP方式

    5. WSHttpBinding httpBinding = new WSHttpBinding(SecurityMode.None);

    6. host.AddServiceEndpoint(typeof(CommonLib.ITest), httpBinding, "http://localhost:8900/");

    7. // TCP方式

    8. NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.None);

    9. host.AddServiceEndpoint(typeof(CommonLib.ITest), tcpBinding, "net.tcp://localhost:1700/");

    10. // 打开服务

    11. host.Open();

    12. Console.WriteLine("服务已启动。");

    13. Console.Read();

    14. host.Close();

    15. }

    大家可以细心看一下,和以前的代码有什么不同? 不妨比较一下,看看。

    1、以前,我们在创建ServiceHost时会指定一个HTTP基地址,但这里不需要了,基址是便于工具生成代理类的,我们既然要手动来写了,就不用生成代码了,也不用基址了。

    2、以前,我们会在ServiceHost.Description.Behaviors集合中加一个ServiceMetadataBehavior对象,以提供WSDL,帮肋工具生成代码。现在我们都自己手动写了,当然就不用提供WSDL了。

    认真想想,看是不是这样? 如果你有兴趣,也可以为ServiceHost弄一个基址,但不添加ServiceMetadataBehavior,然后在客户端项目中添加引用,你会发现……呵呵,你懂的。

    那现在在客户端怎么调用服务呢? 使用通道,可能有朋友会看到IChannel接口,又派生出很多接口,但貌似没有一个是类的,是不是要自己来写通道啊? 不用,当然你要扩展通道层是另一回事,通常我们无需扩展通道,因为现有的已经足够牛逼了。我们在“对象浏览器”中是看不到与通道相关的可用的类,因为.NET内部是有实现的,只是没有定义为public而已,是internal。

    我们根本可以不必理会如何找通道的问题,就好像我们坐在一辆全自动导航或者有专业司机驾驶的车上,司机知道怎么走,我们不必要担心不知道怎么走这段路。同理,我们可以不直接操作通道,为什么呢?因为我们定义的每一个服务协定都可以认为是一个通道。

    上面我们定义的那么ITest就是一个通道,WCF内部已经帮我们把它变成一个通道了,不信的话,你往后看例子。

    我们已经知道,编写的服务协定可以当成一个通道来操作,所以,在客户端中,我们要手动写代码来调用服务,要可以遵循以下步骤,有兴趣的话你可以背下来,但告诉你,背了没用。

    1、创建与服务器匹配的Binding,这个就不用怀疑的了,你跟别人签合同,那肯定是一式两份,对方持一份,你拿一份,你肯定不会拿一张白纸回家保存吧。

    2、创建通道,使用ChannelFactory<TChannel>类可以创建通道,因为它是“工厂”嘛,工厂当然是用来生产的,但ChannelFactory工厂不是用来生产老鼠药也不是生产地雷的,它是专门生产Channel(通道)的。这个TChannel就可以写上你定义的服务协定的接口,如上面的ITest。

    3、得到的通道就是ITest,然后就可以调用服务了,比如要两个数相加,就调用ITest.Add。

    4、关闭通道,把ITest强制转换为IClientChannel就可以调用Close方法关闭通道。

    可能你对这些步骤还有疑问,没关系,你不妨先疑一下。我们继续往下操作。

    前面定义服务主机的时候,我们使用了两个终结点,一个是HTTP的,另一个是TCP调用。所以我们这里也要分别用这两种方法调用。我可没说一定要用这两种方法调用,我只是多写了一个作演示。

    在客户端,先声明这两个终结点地址,就是我们在服务器定义的两个地址。

    
     
    1. EndpointAddress edpHttp = new EndpointAddress("http://localhost:8900/");

    2. EndpointAddress edpTcp = new EndpointAddress("net.tcp://localhost:1700/");

    然后,分别用两种Binding调有服务。

    
     
    1. private void btnHTTP_Click(object sender, EventArgs e)

    2. {

    3. // 创建Binding

    4. WSHttpBinding httpBinding = new WSHttpBinding(SecurityMode.None);

    5. // 创建通道

    6. ChannelFactory<CommonLib.ITest> factory = new ChannelFactory<CommonLib.ITest>(httpBinding);

    7. CommonLib.ITest channel = factory.CreateChannel(edpHttp);

    8. // 调用

    9. int resAdd = channel.Add(int.Parse(txtNum11.Text), int.Parse(txtNum12.Text));

    10. txtResAdd.Text = resAdd.ToString();

    11.  
    12. int resMult = channel.Multiply(int.Parse(txtNum21.Text), int.Parse(txtNum22.Text));

    13. txtResMulti.Text = resMult.ToString();

    14.  
    15. int rand = channel.GetRandmon();

    16. txtRand.Text = rand.ToString();

    17.  
    18. // 关闭通道

    19. ((IClientChannel)channel).Close();

    20. }

    21.  
    22. private void btnTCP_Click(object sender, EventArgs e)

    23. {

    24. // 创建Binding

    25. NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.None);

    26. // 创建通道

    27. ChannelFactory<CommonLib.ITest> factory = new ChannelFactory<CommonLib.ITest>(tcpBinding);

    28. CommonLib.ITest channel = factory.CreateChannel(edpTcp);

    29. // 调用

    30. txtResAdd.Text = channel.Add(int.Parse(txtNum11.Text),int.Parse(txtNum12.Text)).ToString();

    31. txtResMulti.Text = channel.Multiply(int.Parse(txtNum21.Text),int.Parse(txtNum22.Text)).ToString();

    32. txtRand.Text = channel.GetRandmon().ToString();

    33.  
    34. // 关闭通道

    35. ((IClientChannel)channel).Close();

    36. }

    你也许会问,ITest不是接口来的吗,怎么可以调用? 别忘了,我们在服务器端已经实现过了,WCF内部会帮我们找到关联的类。

    现在,你就兴奋地看看结果吧。记着,运行服务器端需要管理员身份运行,这个我说了三千五百遍了。

    嘿嘿,乍一看,好像可以了,已经能调用了,但是,这样是不是不太简洁呢? 而且我们不能将其当成一人类来用,每次调用要通过ChannelFactory来生产,比较麻烦,更重要的是,如果有服务器回调协定,就不好弄了。

    因此,对于上面的客户端代码我们是否考虑进一个封装呢? 这里我们完全可以考虑使用ClientBase<TChannel>类,它对于通道和相关操作作了进一步封装,当然它是抽象类,不能直接拿来玩,要先派生出一个类。

    
     
    1. /// <summary>

    2. /// 用于调用服务的类

    3. /// </summary>

    4. public class MyClient : ClientBase<CommonLib.ITest>,CommonLib.ITest

    5. {

    6. public MyClient(System.ServiceModel.Channels.Binding binding, EndpointAddress edpAddr)

    7. : base(binding, edpAddr) { }

    8.  
    9. public int Add(int a, int b)

    10. {

    11. return base.Channel.Add(a, b);

    12. }

    13.  
    14. public int GetRandmon()

    15. {

    16. return base.Channel.GetRandmon();

    17. }

    18.  
    19. public int Multiply(int a, int b)

    20. {

    21. return base.Channel.Multiply(a, b);

    22. }

    23. }

    有人会问,为什么从ClientBase<CommonLib.ITest>派生,又要实现一次CommonLib.ITest接口呢? 当然,你不实现也无所谓,再实现一次CommonLib.ITest接口是为了让这个类的公共方法和ITest的方法一样,这样方便调用。

    通过访问base.Channel就可以得到一个对ITest的引用,无需要我们自己创建通道,因为基类中已经带了默认实现。

    现在,把前面的调用代码改一下,是不是觉得简洁了?

    
     
    1. private void btnHTTP_Click(object sender, EventArgs e)

    2. {

    3. MyClient client = new MyClient(new WSHttpBinding(SecurityMode.None), edpHttp);

    4. txtResAdd.Text = client.Add(int.Parse(txtNum11.Text), int.Parse(txtNum12.Text)).ToString();

    5. txtResMulti.Text = client.Multiply(int.Parse(txtNum21.Text), int.Parse(txtNum22.Text)).ToString();

    6. txtRand.Text = client.GetRandmon().ToString();

    7. }

    8.  
    9. private void btnTCP_Click(object sender, EventArgs e)

    10. {

    11. MyClient client = new MyClient(new NetTcpBinding(SecurityMode.None), edpTcp);

    12. txtResAdd.Text = client.Add(int.Parse(txtNum11.Text), int.Parse(txtNum12.Text)).ToString();

    13. txtResMulti.Text = client.Multiply(int.Parse(txtNum21.Text), int.Parse(txtNum22.Text)).ToString();

    14. txtRand.Text = client.GetRandmon().ToString();

    15. }

    现在看看我们自己写的这段代码,是不是与VS生成的代码比较接近了? 而且连配置文件也省了。

  • 相关阅读:
    KindEditor-编辑器配置参数属性
    泛型作为返回类型的写法
    ObservableCollection<T> 类
    常遇到的问题
    实现跨浏览器html5表单验证
    mysql 密码重置
    Web用户的身份验证及WebApi权限验证流程的设计和实现
    Discuz3.2 新用户插入数据库SQL
    3. 深入研究 UCenter API 之 加密与解密(转载)
    window.open实现模式窗口
  • 原文地址:https://www.cnblogs.com/grj001/p/12223783.html
Copyright © 2011-2022 走看看