虽然已经在多个项目中成功应用过WCF,但是感觉自己对WCF的知识只知道一些皮毛而已。上次学习WCF也是为了项目需要,囫囵吞枣。这不是我学习方法的态度。所以时至今日,又重新拾卷,再仔细的将WCF一些细节知识,边边角角自己回顾一番。

Host

三种Host的方式:IIS Host、WAS Host、Self-Host。

IIS Host

IIS这种非常简单,但只支持HTTP协议。不过,你可以借助IIS来管理服务的生命周期。在IIS上发布WCF Service是极其简单的,只需要写一个后缀名为svc的文件就ok了:

<%@ ServiceHost Language=”C#” Debug=”false” CodeBehind=”~/App_Code/MyService.cs” Service=”MyService” %>

还记得Web Service的asmx文件么?是不是如出一辙!

Self-Host

顾名思义,就是自托管了。开发人员完全控制,你可以将服务驻留在Console Application、WinForm、Windows Service。只需要保证服务先于客户端启动就Ok了。使用这种托管方式,开发人员可以完全的控制,可以使用任何协议,灵活性最大。

WAS Host

WAS的全称是Windows Activation Service,是个系统服务,跟随Vista发布的,是IIS 7的一部分,但是也可以单独安装和配置。要使用WAS,和IIS Host一样,也需要提供一个svc文件。但是,WAS不仅仅可以使用HTTP,可以使用WCF可以使用的任何协议。

WAS提供了很多Self-Host没有的优点,比如应用程序池、回收、身份管理等。

Endpoint

WCF中最重要的概念莫过于Endpoint了,Endpoint就是服务的接口。一个Endpoint包括三个要素:Address、Binding、Contract。

这三个方面实际上表达的是Where?How?What?的意思。

Address就是我到哪里(Where)寻找这个服务?

Binding就是我如何(How)与这个服务交互?

Contract就是这个服务是什么(What)?

每个Endpoint必须具有三个要素,缺一不可。

ServiceHost

Host架构

Service就驻留在ServiceHost实例中,每个ServiceHost实例只能托管一个服务。每个ServiceHost可以有多个Endpoint。一个进程里面可以有多个ServiceHost实例,同个宿主进程里不同的ServiceHost实例可以享用相同的BaseAddress。

使用Visual Studio自动生成服务端

Visual Studio的项目模板里已经为我们准备了WCF Service Application,使用Visual Studio创建的WCF Service Application项目默认是使用IIS托管的(WAS的托管方式与IIS的类似):

生成后的工程(经过修改):

如果采用Selft-Host该怎么办呢?Visual Studio里还有一个WCF Service Library的项目模板,我们可以使用这个模板为Self-Host生成很多代码:

添加后生成的工程(经修改):

但不管是WCF Service Application还是WCF Service Library,我觉得这种自动生成的方式都不太好。从上面几个图我们可以看出,这两种项目模板都将服务契约与服务的实现放在同一个项目中,最后编译出来服务契约与服务实现也在同一个程序集中,既然如此那为何又要将契约和服务分开?不是多次一举么?对于Best Practice来讲,我们应该永远都为每个服务创建一个接口,而将[ServiceContract]特性加在这些接口上,然后在另一个项目里编写服务的实现类,引用服务契约的项目,实现这些接口(契约)。所以无论从学习还是Best Practice来讲,我们都应该具有手动从头到尾编写服务契约、实现服务、服务托管的代码的能力:

代码示例:

   1: //订单项
   2: [DataContract]
   3: public class OrderItem
   4: {
   5:     [DataMember]
   6:     public int Id{get;set;}
   7:     [DataMember]
   8:     public int ProductId{get;set;}
   9: }
  10: //订单
  11: [DataContract]
  12: public class Order
  13: {
  14:     [DataMember]
  15:     public int OrderId{get;set;}
  16:  
  17:     [DataMember]
  18:     public IList<OrderItem> OrderItems{get;set;}
  19: }

   1: //订单服务契约
   2: [ServiceContract]
   3: public interface IOrderService
   4: {
   5:     [OperationContract]
   6:     bool CreateOrder(Order order);
   7:  
   8:     [OperationContract]
   9:     bool DeleteOrder(int orderId);
  10:  
  11:     [OperationContract]
  12:     bool CancelOrder(int orderId);
  13: }

   1: //订单服务
   2: public class OrderService : IOrderService
   3: {
   4:     public void CreateOrder(Order order)
   5:     {
   6:         return true;
   7:     }
   8:     public bool DeleteOrder(int orderId)
   9:     {
  10:         return false;
  11:     }
  12:     public bool CancelOrder(int orderId)
  13:     {
  14:         return false;
  15:     }
  16: }

服务托管(Self-Host)

   1: static void Main()
   2: {
   3:     //binding,how?
   4:     Binding tcpBinding = new NetTcpBinding();
   5:     ServiceHost host = new ServiceHost(typeof(OrderService),new Uri("net.tcp://localhost:8000/"));
   6:     host.AddServiceEndpoint(typeof(IOrderService),tcpBinding,"OrderService");
   7:     host.Open();
   8:     Console.ReadLine();
   9:     host.Close();
  10: }

使用配置的方式:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <services>
   5:             <!--注意,这里的name要与服务的类名是一致的-->
   6:             <service name="OrderService">
   7:                 <host>
   8:                     <baseAddresses>
   9:                         <add baseAddress="net.tcp://localhost:8000/" />
  10:                     </baseAddresses>
  11:                 </host>
  12:                 <endpoint contract="IOrderService" binding="netTcpBinding" address="OrderService" />
  13:             </service>
  14:     </system.serviceModel>
  15: </configuration>

MetadataExchange(元数据交换)

启用元数据交换有居多的好处,客户端可以使用SvcUtil工具自动的从服务元数据中生成客户端代理已经数据契约。

WCF启用元数据交换有两种方式:

1、使用HttpGet

2、使用一个专门的Endpoint用来进行元数据交换

HttpGet

先来看实例,HttpGet:

   1: Uri httpBaseAddress = new Uri("http://locahost/");
   2: Uri tcpBaseAddress = new Uri("net.tcp://localhost:8000/");
   3: ServiceHost host = new ServiceHost(typeof(OrderService),httpBaseAddress,tcpBaseAddress);
   4: ServiceMetadataBehavior metadataBehavior;
   5: metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
   6: if(metadataBehavior == null)
   7: {
   8:     metadataBehavior = new ServiceMetadataBehavior();
   9:     //启用http-get方式
  10:     metadataBehavior.HttpGetEnabled = true;
  11:     host.Description.Behaviors.Add(metadataBehavior);
  12: }
  13: host.AddServiceEndpoint(typeof(IOrderService),new NetTcpBinding(),"OrderService");
  14: host.Open();
  15: Console.ReadLine();
  16: host.Close();

既然是Http-Get的方式,顾名思义,肯定是使用http协议,所以必须有一个http的baseaddress。上面是使用代码的方式启用http-get,看看如何用配置打开http-get:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <services>
   5:             <service name="OrderService" behaviorConfiguration="EnableHttpGetBehavior">
   6:                 <host>
   7:                     <baseAddresses>
   8:                         <add baseAddress="net.tcp://localhost:8000/" />
   9:                         <add baseAddress="http://localhost/" />
  10:                     </baseAddresses>
  11:                 </host>
  12:                 <endpoint contract="IOrderService" binding="netTcpBinding" address="OrderService" />
  13:             </service>
  14:             <behaviors>
  15:             <serviceBehaviors>
  16:               <behavior name="EnableHttpGetBehavior">
  17:                 <serviceMetadata httpGetEnabled="true" />
  18:                </behavior>
  19:             </serviceBehaviors>
  20:            </behaviors>
  21:     </system.serviceModel>
  22: </configuration>

添加专用Endpoint

编程添加

   1: Uri httpBaseAddress = new Uri("http://locahost/");
   2: Uri tcpBaseAddress = new Uri("net.tcp://localhost:8000/");
   3: ServiceHost host = new ServiceHost(typeof(OrderService),httpBaseAddress,tcpBaseAddress);
   4: //虽然不使用http-get的方式,ServiceMetadataBehavior还是要加的,而且是先加这个Behavior,然后再添加
   5: //专门用于元数据交换的Endpoint,否则会抛出异常
   6: ServiceMetadataBehavior metadataBehavior;
   7: metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
   8: if(metadataBehavior == null)
   9: {
  10:     metadataBehavior = new ServiceMetadataBehavior();
  11:     //使用这种方式,HttpGetEnabled是否为true都无所谓,HttpGetEnabled默认值是false
  12:     host.Description.Behaviors.Add(metadataBehavior);
  13: }
  14: host.AddServiceEndpoint(typeof(IOrderService),new NetTcpBinding(),"OrderService");
  15: //注意这里
  16: BindingElement bindingElement = new TcpTransportBindingElement();
  17: Binding customBinding = new CustomBinding(bindingElement);
  18: //添加一个专门用于元数据交换的Endpoint
  19: host.AddServiceEndpoint(typeof(IMetadataExchange),customBinding,"MEX");
  20: host.Open();
  21: Console.ReadLine();
  22: host.Close();

配置的方式添加

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <services>
   5:             <service name="OrderService" behaviorConfiguration="EnableHttpGetBehavior">
   6:                 <host>
   7:                     <baseAddresses>
   8:                         <add baseAddress="net.tcp://localhost:8000/" />
   9:                         <add baseAddress="http://localhost/" />
  10:                         </baseAddresses>
  11:                 </host>
  12:                 <endpoint contract="IOrderService" binding="netTcpBinding" address="OrderService" />
  13:                 <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="MEX" />
  14:             </service>
  15:             <behaviors>
  16:             <serviceBehaviors>
  17:               <behavior name="EnableHttpGetBehavior">
  18:                 <serviceMetadata />
  19:                </behavior>
  20:             </serviceBehaviors>
  21:            </behaviors>
  22:     </system.serviceModel>
  23: </configuration>

从上面的代码我们基本上就知道了如何启用元数据交换了。使用http-get的方式简单,只需要一条设置就ok了,但是只能使用http或https,使用专用的endpoint则可以使用所有的协议。很多内容写在注释里了,仔细阅读。

服务已经Host了,Endpoint也已添加了,现在就要看看如何编写Client端。

Client端编程

使用Visual Studio自动生成客户端

客户端是通过一个代理与服务端交互的,我们可以使用Visual Studio的Add Service Reference的功能添加远程服务,这样Visual Studio就会自动的帮我们生成客户端服务的代理。要使用Add Service Reference首先你得服务端必须启用了元数据交换。如果你是使用Visual Studio在同一个解决方案下创建的WCF Service Application或WCF Service Library,在Add Service Reference窗口中,还可以使用Discovery按钮自动的找到本解决方案下的所有服务:

添加服务引用以后:

在这个窗口中,点击“Advanced”还可以对生成的代理做一些设置,在Namespace里可以设置生成服务的命名空间。这样做非常简便、高效,而且,当服务端修改了什么,我们只需要在客户端项目的Service Reference文件件下选择对应的服务,然后点击右键中的“Update Service Reference”,客户端代理就可以自动更新成新版本了。具有这样的优势很有诱惑力,但我们对Visual Studio生成了什么还是一无所知,不能自己完全的控制。所以你可以决定自己编写客户端代理。

Client Proxy

   1: public class OrderServiceProxy : ClientBase<IOrderService>,IOrderService
   2: {
   3:     public OrderServiceProxy(){}
   4:     public OrderServiceProxy(string endpointName):base(endpointName){}
   5:     public OrderServiceProxy(Binding binding,EndpointAddress remoteAddress):base(binding,remoteAddress){}
   6:  
   7:     public bool DeleteOrder(int orderId)
   8:     {
   9:         return this.Channel.DeleteOrder(orderId);
  10:     }
  11:     public bool CancelOrder(int orderId)
  12:     {
  13:         return this.Channel.CancelOrder(orderId);
  14:     }
  15:     public bool CreateOrder(Order order)
  16:     {
  17:         return this.Channel.CreateOrder(order);
  18:     }
  19: }

这样一个本地的代理就创建好了,如何去使用这个代理呢?也有两种方式,第一种,我们可以编写代码创建一个本地代理,第二种,我们可以将配置保存在配置文件中。

使用代理

编程方式

   1: static void Main()
   2: {
   3:     Binding tcpBinding = new NetTcpBinding();
   4:     EndpointAddress address = new EndpointAddress("net.tcp://localhost:8000/OrderService");
   5:     OrderServiceProxy proxy = new OrderServiceProxy(tcpBinding,address);
   6:     
   7:     //打开到远程服务的连接
   8:     proxy.Open();
   9:     //调用远程服务,是不是像调用本地方法一样
  10:     proxy.CancelOrder(5);
  11:     //关闭
  12:     proxy.Close();
  13:     
  14: }

编程的方式的优点是能得到编译器的检查,但是如果想修改一下,比如日后改为http协议访问就得修改源代码。我们还可以使用配置的方式,在上面代理的代码中,我们发现代理还有一个构造器接受一个“endpointName”的参数,这个参数就是指配置文件中Endpoint的名称:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: configuration>
   3:    <system.serviceModel>
   4:          <client>
   5:            <endpoint address="net.tcp://localhost:8000/OrderService"
   6:                binding="netTcpBinding"
   7:                contract="IOrderService" name="OrderService">
   8:            </endpoint>
   9:        </client>
  10:    </system.serviceModel>
  11: </configuration>

然后可以这样使用代理:

   1: OrderServiceProxy proxy = new OrderServiceProxy("OrderService");
   2: proxy.Open();
   3: proxy.CancelOrder(5);
   4: proxy.Close();

这种方式虽然不能获得编译时的检查,配置文件如果写错了,只有等到运行时才可以发现,但是将配置保存在程序员可以带来非常大的灵活性。

使用ChannelFactory创建代理

实际上,还有一种方式:

   1: Binding binding = new NetTcpBinding();
   2: EndpointAddress address = new EndpointAddress("net.tcp://localhost:8000/OrderService")
   3: IOrderService proxy = ChannelFactory<IOrderService>.CreateChannel(binding,address );
   4: //代理使用后一定要关闭,看看下面的方式
   5: using(proxy as IDisposable)
   6: {
   7:   proxy.Login();
   8: }
   9: //或者这种方式也可以
  10: ICommunicationObject channel = proxy as ICommunicationObject;
  11: channel.Close();

元数据除了协助Visual Studio发现服务,自动生成代码(客户端代理,数据契约)还有什么用?我们可以使用编程的方式访问元数据么?答案是肯定的,下一节我们看看如果使用编程方式访问元数据。

元数据导入

我们可以编写代码,判断一个服务是否提供我们期望的Contract,这将怎么实现呢?比如我们要做一个小程序,遍历出某个指定address里暴露的所有Contract。WCF为我们提供了MetadataExchangeClient类。

   1: //MetadataExchangeClient的构造器有几个重载
   2: MetadataExchangeClient mexClient = new MetadataExchangeClient(new Uri("net.tcp://localhost:8000/MEX"),
   3:                         MetadataExchangeClientMode.MetadataExchange);
   4: //GetMetadata方法也有好几个重载
   5: MetadataSet metadataSet = mexClient.GetMetadata();
   6: WsdlImporter importer = new WsdlImporter(metadataSet);
   7: ServiceEndpointCollection endpoints = importer.ImportAllEndpoints();
   8: foreach(ServiceEndpoint endpoint in endpoints)
   9: {
  10:     ContractDescription contract = endpoint.Contract;
  11:     Console.WriteLine("Namespace:{0},Name:{1}",contract.Namespace,contract.Name);
  12: }

WCF架构

这个架构图对于日后的WCF扩展非常重要。

本文为学习WCF笔记,文章大部分内容“抄袭”自《Programming WCF Services》。

【应用篇】WCF学习笔记(一):Host、Client、MetadataExchage相关推荐

  1. 数据库LINQ TO SQL在Silverlight中的应用(WCF)------学习笔记(一)

    数据库LINQ TO SQL在Silverlight中的应用(WCF)------学习笔记(一) 步骤: 1. 创建SILVERLIGHT应用程序 2. 创建LINQ TO SQL [注意序列化的问题 ...

  2. WCF学习笔记(2)——独立WCF服务

    本文将建立一个silverlight与wcf通讯的简单实例,wcf服务将被独立出来,而不再寄放在Web中.以下是详细步骤: 新建Silverlight应用程序,名称WCFtest,在解决方案上右键添加 ...

  3. 实用篇 | MySQL 学习笔记

    实用篇 | MySQL 学习笔记 MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS (Relational Database Management S ...

  4. Go语学习笔记 - grpc server/client protobuf | 从零开始Go语言

    目录 创建Proto文件 生成proto文件对应的go文件 创建服务结构体 创建客户端测试 小结 学习笔记,写到哪是哪. 上一篇是写的redis操作来着,最近主要研究了一下grpc. 在玩grpc的过 ...

  5. 【MySQL进阶篇】学习笔记

    文章目录 MySQL进阶学习 前言 1.存储引擎 1.1 MySQL体系结构概览 1.2 存储引擎介绍 1.3 常见存储引擎的特点 1.3.1 InnoDB的特点 1.3.2 MyISAM 1.3.3 ...

  6. WCF学习笔记(基于REST规则方式)

    一.WCF的定义 WCF是.NET 3.0后开始引入的新技术,意为基于windows平台的通讯服务. 首先在学习WCF之前,我们也知道他其实是加强版的一个面向服务(SOA)的框架技术. 如果熟悉Web ...

  7. WCF学习笔记(一):WCF Service Application和WCF Service Library的区别

    近来在学习WCF,遇到了不少问题,有的让我焦头烂额,不过解决问题的过程就是学习的过程,收获也不少. 昨天有个问题开始困扰我--WCF Service Application和WCF Service L ...

  8. SQL必知必会-进阶篇[SQL学习笔记]

    本篇博客是对于陈旸老师极客专栏"SQL 必知必会"进阶篇的笔记总结.需要学习资料可私信. 文章目录 第20课 数据库优化 第21课 数据库的设计范式都有哪些? 数据表的键都有哪些? ...

  9. WCF学习笔记之可靠会话

    可靠会话传输需要解决两个问题:重复消息和无序交付:制定WS-RM的一个主要目的就是实现一种模块化 的可靠消息传输机制:WS-RM两个版本(WS-RM1.0和WS-RM1.1): WCF中整个可靠会话的 ...

最新文章

  1. java lList Map Set总结
  2. 记一次找因 Redis 使用不当导致应用卡死 bug 的过程
  3. linux离线安装ftp_安装Kali Linux之后要做的前10件事
  4. 如何在腾讯云上安装Cloud Foundry
  5. 基于docker微服务架构_使用基于微服务的流架构更好地进行大规模的复杂事件处理(第1部分)...
  6. 总线驱动:Bus driver - USB driver for example
  7. JSch连接不上Linux服务器,windows 下 java程序jsch连接远程linux服务器执行shell命令
  8. 2015第28周六SVN和Git
  9. JAVA基础知识|进程与线程
  10. 注:以前我的博客,因为丢了用户名和口令,无法使用,声明作废;现转于此。...
  11. 初入PLC编程(基本理论知识)
  12. EXCEL 基础函数大全
  13. 华为设备静态路由配置命令
  14. CCF NOI 2022获奖名单
  15. 【SparkSQL笔记】SparkSQL的入门实践教程(一)
  16. 如何做到长期稳定的禅修?
  17. android-ProGuard混淆
  18. 电磁波极化原理及仿真
  19. simple rpc framework
  20. 江南大学计算机技术复试科目,江南大学计算机专硕考哪些科目

热门文章

  1. python日志,支持彩色打印和文件大小切片写入和写入mongodb
  2. GRUB中硬盘和分区编号,UUID
  3. 大数据基础知识问答----hadoop篇
  4. 让你的Android程序更省电
  5. 无法使用此数据源,因为没有正确配置performancepoint services
  6. 【项目总结】达能益力--官网
  7. csdn的blog后台程序的导航菜单的实现
  8. Linux下基于eclipse的arm开发环境的建立
  9. 求周期字符串的最小子串
  10. ubuntu18.04新安装时Unable to locate package问题