# 跟我一起学WCF(4)——第一个WCF程序
## 一、引言
前面几篇文章分享了.NET 平台下其他几种分布式技术,然而前面几种分布式技术专注于某一特定的领域,并且具有不同编程接口,这使得开发人员需要掌握多个API的使用。基于这样的原因,微软在.NET 3.0时实现了WCF。WCF是.NET平台下各种分布式技术的集成,它将前面介绍的几种分布式技术完全整合在一起,并提供了一套统一的编程接口(API)。对于,开发人员来来说只需要掌握WCF一套的API,就可以实现之前分布式技术所实现的所有功能。
## 二、WCF详细介绍
WCF(Windows Communication Foundation)是微软为构建面向服务的应用程序(SOA)而提供的统一编程模型,借助该模型,使得在构建分布式系统中,无需再考虑如何去实现通信的相关的问题,使开发人员更加关注与系统业务逻辑本身的实现。而在WCF中,各个Application之间的通信是由Endpoint(终结点)来实现的。下面详细介绍下WCF几个重要的概念。
## 2.1 EndPoint 详细介绍
服务的提供者将服务通过一个或多个终结点发布给潜在的服务消费者,服务的消费者则通过与之匹配的终结点对服务进行消费。终结点由地址(Address)、绑定(Binding)和契约(Contract)三要素组成。如下图所示。由于它们的首字母分别是A、B、C。所以就有了:EndPoint = ABC。
![](https://box.kancloud.cn/2016-01-23_56a2eb47631e9.png)
这三个要素在WCF通信中起到的作用分别是:
* **地址(Address)**:地址标识了服务的位置,提供寻址的辅助信息和标识了服务的真实身份。Address解决了**Where the WCF service?**的问题。
* **绑定(Binding)**:绑定实现了通信的所有细节,包括网络传输,消息编码,以及其他为实现某种功能对消息进行的相应处理,例如安全、可靠传输和事务等功能。 WCF中具有一系列的系统已定义的绑定,如BasicHttpBinding、WsHttpBinding、NetTcpBinding等。Binding解决了How to Communicate with Service?的问题。
* **契约(Contract)**:契约是对服务操作的抽象,也是对消息交互模式以及消息结构的定义。WCF的契约大体可以分为两类,一类是对服务操作的描述;另一类是对数据的描述。服务契约(Service Contract)则属于对服务操作的描述,而后一类包括其余3中契约:数据契约(Data Contract)、消息契约(Message Contract)和错误契约(Fault Contract)。Contract解决了What function does the Service Provide?的问题。
## 2.2 WCF 基础概念
* 消息模式
消息时一个独立的数据单元,它可能由几个部分组成,包括消息正文和消息头。WCF支持多种消息模式,包括请求-恢复、单向和双工通信。不同传输协议支持不同的消息模式,WCF API和运行库还能保证安全而可靠地发送消息
* 通信协议和编码
WCF支持Http、TCP、Peer network(对等网)、IPC(基于命名管道的内部进程通信)和MSMQ协议。在进行消息传递之前,必须对给定的消息进行格式化的编码,WCF提供了3种编码选择:一种是文本编码,一种跨平台的编码;一种是消息传输优化机制(MOMO)编码,该编码用于高效地将非结构化的二进制数据发送到服务或从服务接收这些数据。一种是用于实现高效传输的二进制编码。
* 行为(Behavior):Behavior的主要作用是定制EndPoint在运行时的一些不要的行为。比如Service回调Client的TimeOut属性的设置。
* 宿主和宿主进程:服务必须承载于某个进程中,宿主进程是专为承载服务而设计的应用程序,这些宿主进程包括Internet信息服务(即IIS)、Windows激活服务(WAS)、Windows服务。由宿主控制服务的生命周期。
* 自承载服务:服务除了可以由现有的宿主进程承载外,还可以自承载,自承载服务是由开发人员创建的进程应用程序来承载服务。该应用程序控制服务的声明周期,设置服务的属性和打开服务和关闭服务等操作。
## 三、创建第一个WCF应用程序
前面介绍了WCF的详细内容,接下来,我们采用以下两种服务寄宿方法来创建WCF应用程序。
* 通过自我寄宿(Self-Hosting)的方式,即自承载服务。创建一个控制台应用程序来作为服务的宿主。
* 通过IIS寄宿方式将服务寄宿在IIS中。客户端通过另一个控制台程序来模拟客户端来对服务进行调用。
接下来,我们一步一步来使用两种方式来实现我们的WCF应用程序。首先介绍自我寄宿方式的实现步骤。
### 步骤一:创建服务契约和服务
既然是分布式应用程序,首先第一步肯定是创建供其他消费者消费的服务应用程序。而WCF采用基于契约的交互方式实现了服务的自治,以及客户端和服务端之间的松耦合。从功能角度上,服务契约抽象了服务提供的所有操作,所以,我们一般通过接口的形式定义服务契约。WCF通过在接口上应用[System.ServiceModel.Service.ServiceContractAttribute](http://msdn.microsoft.com/zh-cn/library/system.servicemodel.servicecontractattribute(v=vs.110).aspx)特性将一个接口定义成服务契约。在应用该特性的同时,还可以指定服务契约的名称和命名空间,在这个服务契约中,我们将契约名称和命名空间设置成HellworldService和http://www.Learninghard.com 。应用ServiceContractAttribute特性将接口定义成服务契约之后,接口中定义的方法也不能自动成为服务的操作方法。此时,我们需要在相应的方法上显式地应用[OperationContractAttribute](http://msdn.microsoft.com/zh-cn/library/System.ServiceModel.OperationContractAttribute(v=vs.110).aspx)特性。具体服务契约的实现代码如下所示:
服务契约成功创建之后,我们需要实现服务契约来创建具体的WCF服务。WCF服务的具体实现代码如下所示:
```
1 public class HelloWorldService : IHelloWorld
2 {
3 public string GetHelloWorld()
4 {
5 return "Hello World";
6 }
7 }
```
### 步骤二:创建服务宿主
前面介绍到,WCF服务必须寄存在某个进程中,该进程称为宿主应用程序。服务寄宿的目的就是开启一个进程来为WCF服务提供一个运行的环境。通过为服务添加一个或多个终结点,使之暴露给服务消费者使用。服务消费者再通过相应匹配的终结点对服务进行调用,下面通过创建一个控制台程序来实现WCF服务的自我寄宿方式,具体的实现代码如下所示:
```
1 using Contract;
2 using Services;
3 using System;
4 using System.ServiceModel;
5 using System.ServiceModel.Description;
6
7 namespace Hosting
8 {
9 class Program
10 {
11 static void Main(string[] args)
12 {
13 using (ServiceHost host = new ServiceHost(typeof(HelloWorldService)))
14 {
15 // 如果采用配置文件的方式,Region中代码就可以注释点
16 #region
17 host.AddServiceEndpoint(typeof(IHelloWorld), new WSHttpBinding(), "http://127.0.0.1:8888/HelloWorldService");
18 if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null)
19 {
20 ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
21 behavior.HttpGetEnabled = true;
22 behavior.HttpGetUrl = new Uri("http://127.0.0.1:8888/HelloWorldService/metadata");
23 host.Description.Behaviors.Add(behavior);
24 }
25 #endregion
26
27 host.Opened += delegate
28 {
29 Console.WriteLine("HelloWorldService 已经启动, 按任意键终止服务!");
30 };
31
32 host.Open();
33 Console.Read();
34 }
35 }
36 }
37 }
```
WCF服务寄宿通过[ServiceHost](http://msdn.microsoft.com/zh-cn/library/system.servicemodel.servicehost(v=vs.110).aspx)对象来完成的。在上面代码中,WCF实例是通过基于WCF服务的类型(typeof(HelloWorldService))来创建的,并通过AddServiceEndpoint添加了一个终结点,具体终结点的地址为http://127.0.0.1:8888/HelloWorldService,采用的绑定(Binding)类型为WSHttpBinding,并指定了服务契约的类型为IHelloWorld。
WCF是SOA的实现,而SOA的一个基本特征是松耦合,WCF应用中客户端和服务端的松耦合体现在客户端只需了解WCF服务基本的描述,而无需知道具体的实现细节就可以实现对服务的访问。WCF服务的描述通过元数据的形式进行发布的。而WCF元数据的发布是通过一个服务行为[ServiceMetadataBehavior](http://msdn.microsoft.com/zh-cn/library/system.servicemodel.description.servicemetadatabehavior(v=vs.110).aspx)来实现的。在上面代码中,我们为创建的ServiceHost对象添加了ServiceMetadataBehavior,并采用了基于Http-GET的元数据获取方式,元数据的发布地址指定为http://127.0.0.1:8888/HelloWorldService/metadata。在调用ServiceHost的Open方法对服务成功寄宿后,你可以通过该地址获取服务相关的元数据,就如Web 服务中通过输入WSDL地址来获得Web服务的描述一样。当我们成功运行ConsoleAppHosting宿主应用程序后,在浏览器中输入http://127.0.0.1:8888/HelloWorldService/metadata这个地址,你将得到如下所示的服务元数据。
![](https://box.kancloud.cn/2016-01-23_56a2eb47743c9.png)
在运行宿主应用程序时,如果你没有以管理员权限运行宿主应用程序的话,你将会得到Http无法注册的异常,具体异常信息如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb4787367.png)
此时,如果你是在VS中运行宿主程序,你就需要以管理员权限运行VS2012,如果直接在文件夹中运行宿主程序,此时需要右键宿主程序的exe文件,选择以管理员身份运行就不会出现如上图所示的异常。MSDN详细解释连接为:[WCF福门教程疑难解答](http://msdn.microsoft.com/zh-cn/library/bb924513.aspx#bkmk_q2)。
而在进行真正的WCF应用开发时,都不会直接通过硬编码的方式进行终结点的添加和服务行为的定义,而是通过配置文件的方式来进行的。你可以以下面配置文件的方式来代替上面的硬编码方式。
```
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="metadataBehavior">
<serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:8888/HelloWorldService/metadata"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="metadataBehavior" name ="Services.HelloWorldService">
<endpoint address="http://127.0.0.1:8888/HelloWorldService"
binding="wsHttpBinding"
contract="Contract.IHelloWorld"/>
</service>
</services>
</system.serviceModel>
</configuration>
```
### 步骤三:创建客户端
服务成功寄宿之后,服务端便开始了服务调用请求的监听工作。另外,服务寄宿将服务描述通过元数据的方式发布出来,相应的客户端就可以获取这些元数据来创建客户端程序来对服务进行调用。在Visual Studio下,当我们添加服务引用时,VS在内部会帮我们实现元数据的获取,并通过代码生成工具(SvcUtil.exe)将这些元数据自动生成用于服务调用的服务代理相关的代码和相应的配置。
在成功运行服务寄宿程序后,右键客户端项目,在弹出的菜单中选择“添加服务引用”,然后在弹出的添加服务引用窗口中输入服务元数据的地址:http://127.0.0.1:8888/HelloWorldService/metadata,并指定一个命名空间,点击确定按钮(具体效果如下图所示),VS将为你生成用于服务调用的代理类代码和配置信息。
![](https://box.kancloud.cn/2016-01-23_56a2eb4797346.png)
添加成功之后,我们可以通过创建服务代理类对象来对服务相应方法进行调用操作,客户端进行服务调用的具体实现代码如下所示:
```
1 using Client.HelloWorldServices;
2 using System;
3
4 namespace Client
5 {
6 class Program
7 {
8 static void Main(string[] args)
9 {
10 // HelloworldServiceClient就是VS为我们创建的服务代理类
11 using (HellworldServiceClient proxy = new HellworldServiceClient())
12 {
13 // 通过代理类来调用进行服务方法的访问
14 Console.WriteLine("服务返回的结果是: {0}", proxy.GetHelloWorld());
15 }
16
17 Console.Read();
18 }
19 }
20 }
```
运行客户端程序后,你将获得如下图所示的运行结果。
![](https://box.kancloud.cn/2016-01-23_56a2eb47b080f.png)
上面演示了通过自我寄宿的方式来寄宿服务,接下来我们来演示如何将WCF服务寄宿到IIS中。因为WCF服务和服务契约在上面方式中已实现,所以IIS寄宿方式的包含两个步骤:创建IIS宿主服务和创建客户端调用程序。下面分别介绍下这两个步骤。
### 步骤一:创建IIS宿主服务
在开始的解决方案中,创建一个Asp.net空Web应用程序,然后添加一个WCF服务文件。这里WCF服务文件与Web 服务中的.asmx文件类似。基于IIS的服务寄宿要求相应的WCF服务具有相应的.svc文件,.svc文件部署于IIS站点中,对WCF服务的调用体现在对.svc文件的访问上。
WCF服务文件的内容很简单,仅仅包含一个ServiceHost指令,该指令具有一个必须的Service属性和一些可选的属性。详细信息见MSDN:[@ServiceHost](http://msdn.microsoft.com/zh-cn/library/aa967286(v=vs.110).aspx)。所以对应的.svc文件内容如下所示:
具体Web.Config的配置内容如下所示:
```
<configuration>
<system.web>
<compilation debug="true"/>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="metadataBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="metadataBehavior" name ="Services.HelloWorldService">
<endpoint binding="wsHttpBinding"
contract="Contract.IHelloWorld"/>
</service>
</services>
</system.serviceModel>
</configuration>
```
从上面配置内容可以看出,这基本上和自我寄宿方式的配置文件一致,唯一不同的是在添加终结点中无需指定地址,因为.svc所在的地址就是服务的地址。
### 步骤二:创建客户端程序
此时,客户端仅仅需要修改终结点地址来对寄宿于IIS下的HellworldService进行访问,该地址为:http://localhost:15826/HelloWorldService.svc。此时可以http://localhost:15826/HelloWorldService.svc?wsdl得到相应的元数据。具体客户端代码的实现如下所示:
```
1 using Client2.HelloWorldService;
2 using System;
3
4 namespace Client2
5 {
6 class Program
7 {
8 static void Main(string[] args)
9 {
10 using (HellworldServiceClient proxy = new HellworldServiceClient())
11 {
12 Console.WriteLine("服务返回的结果是: {0}", proxy.GetHelloWorld());
13 }
14
15 Console.Read();
16 }
17 }
18 }
```
具体的配置文件内容如下所示:
```
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_HellworldService" />
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:15826/HelloWorldService.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_HellworldService"
contract="HelloWorldService.HellworldService" name="WSHttpBinding_HellworldService">
</endpoint>
</client>
</system.serviceModel>
</configuration>
```
运行客户端2,得到的运行结果与自我寄宿方式得到的结果一样。这里就不贴出结果图了。
## 四、总结
到这里,本篇文章的分享就结束。本文首先通过介绍WCF相关的基础概念,其中最重要的莫过于终结点和组成它的三个元素,之后分别介绍自我寄宿和IIS寄宿方式来创建WCF应用程序,在平常开发过程中,用到最多是通过IIS寄宿方式来对服务进行寄宿。在一篇文章中将分享关于WCF服务契约的内容。
本文所有源代码下载地址:[FirstWCFApp.zip](http://files.cnblogs.com/zhili/FirstWCFApp.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结