WCF服务创建实例说明

创建服务端程序

WCF的服务必须有一个宿主来承载,这个宿主的选择很多,控制台程序、窗体程序、IISWindows服务等等。我就选一个最简单的控制台程序来承载。

WCF技术涉及的知识点和概念庞杂零散,时常会被各种教材资料开头便抛出的诸多名词概念弄晕,因此我们先了解必备的知识,尽快进入动手编码。

 

(1) 体系中的角色。

WCF的结构中,至少应该有两个角色:服务端和客户端。服务端公开一个或多个服务,客户端访问这些服务以获得相应的逻辑。

(2) 服务(Service)和操作(Operation)

服务端公开了一个或多个服务,每个服务都拥有一个或多个操作,客户端通过调用服务的操作来获得服务提供的逻辑。

(3) 终结点(EndPoint)

服务端公开了服务和操作,客户端如何访问到他们呢?就是通过终结点,终结点相当于服务的地址,类似于门牌号,客户端就是循着这个地址来找到服务和操作的。终结点是和服务相对应的,找到了终结点就找到了服务,找到了服务就获得了操作。

(4) 绑定(Binding)

绑定描述了客户端与服务端沟通的方式,双方沟通的语言,或者说协议,双方必须保持一致才能相互沟通,比如一个服务端把服务公开,要求必须用http协议来访问,那么此时绑定就是http绑定,客户端必须用http的方式来与这个服务端通信才能访问到服务。

(5) 元数据(Metadata)

现在服务端公开了服务,客户端循着正确的终结点,持着正确的绑定找到了服务端,此时服务端笑而不语,客户端不知所措,因为客户端根本不知道服务端公布了那些服务、操作、数据。客户端一个调用也写不出来。虽然客户端不需知道服务和操作的具体实现细节,但是它必须知道服务的接口描述(或类型描述)、操作的方法签名,数据的类描述,这些描述就叫做元数据,服务端必须通过某种方法把元数据交给客户端,这种方法叫做元数据交换(MetadataExchange)

 

 

以下是服务端程序的所有代码:

[csharp] view plaincopy

1.  using System;  

2.  using System.Collections.Generic;  

3.  using System.Linq;  

4.  using System.Text;  

5.    

6.  using System.ServiceModel;  

7.  using System.ServiceModel.Description;  

8.    

9.  namespace HelloWCFService  

10. {  

11.     class Program  

12.     {  

13.         static void Main(string[] args)  

14.         {  

15.             Uri baseAddress = new Uri("http://localhost:8000/MyService");  

16.   

17.             ServiceHost host = new ServiceHost(typeof(HelloWCFService), baseAddress);  

18.   

19.             host.AddServiceEndpoint(typeof(IHelloWCFService), new WSHttpBinding(), "HelloWCFService");  

20.   

21.             ServiceMetadataBehavior smb = new ServiceMetadataBehavior();  

22.             smb.HttpGetEnabled = true;  

23.             host.Description.Behaviors.Add(smb);  

24.   

25.             host.Open();  

26.   

27.             Console.WriteLine("Service is Ready");  

28.             Console.WriteLine("Press Any Key to Terminated...");  

29.             Console.ReadLine();  

30.   

31.             host.Close();  

32.         }  

33.     }  

34.   

35.     [ServiceContract]  

36.     interface IHelloWCFService  

37.     {  

38.         [OperationContract]  

39.         string HelloWCF();  

40.     }  

41.   

42.     public class HelloWCFService : IHelloWCFService  

43.     {  

44.         public string HelloWCF()  

45.         {  

46.             return "Hello WCF!";  

47.         }  

48.     }              

49. }  

 

为了验证我们的宿主和服务运行正常,在上面的程序运行的时候,不要关掉它,然后打开浏览器,输入服务地址:http://localhost:8000/MyService 

我们还没有启用元数据交换,也就是说客户端还没有得到关于服务协定的任何信息,它无法做出任何调用。所以,我们得想个办法让服务端把自己的服务协定元数据交给客户端。

启用元数据交换,是通过服务元数据行为的相关设置来实现的,要进行这方面的设置,首先要引入命名空间:System.ServiceModel.Description

接下来,我们创建一个服务元数据行为对象。然后把它加入到宿主ServiceHost对象的行为集合中去,这样元数据交换就被打开了

以下代码建立一个服务元数据行为对象

1.  ServiceMetadataBehavior smb = new ServiceMetadataBehavior();  

2.   构造函数没有参数,这真是我们喜闻乐见的。

3.    

4.   服务元数据行为有一个属性HttpGetEnabled,我们要把它设为true ,这样,客户端就可用HTTPGET的方法从服务端下载元数据了。

5.   以下代码设置HttpGetEnabled属性

最后,把这个服务元数据行为对象添加到宿主ServiceHost 对象的行为集合中去。

这样,服务的元数据交换就配置好了。(其实这样不算配置好,必须添加元数据交换终结点才行,只是.Net Framework 4.0替我们做了,以后再展开)

 

首先要保证服务端的宿主是运行着的,然后右击客户端项目中的"引用",选择"添加服务引用"

在弹出的对话框中的地址一栏输入服务端的服务地址(http://localhost:8000/MyService),点击"前往"VS2010会前往服务地址处下载元数据,然后报告发现的服务,在下面的命名空间中为我们的元数据类型建立一个新的命名空间,这里可以随意命名,我就把她命名为MyService

 

 

客户端部分的完整代码:

[csharp] view plaincopy

1.  using System;  

2.  using System.Collections.Generic;  

3.  using System.Linq;  

4.  using System.Text;  

5.    

6.  namespace HelloWCFClient  

7.  {  

8.      class Program  

9.      {  

10.         static void Main(string[] args)  

11.         {  

12.             MyService.HelloWCFServiceClient client = new MyService.HelloWCFServiceClient();  

13.   

14.             string strRet = client.HelloWCF();  

15.   

16.             Console.WriteLine(strRet);  

17.             Console.ReadLine();  

18.   

19.             client.Close();  

20.         }  

21.     }  

22. }  

23. 3. 总结

24. 通过这个例子,我们以最简单的方式实现了一个WCF程序,我们应该了解一个WCF程序最基本的编程步骤。

25. (1) 定义服务协定接口和接口中的操作协定。

26. (2) 用一个服务类实现服务协定接口和接口中的操作协定。

27. (3) 建立一个宿主程序用来寄存服务。

28. [针对控制台程序宿主]

29. (4) 为服务建立ServiceHost对象并指定要寄存的服务类和服务基地址。

30. (5) ServiceHost添加终结点,指定其服务协定、绑定类型和终结点地址(或相对地址)

31. (6) ServiceHost添加服务元数据行为对象,要将该对象的HttpGetEnabled属性设为true 以启动元数据交换。

32. (7) 启动ServiceHost

33. (8) 建立客户端。

34. (9) 为客户端添加指向服务基地址的服务引用以下载元数据。

35. (10) 客户端使用代理类调用服务操作。

36.  

37. 简而言之。

38. (1) 定义服务协定。

39. (2) 实现服务协定。

40. (3) 配置和建立宿主。

41. (4) 配置和建立客户端。

42. (5) 调用服务。

 

 

现在我们准备开始配置一个服务,服务配置元素标签为<services></services>,是<system.serviceModel>的子节,在一个宿主上可以承载许多服务,每一个服务用<service></service>来配置,它是<services>的子节。在配置<service>前,我们还要先添加一个基地址配置,基地址用<baseaddress>描述,他是<host>的子节,<host><service>的子节。

 

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <services>  

5.        <service name="HelloWCFService.HelloWCFService">  

6.          <host>  

7.            <baseAddresses>  

8.              <add baseAddress="http://localhost:8000/MyService"/>  

9.            </baseAddresses>  

10.         </host>  

11.         <endpoint address="HelloWCFService" binding="wsHttpBinding" contract="HelloWCFService.IHelloWCFService" />  

12.       </service>  

13.     </services>      

14.   </system.serviceModel>  

15. </configuration>  

 

 

第二篇 配置WCF

 

在上一篇中,我们在一个控制台应用程序中编写了一个简单的WCF服务并承载了它。先回顾一下服务端的代码:

 

[csharp] view plaincopy

1.  using System;  

2.  using System.Collections.Generic;  

3.  using System.Linq;  

4.  using System.Text;  

5.    

6.  using System.ServiceModel;  

7.  using System.ServiceModel.Description;  

8.    

9.  namespace HelloWCFService  

10. {  

11.     class Program  

12.     {  

13.         static void Main(string[] args)  

14.         {  

15.             Uri baseAddress = new Uri("http://localhost:8000/MyService");  

16.   

17.             ServiceHost host = new ServiceHost(typeof(HelloWCFService), baseAddress);  

18.   

19.             host.AddServiceEndpoint(typeof(IHelloWCFService), new WSHttpBinding(), "HelloWCFService");  

20.   

21.             ServiceMetadataBehavior smb = new ServiceMetadataBehavior();  

22.             smb.HttpGetEnabled = true;  

23.             host.Description.Behaviors.Add(smb);  

24.   

25.             host.Open();  

26.   

27.             Console.WriteLine("Service is Ready");  

28.             Console.WriteLine("Press Any Key to Terminate...");  

29.             Console.ReadLine();  

30.   

31.             host.Close();  

32.               

33.         }  

34.     }  

35.   

36.     [ServiceContract]  

37.     interface IHelloWCFService  

38.     {  

39.         [OperationContract]  

40.         string HelloWCF();  

41.     }  

42.   

43.     public class HelloWCFService : IHelloWCFService  

44.     {  

45.         public string HelloWCF()  

46.         {  

47.             return "Hello WCF!";  

48.         }  

49.     }  

50. }  


所有的这些代码都写在program.cs中,干净清爽。

 

我们稍微审视一下这段程序会发现,我们用了很多的代码来定制服务的特性,例如基地址、终结点、绑定、行为等。这些都叫做配置。而真正对服务的本身的定义是很少的(主逻辑就是返回一个字符串而已),因此我们不难看出,WCF的编程中配置占了很大的比重。

 

WCF的配置选项是很多的,我们这里只考虑最简单的情况。我们在定义和实现了服务协定后,至少应该做哪些配置才能让服务运行起来呢?

(1) 依据服务实现类配置一个服务(ServiceHost)

(2) 指定一个基地址(如果终结点中指定了绝对地址,这步可以省略)

(3) 建立一个终结点,并为其指定地址、绑定和服务协定。

(4) 建立一个元数据交换终结点。

(5) 为服务添加一个行为来启用元数据交换。

虽然在.Net 4.0下微软提供了简化配置,我们甚至可以一行配置都不做,但是为了搞清楚配置的基本原理,我们暂时不考虑简化配置的情况。

 

以下这些配置是我们必须要做的,我们从代码中可以看到以上几种配置相应语句:

 

建立基地址:

[csharp] view plaincopy

1.  Uri baseAddress = new Uri("http://localhost:8000/MyService");  


建立服务:

[csharp] view plaincopy

1.  ServiceHost host = new ServiceHost(typeof(HelloWCFService), baseAddress);  


建立终结点并指定地址、绑定和服务协定:

[csharp] view plaincopy

1.  host.AddServiceEndpoint(typeof(IHelloWCFService), new WSHttpBinding(), "HelloWCFService");  


添加元数据交换终结点并添加启用元数据交换行为

[csharp] view plaincopy

1.  ServiceMetadataBehavior smb = new ServiceMetadataBehavior();  

2.  smb.HttpGetEnabled = true;  

3.  host.Description.Behaviors.Add(smb);  


看上去清楚明白,但是只是看上去很美,这样的配置方式存在弊端,例如基地址,如果当服务部署之后迁移了服务器,基地址发生变化,我们必须修改源程序并重新编译重新部署才能实现这个要求。对于其他的配置选项亦是如此。这对于产品环境是不能接受的。好在WCF提供针对这个问题的解决方案:配置文件。

 

我们把对服务的配置写在应用程序的配置文件中(IIS程序是web.config 其他程序是app.config),当配置发生改变的时候我们就不用重新编译程序集了。

 

配置文件的写法很复杂,有很多选项,为了便于上手,我们先从跟本例相关的选项开始。

 

在配置文件中,根节是<configuration>,所有的配置元素都位于其中。对于WCF服务的配置部分,最外层的节是<system.serviceModel>,所以配置文件中至少先应该是这个样子:

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.        

5.    </system.serviceModel>  

6.  </configuration>  


现在我们准备开始配置一个服务,服务配置元素标签为<services></services>,是<system.serviceModel>的子节,在一个宿主上可以承载许多服务,每一个服务用<service></service>来配置,它是<services>的子节。在配置<service>前,我们还要先添加一个基地址配置,基地址用<baseaddress>描述,他是<host>的子节,<host><service>的子节。

晕了么...慢慢来。

先把<services>节加上,这里可以容纳许多服务,注意这个是带s

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <services>  

5.          

6.      </services>      

7.    </system.serviceModel>  

8.  </configuration>  


<services>的怀抱中,我们添加一个<service>,这是我们要配置的服务本体,注意这个是不带s

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <services>  

5.        <service>  

6.            

7.        </service>  

8.      </services>      

9.    </system.serviceModel>  

10. </configuration>  


<service>中,添加一个基地址,先添加一个<host>再添加一个<baseaddress>

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <services>  

5.        <service>  

6.          <host>  

7.            <baseAddresses>  

8.              <add baseAddress="http://localhost:8000/MyService"/>  

9.            </baseAddresses>  

10.         </host>  

11.       </service>  

12.     </services>      

13.   </system.serviceModel>  

14. </configuration>  

 

到这里,基地址的部分已经完成,对应代码中的位置你找到了么?

服务的配置还差一点,我们在代码中为服务指定了实现类型,在配置文件中如何指定呢?就用<service>标签的name属性,指定的时候后要用完全限定名(带着命名空间)

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <services>  

5.        <service name="HelloWCFService.HelloWCFService">  

6.          <host>  

7.            <baseAddresses>  

8.              <add baseAddress="http://localhost:8000/MyService"/>  

9.            </baseAddresses>  

10.         </host>  

11.       </service>  

12.     </services>      

13.   </system.serviceModel>  

14. </configuration>  

我这个例子举的不好了,命名空间和实现类型是一个名字,要注意区分。

 

到这里,服务也配置完了,对应代码的位置翻上去找一下。

接下来配置终结点,终结点用<endpoint>元素表示,正如代码实现中的参数,在配置中也需要一一指定地址、绑定和服务协定接口,分别用addressbindingcontract属性来表示,当然<endpoint>也是<service>的子节,毕竟他是属于服务的嘛。

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <services>  

5.        <service name="HelloWCFService.HelloWCFService">  

6.          <host>  

7.            <baseAddresses>  

8.              <add baseAddress="http://localhost:8000/MyService"/>  

9.            </baseAddresses>  

10.         </host>  

11.         <endpoint address="HelloWCFService" binding="wsHttpBinding" contract="HelloWCFService.IHelloWCFService" />  

12.       </service>  

13.     </services>      

14.   </system.serviceModel>  

15. </configuration>  

这里用了相对地址"HelloWCFService",他会和基地址组合在一起(排在后面)成为终结点的地址,这里也可以指定为空字符串"",此时基地址(即服务地址)就是终结点的地址,还可以指定一个绝对地址,那样他会覆盖基地址,基地址对这个终结点来说就不起作用了,这里可以看出,终结点的地址是独立的,可以是基地址的子地址,也可以独立使用另一个地址,他们之间没有必然的链接。

这里的contract <service>里面一样,也要使用完全限定名称(带上命名空间)

注意,在使用IIS承载的时候,必须使用相对地址,也就是终结点必须是基地址的子地址,这是由IIS的部署结构决定的。

 

到这里终结点也配置完成,对应代码的位置找到了吗?

 

接下来是最后一步(或者说两步),配置元数据交换终结点并开启元数据交换行为。这个过程,代码中用了三行,实际上代码这三行仅仅是添加了元数据交换行为,并没有配置元数据交换终结点,运行时系统为我们自动添加了终结点。这两点缺一不可,虽然系统会为我们添加,我们还是要知道这个配置的写法。这个很重要。

 

开启元数据交换从原理上应该是两件事,第一是服务允许元数据交换,第二是服务提供元数据交换方式,第一条就是添加元数据交换行为,表示服务允许这样的请求,第二条就是服务告诉你如何请求,客户端是通过一个固定的终结点去请求的,这个终结点的地址、绑定和协定都是固定的,我们不能更改,这个是框架的约定,我们只能按要求配置。

 

首先,第一条,允许元数据交换,这个是通过为服务添加一个行为来实现的,行为是用来描述服务的特性的,不同的服务可能有相同的行为,行为并不是归某服务独有的,因此行为被定义为<system.serviceModel>节的子节,可以被不同的服务引用,他的定义有些像服务,外面是带s的,里面是不带s的,毕竟可能有许多的行为定义嘛。

先定义个行为:

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <services>  

5.        <service name="HelloWCFService.HelloWCFService">  

6.          <host>  

7.            <baseAddresses>  

8.              <add baseAddress="http://localhost:8000/MyService"/>  

9.            </baseAddresses>  

10.         </host>  

11.         <endpoint address="HelloWCFService" binding="wsHttpBinding" contract="HelloWCFService.IHelloWCFService" />  

12.       </service>  

13.     </services>  

14.     <behaviors>  

15.       <serviceBehaviors>  

16.         <behavior name="metaExchange">  

17.           <serviceMetadata httpGetEnabled="true"/>  

18.         </behavior>  

19.       </serviceBehaviors>  

20.     </behaviors>  

21.   </system.serviceModel>  

22. </configuration>  

因为存在服务行为和终结点行为之分,所有<behaviors><behavior>之间又套了一个<serviceBehaviors>,表示这个是服务行为,我们为行为制定了名字,好让<service>可以引用,也可以不指定,那么所有服务都会应用。交换元数据的行为有固定的标签描述,就是<serviceMetadata>,对着代码看,很熟悉吧。

 

建立了行为以后,要让我们刚才建立的服务去引用他,用<service>behaviorConfiguration属性:

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <services>  

5.        <service name="HelloWCFService.HelloWCFService"behaviorConfiguration="metaExchange">  

6.          <host>  

7.            <baseAddresses>  

8.              <add baseAddress="http://localhost:8000/MyService"/>  

9.            </baseAddresses>  

10.         </host>  

11.         <endpoint address="HelloWCFService" binding="wsHttpBinding" contract="HelloWCFService.IHelloWCFService" />  

12.       </service>  

13.     </services>  

14.     <behaviors>  

15.       <serviceBehaviors>  

16.         <behavior name="metaExchange">  

17.           <serviceMetadata httpGetEnabled="true"/>  

18.         </behavior>  

19.       </serviceBehaviors>  

20.     </behaviors>  

21.   </system.serviceModel>  

22. </configuration>  

 

接下来第二步,建立元数据交换终结点,建立的位置和我们刚才建立的终结点位置相同,但是属性是固定的,大小写都不能写错。

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <services>  

5.        <service name="HelloWCFService.HelloWCFService" behaviorConfiguration="metaExchange">  

6.          <host>  

7.            <baseAddresses>  

8.              <add baseAddress="http://localhost:8000/MyService"/>  

9.            </baseAddresses>  

10.         </host>  

11.         <endpoint address="HelloWCFService" binding="wsHttpBinding" contract="HelloWCFService.IHelloWCFService" />  

12.         <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>  

13.       </service>  

14.     </services>  

15.     <behaviors>  

16.       <serviceBehaviors>  

17.         <behavior name="metaExchange">  

18.           <serviceMetadata httpGetEnabled="true"/>  

19.         </behavior>  

20.       </serviceBehaviors>  

21.     </behaviors>  

22.   </system.serviceModel>  

23. </configuration>  


到这里,配置文件就写完了。我们把它放到程序里面去,打开上一篇中建立的服务端程序,为程序添加一个配置文件

右键点击项目->添加->新建项->应用程序配置文件,名字系统会起好(app.config)。把上面的配置写进去,保存。

既然我们已经有了配置文件,就不需要(也不应该)在代码中配置了。代码中的配置会覆盖掉配置文件中的配置。所以我们要对代码修改一下。

main函数中只留下这么几行:

[csharp] view plaincopy

1.  ServiceHost host = new ServiceHost(typeof(HelloWCFService));  

2.    

3.  host.Open();  

4.    

5.  Console.WriteLine("Service is Ready");  

6.  Console.WriteLine("Press Any Key to Terminate...");  

7.  Console.ReadLine();  

8.    

9.  host.Close();  

其中,建立SeviceHost 那行被修改了,去掉了baseAddress的参数,但是我们仍需要告诉host 我们要寄存的服务类的类型。

F5运行起来。

然后在浏览器中访问一下服务试试

[html] view plaincopy

1.  http://localhost:8000/MyService  


是不是和昨天的结果一样呢。

(优快云的传图好象挂了哩...)

 

总结一下今天的学习。

我们使用配置文件的方法完成了对WCF服务的配置,从中接触到了服务、终结点和行为的配置方法。配置文件的元素还有许多,像绑定、安全性等等特性。在今后学到的时候再慢慢展开,配置文件的每一个元素都应该力求背着写下来,一行一行的写,在写的过程中体会,而不是四处复制和粘贴,这样才能对配置文件的写法有深刻的印象。

第三篇 在IIS中寄宿服务

 

通过前两篇的学习,我们了解了如何搭建一个最简单的WCF通信模型,包括定义和实现服务协定、配置服务、寄宿服务、通过添加服务引用的方式配置客户端并访问服务。我们对WCF的编程生命周期有了一个最基本的了解。

 

在前两篇中演示的例子,一定要力求背着做下来,包括源程序、配置文件都要背着一行行的手写下来,这样才能有深刻的体会。WCF的知识零散复杂,必须扎扎实实的学习和练习。如果你还没有做到了然于胸,现在赶紧翻回去把例子再做一遍。

 

今天让我们稍微深入一点,了解一些关于寄宿的新知识:在IIS中寄宿服务。

 

在前两篇的例子中,我们建立了一个控制台应用程序来作为服务的宿主,这种寄宿方式叫做"自托管",即WCF服务和应用程序是一体的。这种寄宿方式有一些优点,他需要最少的框架支持(只需要一个控制台应用程序就可以了,随处建立,随处运行),因此配置和使用都是最简单的,此外通过控制台程序还可以对WCF服务运行中发生的错误进行监视,在开发服务阶段,这种方式能提供调试的便利。

 

然而,如果作为最终产品部署,自托管的寄宿方式就不那么合适,应用程序相比框架(IISWindows服务等)是不稳定的,WCF与应用程序共享生命周期,应用程序关闭后WCF也会停止。还有许多特性诸如进程回收、空闲关闭等自托管都是不支持的。因此,为了使我们的WCF符合产品级别的要求,应该为其选择一个更稳定、伸缩性更好的宿主。

 

除了自托管,WCF还可以寄宿于IISWindows服务、Windows进程激活服务(WAS)中。比较流行的是在IISWindows进程激活服务寄宿。

 

IIS中寄宿,需要IIS5.1或更高版本的支持,IIS会为我们管理ServiceHost(还记得他吗,看第一篇中的代码),同时为我们提供进程回收、空闲关闭、进程运行状况监视等特性支持,我们只需要把服务相关的文件按照一定的组织方法放入IIS的托管中(就像建立一个网站应用程序或虚拟目录)IIS会为我们管理一切。这种托管受到支持的系统很多,从Windows XP SP2 WIndows Server 2008,所以它非常流行。然而他也有缺点,它只能接受http协议的绑定,对于tcp、管道、MSMQ都是不支持的。

 

IIS7开始,提供了所谓Windows进程激活服务(WAS) 的功能,如果把WCF寄存在WAS中,就可以支持所有的绑定协议(TCP),像MSMQ这样的协议,在内网和.Net编程模型下有很大的性能优势,因此WAS应该会成为未来WCF寄宿的主要方式,但是IIS7要求Windows Vista以上版本的系统才能支持,他的普及可能尚需时日吧。

 

我们今天先学习在IIS中寄宿,记住,IIS寄宿只支持http协议的绑定。

 

实验环境在说明一下:

Windows 7 家庭高级版 SP1

IIS7

Visual Studio2010 旗舰版 SP1

 

老老实实的学习,我们今天不借助IDE帮助建立的项目,完全手写一个寄宿于IIS的服务。

 

1. IIS应用建立物理位置

IIS应用程序需要映射到本地驱动器的一个物理路径上,我们先把它建好。

我把这个文件夹建立在了C:\WCF\下,取名为IISService(HelloWCF是我们在前两篇中建立的,还记得么)

 

 

2. 建立IIS应用程序

 物理路径建好了,现在我们在这个位置上建立一个应用程序。点击开始->控制面板,在项目中找到管理工具,然后打开IIS

 

展开左边的节点,在默认网站节点上点击右键,选择添加应用程序

 

为应用程序指定一个别名,这个可以随意起的,这个名字将成为将来服务地址的一部分,我把它起作IISService,物理路径就选择我们刚才建立的文件夹。

 

点击确定,IIS应用程序就建好了,我们可以看到在默认网站下多了这个应用程序,但是里面还什么都没有。

 

3. 建立服务文件

我们需要按照IIS宿主的要求建立几个文件放到IIS中才能承载起我们的服务,当然服务的相关信息也是描述在这些文件中的。

(1) svc文件。

svc就是service的意思了,我们需要首先建立一个XXX.svc的文件放到IIS应用程序目录下,这个文件是服务的入口,客户端需要这个文件的地址来访问服务(当然也包括原数据交换)

我们来手动建立这个文件,打开VS2010,选择文件菜单->新建->文件。在常规栏目中,选择一个文本文件,然后点击"打开"按钮。

 

这应该是个svc文件,而不是.txt文件,所以我们另存一下,另存的时候要注意保存类型选为所有文件。我把这个文件起名为HelloWCFService.svc,这个名字可以随意起,这个名字也将成为服务地址的一部分。保存位置就是我们刚刚建立IIS应用程序的位置。

 

现在我们来编辑这个文件的内容,很简单,就只有一行代码。

[html] view plaincopy

1.  <%@ServiceHost language=cDebug="true" Service="LearnWCF.HelloWCFService"%>  

<%%>框住的表示这个是一个服务器端包含,@ServiceHost 标签表示这是个WCF的服务,联想一下前两篇代码中的ServiceHost 对象。language=c# 表示我们用C#语言来写代码,Debug=true顾名思义了,最主要的是Service这个属性,他表示这个服务的实现类是什么,这里要用完全限定名,即要包括命名空间。我起了一个命名空间名LearnWCF,我们把服务定义都放在这个命名空间下,后面的HelloWCFService就是服务的实现类了。我们接下来要去完善这个类的内容。

可以看出。.svc文件就相当于一个向导,帮我们在IIS宿主中找到服务的位置,具体的代码,我们可以写在另一个地方(其实也可以写在svc文件中,不推荐)

 

把写的内容保存一下,我们继续前进。

接下来我们要写这个定义服务的类文件了。但是在这之前,我们先为类文件建立一个存放的位置,IIS的代码文件应该存放在IIS应用程序目录的App_Code子目录下,所以我们先把这个文件夹建立起来。

可以看到我们刚刚建立的HelloWCFService.svc已经被识别为WCF服务了。

 

(2) cs文件

回到VS2010,还是文件->新建->文件,选择文本文件。

这次我们要建立的是类文件,其名字为HelloWCFService.cs,注意另存为的时候要把保存类型选为所有文件,路径要选择我们刚建立的App_Code文件夹

编写这个文件,我们在这里定义和实现服务协定,应该很熟悉吧,尝试着背着写下来吧。

[csharp] view plaincopy

1.  using System;  

2.  using System.ServiceModel;  

3.    

4.  namespace LearnWCF  

5.  {  

6.      [ServiceContract]  

7.      public interface IHelloWCF  

8.      {  

9.          [OperationContract]  

10.         string HelloWCF();  

11.     }  

12.   

13.     public class HelloWCFService : IHelloWCF  

14.     {  

15.         public string HelloWCF()  

16.         {  

17.             return "Hello WCF!";  

18.         }  

19.     }  

20. }  

这个代码应该很熟练的打出来,如果对这段代码还有什么不理解的地方,赶快翻回第一篇复习一下。

 

保存一下,我们继续前进

 

(3)web.config文件

我们已经很清楚,还需要一个配置文件,在其中配置终结点、服务、行为等等的信息。这个配置文件和我们之前建立的大致相同。

还是回到VS2010,还是新建一个文本文件,另存为web.config。这个文件名,是不能改的,保存路径是我们建立的IIS应用程序IISService的目录下(svc保存在一起)

先把他写下来,再做说明:

[html] view plaincopy

1.  <configuration>  

2.    <system.serviceModel>  

3.      <services>  

4.        <service name="LearnWCF.HelloWCFService" behaviorConfiguration="metadataExchange">  

5.          <endpoint address="" binding="wsHttpBinding" contract="LearnWCF.IHelloWCF"/>  

6.          <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>  

7.        </service>  

8.      </services>  

9.      <behaviors>  

10.       <serviceBehaviors>  

11.         <behavior name="metadataExchange">  

12.           <serviceMetadata httpGetEnabled="true"/>  

13.         </behavior>  

14.       </serviceBehaviors>  

15.     </behaviors>  

16.   </system.serviceModel>  

17. </configuration>  

这个配置文件和我们之前写的有一些不同之处:

1) 配置文件的文件名为web.config,而不是app.config

2)<Service>标签没有了基地址的描述,在IIS寄宿中,服务基地址是由IIS负责指定的。例如本例中服务的基地址为

[html] view plaincopy

1.  http://localhost/IISService/HelloWCFService.svc  

3) 终结点的地址指定为了空,表示就使用服务基地址作为终结点地址,当然这里也可以指定一个相对地址,但是不能指定绝对地址,必须服从IIS指定的基地址。

其他地方并没有什么区别,特别注意在指定服务实现类和协定接口类的时候一定要带上命名空间,这是一个非常容易犯的错误。

 

保存,大功告成

4. 完成

到这里,在IIS中的寄宿就完成了,很简单,一个IIS应用程序,3个文件。当然这只是最简单的情况。

至于运营服务,IIS都会为我们去做,只要IIS应用程序(或网站)在线,服务就在线运行。

老办法,在浏览器里面看一下,是不是成功了。

IIS寄宿的服务地址格式:

[html] view plaincopy

1.  http://机器名/IIS应用程序名/XXX.svc  

所以我们这个例子的服务地址应该是:

[html] view plaincopy

1.  http://localhost/IISService/HelloWCFService.svc  


不出意外,情况如图

因为用IIS,所以系统自动给出了交换元数据的机器地址,而没有用localhost

 

5. 总结。

这一篇我们学习了如何在IIS中寄宿WCF服务,必备的要素总结如下几点:

(1) 建立IIS应用程序及物理路径

(2) 在应用程序路径下建立XXX.svc文件用于声明WCF入口和服务地址导航

(3) 在应用程序路径的子目录App_Code下建立XXX.cs文件用于定义和实现服务协定

(4) 在应用程序路径下建立web.config 用于配置服务。

(5) 保持IIS为启动状态。

 

 

第四篇 初探通信--ChannelFactory

 

通过前几篇的学习,我们简单了解了WCF的服务端-客户端模型,可以建立一个简单的WCF通信程序,并且可以把我们的服务寄宿在IIS中了。我们不禁感叹WCF模型的简单,寥寥数行代码和配置,就可以把通信建立起来。然而,仔细品味一下,这里面仍有许多疑点:服务器是如何建起服务的?我们在客户端调用一个操作后发生了什么?元数据到底是什么东西?等等。我们现在对WCF的理解应该还处于初级阶段,我们就会觉得有许多这样的谜团了。

 

虽然我们生活在WCF为我们构建的美好的应用层空间中,但是对于任何一项技术,我们都应力求做到知其所以然,对于底层知识的了解有助于我们更好的理解上层应用,因此在刚开始学习入门的时候,慢一点、细一点,我觉得是很有好处的。

 

言归正传,我们现在已经知道了一件最基本的事情,客户端和服务器是要进行通信的。那么这个通信是如何发生的呢?根据我们前面的学习,从实际操作上看,我们在服务端定义好协定和实现,配置好公开的终结点,打开元数据交换,在客户端添加服务引用,然后就直接new出来一个叫做XXXClient 的对象,这个对象拥有服务协定里的所有方法,直接调用就可以了。仔细想想?天哪,这一切是怎么发生的?!

 

服务端定义协定和实现并公开终结点,这看上去没什么问题,虽然我们对底层的实现不了解,但总归是合乎逻辑的,而客户端怎么就通过一个添加服务引用就搞定一切了呢?似乎秘密在这个添加的服务引用中。

 

打开第二篇中我们建立的客户端(如果你为第三篇的IIS服务建立了客户端,打开这个也行,我用的就是这个),看看服务引用里面有什么。

1. 服务引用初探

在解决方案浏览器中点击上方的"显示所有文件"按钮,然后展开服务引用。

这么一大堆,有一些xsd文件我们可能知道是框架描述的文档,那wsdl什么的是什么,还有disco(迪斯科?)是什么,一头雾水。

其中有一个cs文件,这个想必我们应该看得懂,打开来看看

[csharp] view plaincopy

1.  //------------------------------------------------------------------------------  

2.  // <auto-generated>  

3.  //     此代码由工具生成。  

4.  //     运行时版本:4.0.30319.261  

5.  //  

6.  //     对此文件的更改可能会导致不正确的行为,并且如果  

7.  //     重新生成代码,这些更改将会丢失。  

8.  // </auto-generated>  

9.  //------------------------------------------------------------------------------  

10.   

11. namespace ConsoleClient.LearnWCF {  

12.       

13.       

14.     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel""4.0.0.0")]  

15.     [System.ServiceModel.ServiceContractAttribute(ConfigurationName="LearnWCF.IHelloWCF")]  

16.     public interface IHelloWCF {  

17.           

18.         [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IHelloWCF/HelloWCF", ReplyAction="http://tempuri.org/IHelloWCF/HelloWCFResponse")]  

19.         string HelloWCF();  

20.     }  

21.       

22.     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel""4.0.0.0")]  

23.     public interface IHelloWCFChannel : ConsoleClient.LearnWCF.IHelloWCF, System.ServiceModel.IClientChannel {  

24.     }  

25.       

26.     [System.Diagnostics.DebuggerStepThroughAttribute()]  

27.     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel""4.0.0.0")]  

28.     public partial class HelloWCFClient : System.ServiceModel.ClientBase<ConsoleClient.LearnWCF.IHelloWCF>, ConsoleClient.LearnWCF.IHelloWCF {  

29.           

30.         public HelloWCFClient() {  

31.         }  

32.           

33.         public HelloWCFClient(string endpointConfigurationName) :   

34.                 base(endpointConfigurationName) {  

35.         }  

36.           

37.         public HelloWCFClient(string endpointConfigurationName, string remoteAddress) :   

38.                 base(endpointConfigurationName, remoteAddress) {  

39.         }  

40.           

41.         public HelloWCFClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :   

42.                 base(endpointConfigurationName, remoteAddress) {  

43.         }  

44.           

45.         public HelloWCFClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :   

46.                 base(binding, remoteAddress) {  

47.         }  

48.           

49.         public string HelloWCF() {  

50.             return base.Channel.HelloWCF();  

51.         }  

52.     }  

53. }  

这么一堆代码,都不是我们写的,初步看似乎有几个类/接口,IHelloWCF,这个应该是服务协定,估计可能是从服务端下载来的,HelloWCFClient,这个就是我们的客户端代理嘛,我们在前面用过,原来是在这里定义的,可是后面继承的ClientBase<>是干嘛用的,还重载了这么多的构造函数。还有一个IHelloWCFChannel接口,我们找遍解决方案也找不到什么地方用到他了啊,干嘛在这里定义出来呢?

先不去细想这些代码的具体意义,看到这里,我们在对VS2010由衷赞叹的同时,不由得心中升起一丝忧虑,如果没有了VS2010,没有了IDE,没有了"添加服务引用",我们该怎么办?

2. 我们自己写通信

虽然我们料想VS2010也不会消失,我们是可以一直享受它提供给我们的便利的。但是我们今天在这里研究,不妨把控制级别向下探一个层次。看看下面有什么。

 

通信到底是怎么发生的?简单说就是两个终结点一个通道,实际上客户端也是有一个终结点的,客户端会在这两个终结点之间建立一个通道,然后把对服务端服务的调用封装成消息沿通道送出,服务器端获得消息后在服务器端建立服务对象,然后执行操作,将返回值再封装成消息发给客户端。

过程大概是这样的,有些地方可能不太严谨,但是这个逻辑我们是可以理解的。如此看来,通讯的工作主要部分都在客户端这边,他要建立通道、发送消息,服务端基本上在等待请求。

 

客户端需要哪些东西才能完成这一系列的操作呢?元数据和一些服务的类。服务类由System.ServiceModel类库提供了,只差元数据。提到元数据我们不禁倒吸一口凉气,难道是那一堆XSD OOXX的东西么?我觉得,是也不是。就我们这个例子来说,元数据包括:服务协定、服务端终结点地址和绑定。对,就这么多。我们是不是一定要通过元数据交换下载去服务端获取元数据呢,当然不是,就这个例子来说,服务端是我们设计的,这三方面的元数据我们当然是了然于胸的。

 

所以,让服务引用见鬼去吧,我们自己来。

 

(1) 建立客户端。

这个过程我们很熟悉,建立一个控制台应用程序,不做任何其他事,仅有清清爽爽的program.cs

 

(2) 添加必要的引用

前面说过,客户端完成通信的发起需要一些服务类的支持,这些类都定义在System.ServiceModel中,因此我们要添加这个程序集的引用。(注意是添加引用,不是服务引用)

 

然后在Program.csusing这个命名空间

[csharp] view plaincopy

1.  using System.ServiceModel;  


(2)
编写服务协定

服务协定是元数据中最重要的部分(还可能有数据协定等),协定接口是服务器和客户端共同持有的,客户端依靠协定来创建通道,然后在通道上调用协定的方法,方法的实现,客户端是不知道的。客户端只知道方法签名和返回值(即接口)

我们把在服务端定义的服务协定原封不动的照搬过来,注意,只把接口搬过来,不要把实现也搬过来,那是服务端才能拥有的。

服务协定我们都很熟悉了,背着打出来吧。就写在Program类的后面

[csharp] view plaincopy

1.  [ServiceContract]  

2.  public interface IHelloWCF  

3.  {  

4.      [OperationContract]  

5.      string HelloWCF();  

6.  }  

OK,元数据的第一部分完成了,另外两部分我们在代码里面提供。

 

(3) 通道工厂登场

System.ServiceModel提供了一个名为ChannelFactory<>的类,他接受服务协定接口作为泛型参数,这样new出来的实例叫做服务协定XXX的通道工厂。顾名思义了,这个工厂专门生产通道,这个通道就是架设在服务器终结点和客户端终结点之间的通信通道了。由于这个通道是用服务协定来建立的,所以就可以在这个通道上调用这个服务协定的操作了。

这个通道工厂类的构造函数接受一些重载参数,使用这些参数向通道工厂提供服务端终结点的信息,包括地址和绑定,这正是元数据的其他两部分。我们先把这两样做好预备着。

 

地址,也可以称终结点地址,实际上就是个URI了,我们也有一个专用的服务类来表示他,叫做EndpointAddress,我们new一个它的实例:

[csharp] view plaincopy

1.  EndpointAddress address = new EndpointAddress("http://localhost/IISService/HelloWCFService.svc");  

只接受一个String参数,就是URI地址,这里我用了第三篇中建立的IIS服务的地址。

 

绑定,我们的服务端终结点要求的是wsHttpBinding,我们也可以用服务类来表示,叫做WSHttpBinding,我们new一个它的实例:

[csharp] view plaincopy

1.  WSHttpBinding binding = new WSHttpBinding();  

使用参数为空的构造函数就可以。

 

好的,元数据的其他两样也准备齐全,现在请通道工厂闪亮登场,我们new一个它的实例,用刚才建立的服务协定接口作为泛型参数,使用上面建立的地址和绑定对象作为构造函数的参数:

[csharp] view plaincopy

1.  ChannelFactory<IHelloWCF> factory = new ChannelFactory<IHelloWCF>(binding, address);  


有了工厂,接下来就要开始生产通道,通过执行通道工厂的CreateChannel方法来生产一个通道,由于工厂是用我们的协定接口创建的,所生产的通道也是实现这个接口的,我们可以直接用协定接口来声明其返回值。

[csharp] view plaincopy

1.  IHelloWCF channel = factory.CreateChannel();  


现在,通道已经打开,我们可以调用这个通道上的协定方法了。

[csharp] view plaincopy

1.  string result = channel.HelloWCF();  


然后我们把结果输出,就像之前做的客户端程序一样。

[csharp] view plaincopy

1.  Console.WriteLine(result);  

2.  Console.ReadLine();  

F5运行一下,看结果

 

Yahoo!,我们没有使用元数交换的功能,凭着手绘的元数据和代码就完成了客户端到服务器端的通信。没有服务引用、没有配置文件,我们依然做得到。虽然对整个过程还不能完全明了,但是对通信过程已经有些理解了。

Program.cs的全部代码

[csharp] view plaincopy

1.  using System;  

2.  using System.Collections.Generic;  

3.  using System.Linq;  

4.  using System.Text;  

5.  using System.ServiceModel;  

6.    

7.  namespace ConsoleClient  

8.  {  

9.      class Program  

10.     {  

11.         static void Main(string[] args)  

12.         {  

13.             EndpointAddress address = new EndpointAddress("http://localhost/IISService/HelloWCFService.svc");  

14.             WSHttpBinding binding = new WSHttpBinding();  

15.   

16.             ChannelFactory<IHelloWCF> factory = new ChannelFactory<IHelloWCF>(binding, address);  

17.   

18.             IHelloWCF channel = factory.CreateChannel();  

19.   

20.             string result = channel.HelloWCF();  

21.   

22.             Console.WriteLine(result);  

23.             Console.ReadLine();  

24.         }  

25.     }  

26.   

27.     [ServiceContract]  

28.     public interface IHelloWCF  

29.     {  

30.         [OperationContract]  

31.         string HelloWCF();  

32.     }  

33. }  


 

4. 再展开一点点

到这里已经很成功了,我们再稍微展开一些,还记得我们稍早前在服务引用生成的文件reference.cs看到的一个接口IHelloWCFChannel么?我们找遍解决方案也没发现哪个地方用到它?他是不是没用的东西呢,当然不会,我们现在来研究一下它。

 

我们上面手绘的程序可以打开通道并调用服务,然而我们回忆我们之前通过服务引用建立的客户端都是提供一个Close()方法来关闭服务连接的。使用我们这种方法按说也应该关闭通道才对,虽然客户端关闭通道就会被关闭了,但是在使用完通道后关闭之总是好的习惯。

 

可是,如何实现呢?我们发现通道无法提供关闭的方法,这是因为我们用IHelloWCF接口声明的通道对象,那这个对象自然只能提供接口所规定的方法了。而实际上通道对象本身是提供关闭方法,只是被我们显示的接口声明给屏蔽了,通道其实已经实现了另一个接口叫做IClientChannel,这个接口提供了打开和关闭通道的方法。如果我们要调用,只需要把通道对象强制转换成IClientChannel接口类型就可以了:

[csharp] view plaincopy

1.  ((IClientChannel)channel).Close();  


但是这么做不够自然优雅,强制转换总是让人莫名烦恼的东西。能不能保持IHelloWCF的对象类型并让他可以提供关闭方法呢?当然是可以的。我们再建立一个接口,让这个接口同时实现IHelloWCF服务协定接口和IClientChannel接口,并用这个新接口去new 通道工厂,这样生产出来的通道对象不就可以同时使用IHelloWCF中的服务操作和IClientChannel中的关闭通道的方法了么?

 

首先,做这个新接口,这个接口只是把服务协定接口和IClientChannel拼接,本身没有其他成员,是一个空接口。

[csharp] view plaincopy

1.  public interface IHelloWCFChannel : IHelloWCF, IClientChannel  

2.  {   

3.    

4.  }  


然后,修改一下前面的建立通道工厂的语句,用这个新接口名作为泛型参数来new通道工厂

[csharp] view plaincopy

1.  ChannelFactory<IHelloWCFChannel> factory = new ChannelFactory<IHelloWCFChannel>(binding, address);  

 

修改一下生产通道方法的对象声明类型,用新接口类型声明:

[csharp] view plaincopy

1.  IHelloWCFChannel channel = factory.CreateChannel();  


最后,我们在调用服务操作之后,就可以直接调用关闭通道的方法了:

[csharp] view plaincopy

1.  channel.Close();  


修改后的program.cs源代码:

[csharp] view plaincopy

1.  using System;  

2.  using System.Collections.Generic;  

3.  using System.Linq;  

4.  using System.Text;  

5.  using System.ServiceModel;  

6.    

7.  namespace ConsoleClient  

8.  {  

9.      class Program  

10.     {  

11.         static void Main(string[] args)  

12.         {  

13.             EndpointAddress address = new EndpointAddress("http://localhost/IISService/HelloWCFService.svc");  

14.             WSHttpBinding binding = new WSHttpBinding();  

15.   

16.             ChannelFactory<IHelloWCFChannel> factory = new ChannelFactory<IHelloWCFChannel>(binding, address);  

17.   

18.             IHelloWCFChannel channel = factory.CreateChannel();  

19.   

20.             string result = channel.HelloWCF();  

21.   

22.             channel.Close();  

23.   

24.             Console.WriteLine(result);  

25.             Console.ReadLine();  

26.         }  

27.     }  

28.   

29.     [ServiceContract]  

30.     public interface IHelloWCF  

31.     {  

32.         [OperationContract]  

33.         string HelloWCF();  

34.     }  

35.   

36.     public interface IHelloWCFChannel : IHelloWCF, IClientChannel  

37.     {   

38.       

39.     }  

40.       

41. }  


现在,我们明白了服务引用中那个看上去没用的接口是什么作用了吧。然而服务引用中的实现跟我们这种还是有区别的,它使用了一个叫做ClientBase<>的类来进行通道通信,我们后面会展开。

5. 总结

今天的研究稍微深入了一点点,没有完全理解也没有关系,心里有个概念就可以了。

我们通过手绘代码的方法实现了客户端和服务端的通信,没有借助元数据交换工具。这让我们对服务端和客户端通信有了更接近真相的一层认识。其实所谓的元数据交换就是让我们获得这样甚至更简单的编程模型,它背后做的事情跟我们今天做的是很类似的,想像一下,产品级的服务可能有许多的协定接口,许多的终结点,我们不可能也不应该耗费力气把他们在客户端中再手动提供一次,因此元数据交换是很有意义的,我们应该学会使用它,我们还要在后面的学习中不断掌握驾驭元数据交换工具的办法。

 

 

第五篇 再探通信--ClientBase

 

在上一篇中,我们抛开了服务引用和元数据交换,在客户端中手动添加了元数据代码,并利用通道工厂ChannelFactory<>类创建了通道,实现了和服务端的通信。然而,与服务端通信的编程模型不只一种,今天我们来学习利用另外一个服务类ClientBase<>来完成同样的工作,了解了这个类的使用方法,我们对服务引用中的关键部分就能够理解了。

 

ClientBase<>类也是一个泛型类,接受服务协定作为泛型参数,与ChannelFactory<>不同的是,这个类是一个基类,即抽象类,是不能实例化成对象直接使用的,我们需要自己写一个类来继承这个类,我们新写的类实例化出来就是客户端代理了,这个对象可以调用基类的一些受保护的方法来实现通信。ClientBase<>为我们封装的很好,我们只需要写个新类来继承他就可以了,通信的很多部分他都替我们做好了,比如我们不用进行去创建通道和打开通道的操作,直接调用协定方法就可以了。这也是服务引用和其他元数据生成工具(svcutil)使用这个类来构造客户端代理的原因。

 

我们一边动手一边学习

 

1. 建立客户端程序

我们依然不用服务引用,完全手写,这次还是建立一个控制台应用程序作为客户端。

2. 添加必要的引用

我们用到的ClientBase<>类在System.ServiceModel程序集中,因此,要添加这个程序集的引用,同时在program.cs中添加using语句

[csharp] view plaincopy

1.  using System.ServiceModel;  

这一步应该很熟了,如果有疑问,返回前几篇温习一下。

3. 编写服务协定

因为没有使用服务引用,客户端现在没有任何元数据的信息,我们要来手写。首先把服务协定写进去。这个再熟悉不过了(写在Program类后面)

[csharp] view plaincopy

1.  [ServiceContract]  

2.  public interface IHelloWCF  

3.  {  

4.      [OperationContract]  

5.      string HelloWCF();  

6.  }  

4. 编写客户端代理类

上面已经提到,我们要自己写一个新类来继承ClientBase<>基类,这样这个新类就是代理类了,同时,为了能够用代理类直接调用服务协定的方法,我们还要让代理类实现服务协定的接口,注意,继承要写在前面,实现接口要写在后面。我们把这个类起名为HelloWCFClient

[csharp] view plaincopy

1.  public class HelloWCFClient : ClientBase<IHelloWCF>, IHelloWCF  

2.  {   

3.    

4.  }  

提供给ClientBase的泛型参数应该是服务协定,这样他才能帮我们建立正确的通道,同时,代理类也实现服务协定接口。

 

ClientBase<>有许多的构造函数,接受不同种类的参数来创建代理类对象,其实这些参数都是元数据信息,刚才我们已经通过泛型参数传递给基类服务协定这个元数据了,现在基类还需要绑定和终结点地址这两个元数据才能正确创建连接,所以我们继承的新类应该把这个构造函数给覆载一下接受这两种元数据参数并传递给基类。

下面我们为新建立的代理类添加一个构造函数:

[csharp] view plaincopy

1.  public class HelloWCFClient : ClientBase<IHelloWCF>, IHelloWCF  

2.  {  

3.      public HelloWCFClient(System.ServiceModel.Channels.Binding binding, EndpointAddress remoteAddress)  

4.          : base(binding, remoteAddress)  

5.      {   

6.        

7.      }  

8.  }  

我们看到这个新建的构造函数什么也没做,只是接受了两个参数,一个是绑定,一个是终结点地址,然后直接调用基类(也就是ClientBase<>)的构造函数,把这个两个参数传递了上去。其实工作都是ClientBase<>做的,我们新建的类就是个传话的,要不然怎么叫代理呢,他什么活都不干。

 

既然我们实现了服务协定接口,当然要实现接口的方法了。下面我们把方法的实现写下来:

[csharp] view plaincopy

1.  public class HelloWCFClient : ClientBase<IHelloWCF>, IHelloWCF  

2.  {  

3.      public HelloWCFClient(System.ServiceModel.Channels.Binding binding, EndpointAddress remoteAddress)  

4.          : base(binding, remoteAddress)  

5.      {   

6.        

7.      }  

8.    

9.      public string HelloWCF()  

10.     {  

11.         return base.Channel.HelloWCF();  

12.     }  

13. }  

别忘了你的处境,凡人!我们这是在客户端啊,怎么可能有服务协定呢?这个可以有,但是这个实现不是我们在做,而是要和服务端通信让服务端做,这里可以看到代理鲜明的特点了,代理类虽然实现了服务协定的方法,但是在方法中,他调用了基类(就是ClientBase<>)上的通道,并通过通道调用了了协定方法。此时,ClientBase<>已经为我们建立好与服务端的通道了,而且是用服务协定建立的,我们当然可以在通道上调用服务协定的方法。所以调用代理类对象的HelloWCF()的过程是代理类委托基类在已经建立好的服务协定通道上调用协定方法,并从服务端获得返回值,然后再返回给代理类对象的调用者。狗腿啊狗腿。

5. 编写程序主体

代理类已经写完了,我们现在开始写程序的主体,让我们来到ProgramMain函数中。

还是有一些准备要做,还差两个元数据呢,对了,就是绑定和地址。和上一篇一样,我们先建立这个两个元数据的对象备用:

[csharp] view plaincopy

1.  WSHttpBinding binding = new WSHttpBinding();  

2.  EndpointAddress remoteAddress = new EndpointAddress("http://localhost/IISService/HelloWCFService.svc");  


接下来就要建立我们的代理类对象了,new一个出来吧:

[csharp] view plaincopy

1.  HelloWCFClient client = new HelloWCFClient(binding, remoteAddress);  

把我们刚刚建立的两个元数据对象作为参数传递进去。

 

接下来就可以调用服务协定方法了:

[csharp] view plaincopy

1.  string result = client.HelloWCF();  


别忘了关闭通道,ClientBase<>很贴心的为我们准备了这个方法,不用再做强制类型转换什么的了(见前一篇)

[csharp] view plaincopy

1.  client.Close();  


最后再添加输出语句来看结果:

[csharp] view plaincopy

1.  Console.WriteLine(result);  

2.  Console.ReadLine();  


F5
一下,熟悉的结果是不是出现了?

 

以下是Program.cs的全部代码:

[csharp] view plaincopy

1.  using System;  

2.  using System.Collections.Generic;  

3.  using System.Linq;  

4.  using System.Text;  

5.    

6.  using System.ServiceModel;  

7.    

8.  namespace ConsoleClient  

9.  {  

10.     class Program  

11.     {  

12.         static void Main(string[] args)  

13.         {  

14.             WSHttpBinding binding = new WSHttpBinding();  

15.             EndpointAddress remoteAddress = new EndpointAddress("http://localhost/IISService/HelloWCFService.svc");  

16.   

17.             HelloWCFClient client = new HelloWCFClient(binding, remoteAddress);  

18.   

19.             string result = client.HelloWCF();  

20.   

21.             client.Close();  

22.   

23.             Console.WriteLine(result);  

24.             Console.ReadLine();  

25.         }  

26.     }  

27.   

28.     [ServiceContract]  

29.     public interface IHelloWCF  

30.     {  

31.         [OperationContract]  

32.         string HelloWCF();  

33.     }  

34.   

35.     public class HelloWCFClient : ClientBase<IHelloWCF>, IHelloWCF  

36.     {  

37.         public HelloWCFClient(System.ServiceModel.Channels.Binding binding, EndpointAddress remoteAddress)  

38.             : base(binding, remoteAddress)  

39.         {   

40.           

41.         }  

42.   

43.         public string HelloWCF()  

44.         {  

45.             return base.Channel.HelloWCF();  

46.         }  

47.     }  

48. }  


 

6. 再展开一点点

这样看上去已经挺成功了,我们现在再打开服务引用的reference.cs代码,看看是不是大部分都看得懂了?监管有些地方他写的可能有些复杂,比如描述协定的属性参数啊,代理类更多的构造函数啊,但是核心的就是我们刚刚写的部分,那一大堆wsdl什么的其实都不是核心,不信的话你把他们都删掉,就留一个reference.cs看看还好不好用?那一堆东西也不是没用就是现在自己看起来还蛮复杂的,我们到后面一点点学习。

 

我们仔细看服务引用的reference.cs代码,有一样东西是我们有而他没有的,那就是对终结点地址和绑定的建立,而且在使用服务引用的时候我们也没有提供之两样东西,直接掉服务协定方法就行了(见第一篇),那么服务引用是从哪里找到这两个关键的元数据元素呢?

 

就在配置文件里,我们做的客户端还没有配置文件,我们可以把这两个元素都放在配置文件里,这样就可以避免硬编码了。

 

为我们的控制台应用程序添加一个应用程序配置文件。方法是右键点击项目->添加->新建项->应用程序配置文件。保持默认名称app.config

打开来看,里面没写什么实际的内容:

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.  </configuration>  


我们对配置服务应该不陌生,如果忘记了,翻回到第二篇去回顾一下。

首先还是要添加<System.ServiceModel>节,无论是服务端还是客户端,只要是WCF的服务配置都要在这个节里面:

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.        

5.    </system.serviceModel>  

6.  </configuration>  


在这里我们要配置的是客户端,所以我们不添加<Services>节了,而改成<Client>

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <client>  

5.          

6.      </client>  

7.    </system.serviceModel>  

8.  </configuration>  


然后我们在<Client>中添加一个终结点,这个是客户端的终结点,我们前面曾经提过,通信实际上发生在两个终结点间,客户端也有个终结点,然而请求总是从客户端首先发起,所以终结点地址应该填写为服务端终结点的地址让客户端来寻址,但是服务协定要客户端本地是有的,所以这个要写本地定义的那个协定的完全限定名:

[html] view plaincopy

1.  <?xml version="1.0" encoding="utf-8" ?>  

2.  <configuration>  

3.    <system.serviceModel>  

4.      <client>  

5.        <endpoint binding="wsHttpBinding" address="http://localhost/IISService/HelloWCFService.svc" contract="ConsoleClient.IHelloWCF"/>  

6.      </client>  

7.    </system.serviceModel>  

8.  </configuration>  

如果你忘记了我们在第三篇中配置的IIS服务的内容,可能稍有迷惑,这里的地址看上去是个服务地址而不是终结点地址,这是因为我们在IIS中把终结点地址设置为了空字符串,此时服务地址就是终结点地址了。注意看后面的contract,他的完全限定名的命名空间是客户端程序的命名空间ConsoleClient,这也表示这个类型是定义在本地的,不要搞错了。

 

保存,配置已经写完了,你如果看服务引用为我们生成的配置会看到一堆东西,实际上核心的就是这些。

 

既然我们已经在配置中声明了绑定和终结点地址,在代码中就不再需要了。

首先我们修改一下代理类,为他提供一个没有参数的构造函数,否则在new他的时候他会非管我们要两个参数。

[csharp] view plaincopy

1.  public HelloWCFClient()  

2.      : base()  

3.  {   

4.    

5.  }  

还是什么也不做,直接调基类的无参构造函数。

 

然后修改一下Main函数,去掉终结点对象和地址对象的声明,并用无参构造函数来new 代理类的实例:

[csharp] view plaincopy

1.  static void Main(string[] args)  

2.  {  

3.      HelloWCFClient client = new HelloWCFClient();  

4.    

5.      string result = client.HelloWCF();  

6.    

7.      client.Close();  

8.    

9.      Console.WriteLine(result);  

10.     Console.ReadLine();  

11. }  


F5
运行一下,结果如一吧!

到这里已经和服务引用的感觉基本一样了,我们已经写出了服务引用的核心部分。

以下是修改后的Program.cs完整代码:

[csharp] view plaincopy

1.  using System;  

2.  using System.Collections.Generic;  

3.  using System.Linq;  

4.  using System.Text;  

5.    

6.  using System.ServiceModel;  

7.    

8.  namespace ConsoleClient  

9.  {  

10.     class Program  

11.     {  

12.         static void Main(string[] args)  

13.         {  

14.             HelloWCFClient client = new HelloWCFClient();  

15.   

16.             string result = client.HelloWCF();  

17.   

18.             client.Close();  

19.   

20.             Console.WriteLine(result);  

21.             Console.ReadLine();  

22.         }  

23.     }  

24.   

25.     [ServiceContract]  

26.     public interface IHelloWCF  

27.     {  

28.         [OperationContract]  

29.         string HelloWCF();  

30.     }  

31.   

32.     public class HelloWCFClient : ClientBase<IHelloWCF>, IHelloWCF  

33.     {  

34.         public HelloWCFClient()  

35.             : base()  

36.         {   

37.           

38.         }  

39.   

40.         public HelloWCFClient(System.ServiceModel.Channels.Binding binding, EndpointAddress remoteAddress)  

41.             : base(binding, remoteAddress)  

42.         {   

43.           

44.         }  

45.   

46.         public string HelloWCF()  

47.         {  

48.             return base.Channel.HelloWCF();  

49.         }  

50.     }  

51. }  


7.
总结

通过本篇的学习,我们熟悉了另一种与服务端通信的办法,即通过ClientBase<>派生代理类的方法,这种方法实际上就是服务引用使用的方法,可以说我们已经在最简单级别上手写了一个服务引用的实现。

 

使用ChannelFactory<>ClientBase<>都可以实现与服务端的通信,这是类库支持的最高层次的通讯方法了,其实还有更底层的通道通信方法,我们现在就不再深入了。而选择这两种方法完全取决于开发人员,有人喜欢工厂,有人喜欢代理类。既然我们都已经掌握了,就随意选择吧。

 

 

 

   using System;

   using System.Runtime.CompilerServices;

   using System.ServiceModel;

   using System.ServiceModel.Description;

 

   public interfaceIClientBase

   {

       event ProxyCreatedHandlerProxyCreated;

 

       void Abort();

       void Close();

       void Open();

 

       System.ServiceModel.Description.ClientCredentialsClientCredentials { get; }

 

       ServiceEndpoint Endpoint { get; }

 

       IClientChannel InnerChannel { get; }

 

       CommunicationState State { get; }

   }

 

   using System;

   using System.Runtime.CompilerServices;

   using System.ServiceModel;

   using System.ServiceModel.Description;

 

   public abstractclass WCFAbstractReusableClientProxy<T>: IClientBase whereT: class

   {

       protected T cachedProxy;

       private readonlystring configName;

 

       public eventProxyCreatedHandler ProxyCreated;

 

       protectedWCFAbstractReusableClientProxy(stringconfigName)

       {

            this.configName= configName;

       }

 

       public voidAbort()

       {

            try

            {

                (this.Proxyas ClientBase<T>).Abort();

            }

            finally

            {

                this.CloseProxyBecauseOfException();

            }

       }

 

       private voidAssureProxy()

       {

            if(this.cachedProxy == null)

            {

                this.cachedProxy= WCFClientProxy<T>.GetInstance(this.configName);

                if(this.ProxyCreated != null)

                {

                    this.ProxyCreated(this);

                }

            }

       }

 

       public voidClose()

       {

            try

            {

                (this.Proxyas ClientBase<T>).Close();

            }

            finally

            {

                this.CloseProxyBecauseOfException();

            }

       }

 

       protected voidCloseProxyBecauseOfException()

       {

            if(this.cachedProxy != null)

            {

                ICommunicationObjectcachedProxy = this.cachedProxy as ICommunicationObject;

                try

                {

                    if(cachedProxy != null)

                    {

                        if (cachedProxy.State != CommunicationState.Faulted)

                        {

                           cachedProxy.Close();

                        }

                        else

                        {

                           cachedProxy.Abort();

                        }

                    }

                }

                catch(CommunicationException)

                {

                    cachedProxy.Abort();

                }

                catch(TimeoutException)

                {

                    cachedProxy.Abort();

                }

                catch

                {

                    cachedProxy.Abort();

                    throw;

                }

                finally

                {

                    this.cachedProxy= default(T);

                }

            }

       }

 

       public voidOpen()

       {

            try

            {

                (this.Proxyas ClientBase<T>).Open();

            }

            catch

            {

                this.CloseProxyBecauseOfException();

                throw;

            }

       }

 

       public System.ServiceModel.Description.ClientCredentials ClientCredentials

       {

            get

            {

                return(this.Proxy as ClientBase<T>).ClientCredentials;

            }

       }

 

       public ServiceEndpointEndpoint

       {

            get

            {

                return(this.Proxy as ClientBase<T>).Endpoint;

            }

       }

 

       public IClientChannelInnerChannel

       {

            get

            {

                return(this.Proxy as ClientBase<T>).InnerChannel;

            }

       }

 

       protected T Proxy

       {

            get

            {

                this.AssureProxy();

                returnthis.cachedProxy;

            }

       }

 

       public CommunicationStateState

       {

            get

            {

                return(this.Proxy as ClientBase<T>).State;

            }

       }

}

 

   using System;

   using System.ServiceModel;

 

   public abstractclass WCFAbstractReusableFaultWrapperClientProxy<T>: WCFAbstractReusableClientProxy<T> where T: class

   {

       protectedWCFAbstractReusableFaultWrapperClientProxy(stringconfigName) : base(configName)

       {

       }

 

       protected virtualvoid HandleFault<TFaultDetail>(FaultException<TFaultDetail> fault)

       {

            Exceptiondetail = fault.Detail as Exception;

            if(detail != null)

            {

                throwdetail;

            }

       }

    }

 

   using System;

   using System.Collections.Generic;

   using System.Reflection;

   using System.Reflection.Emit;

   using System.Threading;

 

   internal abstractclass AbstractClassBuilder<TInterface>where TInterface: class

   {

       private staticAssemblyBuilder assemblyBuilder;

       private staticAssemblyName assemblyName;

       private readonlyType baseClassType;

       private readonlystring classNameSuffix;

       private constMethodAttributes defaultMethodAttributes = (MethodAttributes.VtableLayoutMask | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.Public);

       private staticstring generatedAssemblyName;

       private readonlystring generatedClassName;

       private staticreadonly IDictionary<string, Type>generatedTypes;

       private staticreadonly objectgenerateLock;

       private readonlyType iType;

       private staticModuleBuilder moduleBuilder;

       private staticbool saveGeneratedAssembly;

 

       static AbstractClassBuilder()

       {

            AbstractClassBuilder<TInterface>.generatedAssemblyName= "Tencent.WCF.DynamicClientProxy.Generated.";

            AbstractClassBuilder<TInterface>.saveGeneratedAssembly= true;

            AbstractClassBuilder<TInterface>.generatedTypes= new Dictionary<string, Type>();

           AbstractClassBuilder<TInterface>.generateLock= new object();

       }

 

       protected AbstractClassBuilder(Type baseClassType, stringclassNameSuffix)

       {

            this.baseClassType= baseClassType;

            this.classNameSuffix= classNameSuffix;

            this.iType= typeof(TInterface);

            if(this.iType.Name.StartsWith("I") && char.IsUpper(this.iType.Name, 1))

            {

                this.generatedClassName= this.iType.Namespace + "." + this.iType.Name.Substring(1)+ classNameSuffix;

            }

            else

            {

                this.generatedClassName= typeof(TInterface).FullName +classNameSuffix;

            }

            AbstractClassBuilder<TInterface>.generatedAssemblyName= AbstractClassBuilder<TInterface>.generatedAssemblyName+ this.generatedClassName;

       }

 

       private staticvoid GenerateAssembly()

       {

            if(AbstractClassBuilder<TInterface>.assemblyBuilder== null)

            {

                AbstractClassBuilder<TInterface>.assemblyName= new AssemblyName();

                AbstractClassBuilder<TInterface>.assemblyName.Name= AbstractClassBuilder<TInterface>.generatedAssemblyName;

                AbstractClassBuilder<TInterface>.assemblyBuilder= Thread.GetDomain().DefineDynamicAssembly(AbstractClassBuilder<TInterface>.assemblyName,AbstractClassBuilder<TInterface>.saveGeneratedAssembly? AssemblyBuilderAccess.RunAndSave : AssemblyBuilderAccess.Run);

                if(AbstractClassBuilder<TInterface>.saveGeneratedAssembly)

                {

                    AbstractClassBuilder<TInterface>.moduleBuilder= AbstractClassBuilder<TInterface>.assemblyBuilder.DefineDynamicModule(AbstractClassBuilder<TInterface>.generatedAssemblyName,AbstractClassBuilder<TInterface>.generatedAssemblyName+ ".dll");

                }

                else

                {

                    AbstractClassBuilder<TInterface>.moduleBuilder= AbstractClassBuilder<TInterface>.assemblyBuilder.DefineDynamicModule(AbstractClassBuilder<TInterface>.generatedAssemblyName);

                }

            }

       }

 

       private voidGenerateConstructor(TypeBuilder builder)

       {

            Type[]parameterTypes = new Type[]{ typeof(string)};

            ILGeneratoriLGenerator = builder.DefineConstructor(MethodAttributes.RTSpecialName| MethodAttributes.Public, CallingConventions.Standard,parameterTypes).GetILGenerator();

            iLGenerator.Emit(OpCodes.Ldarg_0);

           iLGenerator.Emit(OpCodes.Ldarg_1);

            ConstructorInfocon = this.baseClassType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,null, parameterTypes, null);

            iLGenerator.Emit(OpCodes.Call, con);

            iLGenerator.Emit(OpCodes.Ret);

       }

 

       protected virtualvoid GenerateMethodImpl(TypeBuilder builder)

       {

            this.GenerateMethodImpl(builder,this.iType);

       }

 

       protected virtualvoid GenerateMethodImpl(TypeBuilder builder, TypecurrentType)

       {

            foreach(MethodInfo info incurrentType.GetMethods())

            {

                Type[]parameters = AbstractClassBuilder<TInterface>.GetParameters(info.GetParameters());

                MethodBuildermethodInfoBody = builder.DefineMethod(info.Name, MethodAttributes.VtableLayoutMask| MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.Public,info.ReturnType, parameters);

               methodInfoBody.CreateMethodBody(null, 0);

                ILGeneratoriLGenerator = methodInfoBody.GetILGenerator();

                this.GenerateMethodImpl(info,parameters, iLGenerator);

               builder.DefineMethodOverride(methodInfoBody, info);

            }

            foreach(Type type incurrentType.GetInterfaces())

            {

                this.GenerateMethodImpl(builder,type);

            }

       }

 

       protected abstractvoid GenerateMethodImpl(MethodInfo method, Type[]parameterTypes, ILGenerator iLGenerator);

        public Type GenerateType()

       {

            Typetype = this.TryGetType();

            if(type != null)

            {

                returntype;

            }

            lock(AbstractClassBuilder<TInterface>.generateLock)

            {

                type = this.TryGetType();

                if(type == null)

                {

                    AbstractClassBuilder<TInterface>.GenerateAssembly();

                    type = this.GenerateTypeImplementation();

                    lock(AbstractClassBuilder<TInterface>.generatedTypes)

                    {

                        AbstractClassBuilder<TInterface>.generatedTypes[this.generatedClassName] = type;

                    }

                }

                returntype;

            }

       }

 

       private TypeGenerateTypeImplementation()

       {

            TypeBuilderbuilder = AbstractClassBuilder<TInterface>.moduleBuilder.DefineType(this.generatedClassName, TypeAttributes.Public,this.baseClassType);

           builder.AddInterfaceImplementation(this.iType);

            this.GenerateConstructor(builder);

            this.GenerateMethodImpl(builder);

            returnbuilder.CreateType();

       }

 

       protected MethodInfoGetMethodFromBaseClass(string methodName)

       {

            returnthis.baseClassType.GetMethod(methodName, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);

       }

 

       private staticType[] GetParameters(ParameterInfo[] declareParams)

       {

            List<Type> list = newList<Type>();

            foreach(ParameterInfo info indeclareParams)

            {

                list.Add(info.ParameterType);

            }

            returnlist.ToArray();

       }

 

       protected boolIsVoidMethod(MethodInfo methodInfo)

       {

            return(methodInfo.ReturnType == typeof(void));

       }

 

       public staticvoid SaveGeneratedAssembly()

       {

            if(AbstractClassBuilder<TInterface>.saveGeneratedAssembly)

            {

                AbstractClassBuilder<TInterface>.assemblyBuilder.Save(AbstractClassBuilder<TInterface>.generatedAssemblyName+ ".dll");

                AbstractClassBuilder<TInterface>.assemblyBuilder= null;

            }

       }

 

       private TypeTryGetType()

       {

            lock(AbstractClassBuilder<TInterface>.generatedTypes)

            {

                Typetype = null;

                if(AbstractClassBuilder<TInterface>.generatedTypes.TryGetValue(this.generatedClassName, outtype))

                {

                    returntype;

                }

            }

            returnnull;

       }

 

       public stringGeneratedClassName

       {

            get

            {

                returnthis.generatedClassName;

            }

       }

    }

 

 

usingSystem;

   using System.Reflection;

   using System.Reflection.Emit;

 

   internal classWCFProxyClassBuilder<TInterface> : AbstractClassBuilder<TInterface> where TInterface: class

   {

       public WCFProxyClassBuilder() : base(typeof(ClientBase<TInterface>),"DynamicClientProxy")

       {

       }

 

       protected overridevoid GenerateMethodImpl(MethodInfo method, Type[]parameterTypes, ILGenerator iLGenerator)

       {

            iLGenerator.Emit(OpCodes.Ldarg_0);

            MethodInfomethodFromBaseClass = base.GetMethodFromBaseClass("get_Channel");

            iLGenerator.EmitCall(OpCodes.Call, methodFromBaseClass, null);

            method.GetParameters();

           for(int i = 0; i < parameterTypes.Length; i++)

            {

                iLGenerator.Emit(OpCodes.Ldarg, (int)(((short) i) + 1));

            }

            iLGenerator.Emit(OpCodes.Callvirt, method);

            iLGenerator.Emit(OpCodes.Ret);

       }

    }

 

 

   using System;

   using System.Reflection;

   using System.Reflection.Emit;

   using System.ServiceModel;

 

   internal classWCFReusableFaultWrapperProxyClassBuilder<TInterface>: WCFReusableProxyClassBuilder<TInterface>where TInterface: class

   {

       publicWCFReusableFaultWrapperProxyClassBuilder() : base(typeof(WCFAbstractReusableFaultWrapperClientProxy<TInterface>),"ReusableProxyFaultWrapper")

       {

       }

 

       protected overridevoid GenerateMethodImpl(MethodInfo method, Type[]parameterTypes, ILGenerator iLGenerator)

       {

            boolflag = !base.IsVoidMethod(method);

            if(flag)

            {

               iLGenerator.DeclareLocal(method.ReturnType);

            }

            iLGenerator.BeginExceptionBlock();

            iLGenerator.Emit(OpCodes.Ldarg_0);

            MethodInfomethodFromBaseClass = base.GetMethodFromBaseClass("get_Proxy");

            iLGenerator.EmitCall(OpCodes.Call, methodFromBaseClass, null);

            method.GetParameters();

            for(int i = 0; i < parameterTypes.Length; i++)

            {

                iLGenerator.Emit(OpCodes.Ldarg, (int)(((short) i) + 1));

            }

            iLGenerator.Emit(OpCodes.Callvirt, method);

            if(flag)

            {

                iLGenerator.Emit(OpCodes.Stloc_0);

            }

            object[]customAttributes = method.GetCustomAttributes(typeof(FaultContractAttribute), true);

            intarg = 0;

            foreach(FaultContractAttribute attribute in customAttributes)

            {

                if(typeof(Exception).IsAssignableFrom(attribute.DetailType))

                {

                    TypelocalType = typeof(FaultException<>).MakeGenericType(new Type[] { attribute.DetailType});

                    arg =iLGenerator.DeclareLocal(localType).LocalIndex;

                   iLGenerator.BeginCatchBlock(localType);

                    iLGenerator.Emit(OpCodes.Stloc_S, arg);

                    iLGenerator.Emit(OpCodes.Ldarg_0);

                    MethodInfometh = base.GetMethodFromBaseClass("CloseProxyBecauseOfException");

                    iLGenerator.Emit(OpCodes.Call, meth);

                    iLGenerator.Emit(OpCodes.Ldarg_0);

                    iLGenerator.Emit(OpCodes.Ldloc_S, arg);

                    MethodInfoinfo4 = base.GetMethodFromBaseClass("HandleFault").MakeGenericMethod(new Type[] {attribute.DetailType });

                    iLGenerator.Emit(OpCodes.Callvirt, info4);

                    iLGenerator.Emit(OpCodes.Rethrow);

                }

            }

            base.GenerateStandardCatch(iLGenerator);

            iLGenerator.EndExceptionBlock();

            if(flag)

            {

                iLGenerator.Emit(OpCodes.Ldloc_0);

            }

            iLGenerator.Emit(OpCodes.Ret);

       }

    }

 

 

  using System;

   using System.Reflection;

   using System.Reflection.Emit;

 

   internal classWCFReusableProxyClassBuilder<TInterface>: AbstractClassBuilder<TInterface> where TInterface: class

   {

       public WCFReusableProxyClassBuilder() : base(typeof(WCFAbstractReusableClientProxy<TInterface>),"ReusableProxy")

       {

       }

 

       public WCFReusableProxyClassBuilder(Type baseClassType, stringclassNameSuffix) : base(baseClassType,classNameSuffix)

       {

       }

 

       protected overridevoid GenerateMethodImpl(MethodInfo method, Type[]parameterTypes, ILGenerator iLGenerator)

       {

            boolflag = !base.IsVoidMethod(method);

            if(flag)

            {

               iLGenerator.DeclareLocal(method.ReturnType);

            }

            iLGenerator.BeginExceptionBlock();

            iLGenerator.Emit(OpCodes.Ldarg_0);

            MethodInfomethodFromBaseClass = base.GetMethodFromBaseClass("get_Proxy");

            iLGenerator.EmitCall(OpCodes.Call, methodFromBaseClass, null);

            method.GetParameters();

            for(int i = 0; i < parameterTypes.Length; i++)

            {

                iLGenerator.Emit(OpCodes.Ldarg, (int)(((short) i) + 1));

            }

            iLGenerator.Emit(OpCodes.Callvirt, method);

            if(flag)

            {

                iLGenerator.Emit(OpCodes.Stloc_0);

            }

            this.GenerateStandardCatch(iLGenerator);

            iLGenerator.EndExceptionBlock();

            if(flag)

            {

                iLGenerator.Emit(OpCodes.Ldloc_0);

            }

            iLGenerator.Emit(OpCodes.Ret);

       }

 

       protected voidGenerateStandardCatch(ILGeneratoriLGenerator)

       {

            iLGenerator.BeginCatchBlock(typeof(object));

            iLGenerator.Emit(OpCodes.Pop);

            iLGenerator.Emit(OpCodes.Ldarg_0);

            MethodInfomethodFromBaseClass = base.GetMethodFromBaseClass("CloseProxyBecauseOfException");

            iLGenerator.Emit(OpCodes.Call, methodFromBaseClass);

            iLGenerator.Emit(OpCodes.Rethrow);

       }

    }

 

 

   using System;

   using System.Collections.Generic;

   usingTencent.OA.Framework.ServiceModel.DynamicClientProxy.Internal;

 

   public classWCFClientProxy<TInterface> where TInterface: class

   {

       private staticIDictionary<Type,string> registeredContracts;

 

       static WCFClientProxy()

       {

            WCFClientProxy<TInterface>.registeredContracts= new Dictionary<Type, string>();

       }

 

       public staticTInterface GetInstance(string configName)

       {

            WCFProxyClassBuilder<TInterface>builder = new WCFProxyClassBuilder<TInterface>();

            return(TInterface) Activator.CreateInstance(builder.GenerateType(),new object[] {configName });

       }

 

       public staticTInterface GetRegisteredInstance()

       {

            lock(WCFClientProxy<TInterface>.registeredContracts)

            {

                stringstr = null;

                if(WCFClientProxy<TInterface>.registeredContracts.TryGetValue(typeof(TInterface), outstr))

                {

                    returnWCFClientProxy<TInterface>.GetReusableInstance(str);

                }

            }

            thrownew ApplicationException("Could not find registered contract:" + typeof(TInterface).FullName);

       }

 

       public staticTInterface GetReusableFaultUnwrappingInstance(stringconfigName)

       {

            WCFReusableFaultWrapperProxyClassBuilder<TInterface>builder = new WCFReusableFaultWrapperProxyClassBuilder<TInterface>();

            return(TInterface) Activator.CreateInstance(builder.GenerateType(),new object[] {configName });

       }

 

       public staticTInterface GetReusableInstance(stringconfigName)

       {

            WCFReusableProxyClassBuilder<TInterface>builder = new WCFReusableProxyClassBuilder<TInterface>();

            return(TInterface) Activator.CreateInstance(builder.GenerateType(),new object[] {configName });

       }

 

       public staticvoid RegisterEndpoint(stringconfigName)

       {

            lock(WCFClientProxy<TInterface>.registeredContracts)

            {

               WCFClientProxy<TInterface>.registeredContracts[typeof(TInterface)] = configName;

                newWCFProxyClassBuilder<TInterface>().GenerateType();

            }

       }

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值