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

22 篇文章 0 订阅

http://foreversky12.iteye.com/blog/2308191


原文:http://www.wcftutorial.net/Introduction-to-WCF.aspx 

自托管Self Hosting 

如果是web服务,我们只能将服务托管在IIS上,但是WCF提供了让服务可以托管在任何应用程序(比如控制台应用程序,Windows Form等等)中的能力。许多有趣开发者有责任提供和管理托管进程的生命周期。服务端和客户端可以同时存在在相同的进程中。现在,让我们创建一个托管在控制台应用程序中的WCF服务。我们将会以抽象类ClientBase为基类创建一个代理。 

Note:托管程序必须在客户端访问之前运行,这也意味着你得预启动它。 

Step1: 让我们从创建服务七月和它的实现开始吧。创建一个控制台应用程序,并且命名为MyCalculatorService。这是一个简单的服务,它只是返回两个数想加的和。 



Step 2: 添加System.ServiceModel引用 

 

Step 3: 创建ISimpleCalculator接口,加入ServiceContract和OperationContract到类和方法上,你会在随后的内容中了解它们的作用。这些Attribute将会把服务公布给调用方。 

IMyCalculatorService.cs 

C#代码   收藏代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.ServiceModel;  
  6.   
  7. namespace MyCalculatorService  
  8. {  
  9.     [ServiceContract()]  
  10.     public interface ISimpleCalculator  
  11.     {  
  12.         [OperationContract()]  
  13.         int Add(int num1, int num2);  
  14.     }  
  15. }  


Step 4: 如下所示,MyCalculatorService是IMyCalculatorService接口的实现类 

MyCalculatorService.cs  

C#代码   收藏代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace MyCalculatorService  
  7. {  
  8.     class SimpleCalculator : ISimpleCalculator  
  9.     {  
  10.         public int Add(int num1, int num2)  
  11.         {  
  12.             return num1 + num2;  
  13.         }  
  14.     }  
  15. }  


Step 5: 现在服务已经准备好了,让我们开始实现托管进程吧,创建一个新的控制台应用程序MyCalculatorServiceHost 



Step 6: ServiceHost是托管WCF服务的核心类。它的构造函数可以接受已经实现了的契约类和服务地址作为参数,你可以用逗号分隔注册多个基地址,但是地址必须基于同样的传输协议(http等) 

C#代码   收藏代码
  1. Uri httpUrl = new Uri("http://localhost:8090/MyService/SimpleCalculator");  
  2.   
  3. Uri tcpUrl = new Uri("net.tcp://localhost:8090/MyService/SimpleCalculator");  
  4.   
  5. ServiceHost host = new ServiceHost(typeof(MyCalculatorService.SimpleCalculator), httpUrl, tcpUrl);  


使用AddServiceEndpoint()可以加入多个终结点。host.Open()将启动服务,之后就可以为客户端提供服务了。 

Step 7: 以下程序说明了如果实现托管进程 

C#代码   收藏代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.ServiceModel;  
  6. using System.ServiceModel.Description;  
  7.   
  8. namespace MyCalculatorServiceHost  
  9. {  
  10.     class Program  
  11.     {  
  12.         static void Main(string[] args)  
  13.         {  
  14.             //Create a URI to serve as the base address  
  15.             Uri httpUrl = new Uri("http://localhost:8090/MyService/SimpleCalculator");  
  16.             //Create ServiceHost  
  17.             ServiceHost host   
  18.             = new ServiceHost(typeof(MyCalculatorService.SimpleCalculator), httpUrl);  
  19.             //Add a service endpoint  
  20.             host.AddServiceEndpoint(typeof(MyCalculatorService.ISimpleCalculator)  
  21.             , new WSHttpBinding(), "");  
  22.             //Enable metadata exchange  
  23.             ServiceMetadataBehavior smb = new ServiceMetadataBehavior();  
  24.             smb.HttpGetEnabled = true;  
  25.             host.Description.Behaviors.Add(smb);  
  26.             //Start the Service  
  27.             host.Open();  
  28.   
  29.             Console.WriteLine("Service is host at " + DateTime.Now.ToString());  
  30.             Console.WriteLine("Host is running... Press <Enter> key to stop");  
  31.             Console.ReadLine();  
  32.         }  
  33.     }  
  34. }  


Step 8: 服务一旦被托管启动,我们就可以创建为客户端服务的带来类了,创建代理有不同的方法 
    1.使用SvcUtil.exe创建代理以及相关联的配置文件 
    2.将服务引用添加到客户端程序 
    3.实现基类ClientBase<T> 
在这些方法中,实现ClientBase<T>是最好的实践。假如你使用另外两个,当服务实现发生变化的时候,你需要每次都创建代理类。但是使用ClientBase<T>就不需要。它会在启动的时候创建代理,所以它可以处理任何变化。 

MyCalculatorServiceProxy.cs  

C#代码   收藏代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.ServiceModel;  
  6. using MyCalculatorService;  
  7. namespace MyCalculatorServiceProxy  
  8. {  
  9.     public class MyCalculatorServiceProxy :   
  10.         //WCF create proxy for ISimpleCalculator using ClientBase  
  11.         ClientBase<ISimpleCalculator>,  
  12.         ISimpleCalculator  
  13.     {  
  14.         public int Add(int num1, int num2)  
  15.         {  
  16.             //Call base to do funtion  
  17.             return base.Channel.Add(num1, num2);  
  18.         }  
  19.     }  
  20. }  


Step 9: 在客户端,我们创建代理类的实例,并且使用如下的方法调用,将代理dll的参照加入到这个工程中。 

C#代码   收藏代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.ServiceModel;  
  6.   
  7. namespace MyCalculatorServiceClient  
  8. {  
  9.     class Program  
  10.     {  
  11.         static void Main(string[] args)  
  12.         {  
  13.             MyCalculatorServiceProxy.MyCalculatorServiceProxy proxy ;  
  14.             proxy= new MyCalculatorServiceProxy.MyCalculatorServiceProxy();  
  15.             Console.WriteLine("Client is running at " + DateTime.Now.ToString());  
  16.             Console.WriteLine("Sum of two numbers... 5+5 ="+proxy.Add(5,5));  
  17.             Console.ReadLine();  
  18.         }  
  19.     }  
  20. }  


Step 10 : 终结点信息需要被加入到客户端程序的配置文件中。 

Xml代码   收藏代码
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <configuration>  
  3.     <system.serviceModel>  
  4.         <client>  
  5.             <endpoint address ="http://localhost:8090/MyService/SimpleCalculator"   
  6.                     binding ="wsHttpBinding"  
  7.                     contract ="MyCalculatorService.ISimpleCalculator">  
  8.             </endpoint>  
  9.         </client>  
  10.     </system.serviceModel>  
  11. </configuration>  


Step 11: 运行客户端之前,你需要先启动服务端,客户端的输出如下所示。 

 

自托管展现了in-Pro hosting的好处。以编程的方式访问,并且可以编程为单例服务。我系统你会喜欢上自托管的方式,现在让我们进入下一步,将服务托管在Windows Activation service中。







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


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

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

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

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

[csharp]  view plain  copy
 print ?
  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. }  

 

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

[csharp]  view plain  copy
 print ?
  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. }  


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

[csharp]  view plain  copy
 print ?
  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调用。所以我们这里也要分别用这两种方法调用。我可没说一定要用这两种方法调用,我只是多写了一个作演示。

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

[csharp]  view plain  copy
 print ?
  1. EndpointAddress edpHttp = new EndpointAddress("http://localhost:8900/");  
  2. EndpointAddress edpTcp = new EndpointAddress("net.tcp://localhost:1700/");  

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

[csharp]  view plain  copy
 print ?
  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>类,它对于通道和相关操作作了进一步封装,当然它是抽象类,不能直接拿来玩,要先派生出一个类。

[csharp]  view plain  copy
 print ?
  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的引用,无需要我们自己创建通道,因为基类中已经带了默认实现。

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

[csharp]  view plain  copy
 print ?
  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生成的代码比较接近了? 而且连配置文件也省了。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值