1、我们通过实现一个简单的示例来对 WCF 有个直观而浅显的认识,希望对初次涉及 WCF 的朋友有所帮助。可以简单地认为 WCF 程序分为 4 部分:契约、服务、宿主、客户端。我们通过一个例子来逐步完成各部分,示例程序中,客户端可以获取一个信息列表,列表中每一项包括 ID、值、读值时刻、状态、状态变动时刻。这里我用的是 VS2010。 首先,创建一个空白解决方案 WCFDemo。我们将在其中添加 n 个项目,分别实现契约、服务、宿主、客户端。如果用 VS2010 新建“WCF 服务库”或者“WCF 服务应用程序”,它会默认把契约和服务放在一个项目中,我们这个示例把契约和服务分别放在 2 个类库项目
2、中。 第一步:契约1、添加一个类库 WCFDemo.Contracts。2、在类库中添加 2 个文件 DataContracts.cs 和 ServiceContracts.cs,分别放置数据契约和服务契约。3、添加引用 System.Runtime.Serialization 和 System.ServiceModel。4、编写代码如下:DataContracts.csusing System;using System.Runtime.Serialization;namespace WCFDemo.ContractsDataContractpublic class DemoDataDataM
3、emberpublic int ID get; set; DataMemberpublic double Value get; set; DataMemberpublic DateTime ValueTime get; set; DataMemberpublic DeviceState State get; set; DataMemberpublic DateTime StateTime get; set; public enum DeviceStateUnknown,Working,Broken(题外话:DemoData 类中各个属性的写法有些偷懒,其实个人不建议这样。这里是为了代码简单)S
4、erviceContracts.csusing System.Collections.Generic;using System.ServiceModel;namespace WCFDemo.ContractsServiceContractpublic interface IDemoServiceOperationContractList GetMonitorData();第二步:服务1、添加一个类库 WCFDemo.Services。2、在类库中加入一个文件 Services.cs 用来放置实现服务的类。3、添加引用 WCFDemo.Contracts。4、编写代码如下: using Syst
5、em;using System.Collections.Generic;using WCFDemo.Contracts;namespace WCFDemo.Servicespublic class DemoService : IDemoServiceRandom random = new Random();public List GetMonitorData()List r = new List();r.Add(new DemoData() ID = 1, Value = random.Next(100), ValueTime = DateTime.Now, State = DeviceSta
6、te.Unknown, StateTime = DateTime.Now );r.Add(new DemoData() ID = 2, Value = random.Next(100), ValueTime = DateTime.Now, State = DeviceState.Working, StateTime = DateTime.Now );r.Add(new DemoData() ID = 3, Value = random.Next(100), ValueTime = DateTime.Now, State = DeviceState.Broken, StateTime = Dat
7、eTime.Now );return r;(题外话:第一步时说过 DemoData 的偷懒写法。如果 DemoData 中针对每个属性定义私有字段,并提供带参数的构造函数,构造函数中对字段赋值而不是对属性赋值,那么每个 DemoData 实例化时比这里的示例代码效率高。)到这里,服务和契约已经完成。 剩下的就是宿主如何对外提供服务和客户端如何享受服务了,我们先使用最最简单的方式来实现。 我们先以最简单的方式来实现宿主和客户端:直接引用契约和服务项目、采用硬编码的方式。第三步:宿主1、添加一个 Windows 窗体应用程序 WCFDemo.Host.WithoutConfig。2、添加引用 Sy
8、stem.ServiceModel。3、引用之前的两个项目。4、在窗体放置两个 Button 和一个 Label,并编写代码如下:using System;using System.Windows.Forms;using System.ServiceModel;using WCFDemo.Services;using WCFDemo.Contracts;namespace WCFDemo.Host.WithoutConfigpublic partial class HostForm : Formpublic HostForm()InitializeComponent();ServiceHost
9、 host;private void button1_Click(object sender, EventArgs e)host = new ServiceHost(typeof(DemoService);host.AddServiceEndpoint(typeof(IDemoService), new BasicHttpBinding(), “http:/localhost:5678/DemoService“);host.Opened += delegate label1.Text = “服务启动“; ;host.Open();private void button2_Click(objec
10、t sender, EventArgs e)if (host != null ;host.Close();第四步:客户端1、添加一个 Windows 窗体应用程序 WCFDemo.Client.WithoutConfig。2、添加引用 System.ServiceModel。3、引用之前契约项目。4、在窗体放置一个 Button 和一个 DataGridView,并编写代码如下:using System;using System.Windows.Forms;using System.ServiceModel;using WCFDemo.Contracts;namespace WCFDemo.C
11、lient.WithoutConfigpublic partial class ClientForm : Formpublic ClientForm()InitializeComponent();private void button1_Click(object sender, EventArgs e)using (ChannelFactory f = new ChannelFactory(new BasicHttpBinding(), “http:/localhost:5678/DemoService“)dataGridView1.DataSource = f.CreateChannel()
12、.GetMonitorData();到这里,已经完成了一个最简单的 WCF 程序,也涉及到了 WCF 的基本概念:终结点、ABC(地址、绑定、契约)。这个示例很简单(甚至简陋,而且编码风格和习惯也不好),只是用来初识 WCF,要做的还有很多,下次继续上一篇介绍了最简单的方式来实现宿主和客户端:直接引用契约和服务项目、采用硬编码的方式,这次通过配置文件来定义终结点。刚接触 WCF 时,直接编辑配置文件会让人一头雾水,所以还是使用直观的方式 使用 WCF 编辑工具,这个工具可以通过“开始”“Microsoft Visual Studio 2010”“Microsoft Windows SDK To
13、ols”“ 服务配置编辑器”打开,也可以通过 VS2010 的 IDE 中“工具”“WCF 服务配置编辑器” 打开。宿主:1、在之前的解决方案中添加一个 Windows 窗体应用程序 WCFDemo.Host.WithConfig。2、添加引用 System.ServiceModel。3、引用上一篇的契约和服务两个项目。4、为宿主项目添加应用程序配置文件,并编辑:运行配置工具,打开宿主项目的配置文件,右击树形目录的“服务”节点新建服务; 把 Name 属性设置为我们之前写的服务; 新建服务终结点,并设置 A(Address)、B(Binding)、C(Contract) 。设置的值和上一篇代码
14、里的一样; 保存后可以查看配置文件。5、在窗体放置两个 Button 和一个 Label,编写代码如下:using System;using System.Windows.Forms;using System.ServiceModel;using WCFDemo.Services;namespace WCFDemo.Host.WithConfigpublic partial class HostWithConfigForm : Formpublic HostWithConfigForm()InitializeComponent();ServiceHost host;private void b
15、utton1_Click(object sender, EventArgs e)host = new ServiceHost(typeof(DemoService); host.Opened += delegate label1.Text = “服务启动“; ;host.Open();private void button2_Click(object sender, EventArgs e)if (host != null ;host.Close();可以发现,和之前唯一不同就是少了添加服务终结点的代码。运行带配置的宿主程序,再运行之前的客户端程序,可以正常通讯。接下来看一下使用配置文件的客户
16、端。客户端:1、在之前的解决方案中添加一个 Windows 窗体应用程序 WCFDemo.Client.WithConfig。2、添加引用 System.ServiceModel。3、引用之前契约项目。4、为客户端项目添加应用程序配置文件,并编辑:运行配置工具,打开客户端项目的配置文件,右击树形目录的“客户端”“终结点”节点新建客户端终结点,并设置 ABC 和Name: 保存后可以查看配置文件:5、在窗体放置一个 Button 和一个 DataGridView,并编写代码如下:using System;using System.Windows.Forms;using System.Servic
17、eModel;using WCFDemo.Contracts;namespace WCFDemo.Client.WithConfigpublic partial class Form1 : Formpublic Form1()InitializeComponent();private void button1_Click(object sender, EventArgs e)using (ChannelFactory channelFactory = new ChannelFactory(“DemoService“) dataGridView1.DataSource = channelFact
18、ory.CreateChannel().GetMonitorData();代码中 ChannelFactory 构造函数的参数和配置文件中的 Name 要一致。现在,使用配置文件和不使用配置文件的宿主及客户端已完成,两个服务器和两个客户端之间都可通讯。看得出来,客户端使用服务都是对某个终结点的,客户端的 ABC 要和服务端一致。目前为止,客户端都是通过直接引用契约类库来使用 WCF 服务的,很多时候客户端无法直接引用契约类库,这就需要服务端发布自己的契约,客户端根据契约生成代理类。如何实现,下一篇再说 简短一点儿好。之前两篇随笔的示例中客户端直接引用契约类库,现实中可能因为开发团队或语言等原因
19、,客户端不能直接引用契约类库,这就需要服务端公布自己的契约、客户端发现契约。服务端:服务端通过配置服务行为,以元数据的形式公布服务。可以使用配置文件也可以使用代码。1、使用配置文件:将之前的 WCFDemo.Host.WithConfig 项目的配置文件用 WCF 服务配置编辑器打开,新建服务行为配置:这里就用默认的 Name,实际项目中起个好听的名字吧添加服务元数据:设置元数据的 HttpGetEnabled 和 HttpGetUrl:选择服务,设置其 BehaviorConfiguration 为刚添加的服务行为:保存后的配置文件:使用配置文件的方式,程序代码不需要任何修改。2、代码方式:
20、在 WCFDemo.Host.WithoutConfig 项目的启动服务代码处添加服务行为的处理代码:host = new ServiceHost(typeof(DemoService);host.AddServiceEndpoint(typeof(IDemoService), new BasicHttpBinding(), “http:/localhost:5678/DemoService“);ServiceMetadataBehavior b = new ServiceMetadataBehavior();b.HttpGetEnabled = true;b.HttpGetUrl = new
21、 Uri(“http:/localhost:5678/DemoService/metadata“);host.Description.Behaviors.Add(b);host.Opened += delegate label1.Text = “服务启动“; ;host.Open();比原来多了 4 行添加服务行为的代码。现在,运行两个宿主程序中的任意一个,点击启动按钮后,服务就启动并发布了,客户端可以发现契约并使用。客户端:客户端如何发现并使用服务,有 2 种方式:使用命令行 svcutil 生成文件、在 IDE 中添加服务引用。1、使用 svcutil运行宿主并启动服务;运行 Visual
22、 Studio 命令提示,键入 svcutil http:/localhost:5678/DemoService/metadata,将生成一个DemoService.cs 文件和一个 output.config 文件(可以通过 /out:指定输出目录);在解决方案中添加一个 Windows 窗体应用程序 WCFDemo.Client,为其添加引用 System.ServiceModel 和 System.Runtime.Serialization;将刚才生成的两个文件添加到项目,并将 output.config 改名为 App.config;在窗体上放置一个 Button 和 DataGrid
23、View,为 Button 的 Click 编写代码如下:DemoServiceClient c = new DemoServiceClient();dataGridView1.DataSource = c.GetMonitorData();当然可以不用配置文件,new DemoServiceClient()中设置参数 binding 和 remoteAddress。svcutil 常用的选项有/out: 、/config:、/noconfig: 等,详细用法这里就不介绍了。查看配置文件会发现里面内容很多,因为它自动为关键的绑定节点设置了默认值,这部分内容可以删除,所以很多时候不使用 svcu
24、til 生成的配置文件。2、添加服务引用右击 WCFDemo.Client,在添加服务引用对话框中输入地址 http:/localhost:5678/DemoService/metadata,点击“前往”按钮:给命名空间起个好名(示例中就用默认名)后确定。我们会发现,除了添加了服务引用,还修改了配置文件,如果原来没有配置文件,添加服务引用后会自动添加配置文件。在窗体上再放置一个 Button,为其 Click 编写代码如下:ServiceReference1.DemoServiceClient c = new ServiceReference1.DemoServiceClient();data
25、GridView1.DataSource = c.GetMonitorData();和前一个一样,可以不用配置文件。服务器有两种方案发布自己的元数据:基于 HTTP-GET 协议、使用专门的终结点。以上介绍的是前一种,下面介绍一下第二种。1、使用配置文件将之前的 WCFDemo.Host.WithConfig 项目的配置文件用 WCF 服务配置编辑器打开,新建服务终结点,并设置 ABC:现在 WCFDemo.Host.WithConfig 已提供两种发布服务的方式,启动服务后,客户端通过之前的地址http:/localhost:5678/DemoService/metadata 和刚才输入的地
26、址 http:/localhost:5678/DemoService/MEX,都可以找到服务。2、使用代码方式在 WCFDemo.Host.WithoutConfig 项目的启动服务代码处增加一行添加终结点代码:host = new ServiceHost(typeof(DemoService);host.AddServiceEndpoint(typeof(IDemoService), new BasicHttpBinding(), “http:/localhost:5678/DemoService“); ServiceMetadataBehavior b = new ServiceMetad
27、ataBehavior();b.HttpGetEnabled = true;b.HttpGetUrl = new Uri(“http:/localhost:5678/DemoService/metadata“);host.Description.Behaviors.Add(b);host.AddServiceEndpoint(typeof(IMetadataExchange), new CustomBinding(new HttpTransportBindingElement(), “http:/localhost:5678/DemoService/MEX“); host.Opened += delegate label1.Text = “服务启动“; ;host.Open();效果同上。