学silverlight学到WCF,嗯,WCF的确有点难,不过没所谓,一点一点来,下面就尝试着实现一个最基本最简单的WCF例子,里面不涉及到EndPoint、Binding、Element、异步调用、序列化等等这些或者其他更难理解的概念,只是简单地按照默认设置用最简单的代码把WCF最基本的应用体现出来。
第一步:创建一个空的解决方案,新建一个WCF服务应用程序项目(使用默认名字) 来模拟服务端,新建一个控制台应用程序项目(名称改为 ConsoleApp)来模拟客户端。
第二步:简单分析WcfService1项目,该项目内容如下:
一句话总结:这个项目模拟服务器端,Service1.svc文件封装的就是提供给客户端的服务引用,Service1.svc.cs文件里是服务引用的具体实现。但这里因为Service1.svc.cs文件里的主要内容——Service1类是继承于 IService1.cs文件里的 IService1接口,所以重头戏分了一半给IService1.cs文件。
首先看IService1.cs文件,从名字上可以看得出这个是接口文件,里面定义了了一些接口,接口声明了一些方法。我在里面添加两个类 public class student1 、 public class student2 和一个方法 public student1 student1Class() 用作测试。代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.ServiceModel.Web; 7 using System.Text; 8 9 namespace WcfService1 10 { 11 // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IService1”。 12 [ServiceContract] 13 public interface IService1 14 { 15 16 [OperationContract] 17 string GetData(int value); 18 19 [OperationContract] 20 CompositeType GetDataUsingDataContract(CompositeType composite); 21 22 [OperationContract] 23 student1 student1Class(); 24 25 // TODO: 在此添加您的服务操作 26 } 27 28 [DataContract] 29 public class student1 30 { 31 string str = "from IService1's student1"; 32 33 [DataMember] 34 public string Str { set { str = value; } get { return str; } } 35 } 36 37 [DataContract] 38 public class student2 39 { 40 string str = "from IService2's student1"; 41 42 [DataMember] 43 public string Str{ set{str = value;} get{return str;} } 44 } 45 46 // 使用下面示例中说明的数据约定将复合类型添加到服务操作。 47 [DataContract] 48 public class CompositeType 49 { 50 bool boolValue = true; 51 string stringValue = "Hello "; 52 53 [DataMember] 54 public bool BoolValue 55 { 56 get { return boolValue; } 57 set { boolValue = value; } 58 } 59 60 [DataMember] 61 public string StringValue 62 { 63 get { return stringValue; } 64 set { stringValue = value; } 65 } 66 } 67 }
上面这段代码要注意以下几方面:
1、服务契约
接口IService1前面加了 [ServiceContract] ,意思是把这个接口(包括继承这个接口的类)声明为服务契约,服务契约是对客户端而言的,就是这个接口 暴露 在客户端面前,就是让客户端可得见这个接口。但看得见接口不表达可以看得见接口里声明的方法,这是两回事(原因很简单,就算接口是可见的,但里面的方法也有一些是可见另一些不可见的嘛),如果想把方法也声明为对客户端可见的,得在声明方法的签名加 [OperationContract],这也叫服务契约。总结:服务契约有两种,[ServiceContract]是声明接口、类对客户端可见的,[OperationContract]是具体声明类里的那些方法对客户端可见。
2、数据契约
接口自带的类CompositeType 和我们自定义的类student1、student2 前面都加了 [DataContract] ,意思是把这个类声明为数据契约,这样子客户端就可以用这个类去定义变量了。这里说的类和上面说的类所指的具体内容稍稍有点不同,上面的类偏向于可以调用类中的方法,这里的类偏向于指作为一种类型(就像int string 等类型),可以用来定义变量。类对客户端可见,但类中的字段、变量则不一定(原理和上面说的一样),要在想暴露在客户端的字段、变量前面加 [DataMember] 。但不能在方法(如构造函数)前面加[DataMember],因为函数只能声明为服务契约嘛(加[OperationContract]),而服务契约只能在 [ServiceContract] 下面声明。总结:数据契约有两种,[DataContract]是声明类或结构的,[DataMember] 是声明类或结构中具体的字段或属性(推荐用属性)对客户可见。
再来看Service.svc.cs文件。可以看到里面只是定义了一个继承 IService1接口的类 Service1,主要内容就是实现IService1接口里声明的方法。代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.ServiceModel.Web; 7 using System.Text; 8 9 namespace WcfService1 10 { 11 // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码、svc 和配置文件中的类名“Service1”。 12 13 public class Service1 : IService1 14 { 15 public string GetData(int value) 16 { 17 return string.Format("You entered: {0}", value); 18 } 19 20 public CompositeType GetDataUsingDataContract(CompositeType composite) 21 { 22 if (composite == null) 23 { 24 throw new ArgumentNullException("composite"); 25 } 26 if (composite.BoolValue) 27 { 28 composite.StringValue += "Suffix"; 29 } 30 return composite; 31 } 32 33 public student1 student1Class() 34 { 35 return null; 36 } 37 38 39 } 40 41 }
里面的定义的类或者是实现的方法前面都没有加什么 [ServiceContract] 或 [OperationContract] 声明,因为类继承的接口已经声明了,所以类就不用再声明[ServiceContract] 了,实现接口的方法 也不用加[OperationContract] 。
可能大家有一个问题,那就是在这个类里添加自己的方法行不?答案是可以,但没意义。因为在这个类作为服务端(可以这么认为),里面的方法就是为了给客户端使用的,那么就要在声明的方法前加[OperationContract],但只有加了 [ServiceContract]属性的类里的方法才能那样子,又因为类是继承了一个具有 [ServiceContract]属性的接口了的,所以这个类就不用再加 [ServiceContract]属性了(已经有了),一句话,就是在 继承了 实现了服务契约的接口 的类里没必要。添加自己的方法。要加就先加在接口里。
当然,如果你不用接口(可以不用接口的,用接口只是为了更面向对象而已),直接在类里定义服务契约的话,直接把接口里的那些[ServiceContract] 、 [OperationContract] 照搬过来就行了。
第三步:分析ConsoleApp项目
一句话总结:红框框里的那个东西叫服务引用,也就是客户端,也就是靠它才能调用服务端提供的方法和自定义类型。添加方法如下:
右键点击 ConsoleApp项目,选择 添加服务引用(使用默认名称),会弹出一方框:
点右边的 “发现”,就会检索出发现的服务并显示在左边,并把地址也显示出来。点左边 服务里发现的服务左边的三角形,一级级打开,赫然发现里面就是 Service1 类,再往下就是 Service1类继承的接口 IService1 !对应右边就是该服务提供给客户端调用的方法!
打开ConsoleApp里的Program.cs文件,里面代码很简单,定义一个student1类的变量,然后获取她的一个属性值并输出。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Runtime.Serialization; 6 7 namespace ConsoleApp 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 //定义通信管道 client,就是通过它来调用服务端提供的方法的 14 ServiceReference1.Service1Client client = new ServiceReference1.Service1Client(); 15 client.student1Class(); 16 17 18 //服务端提供给客户端使用的类 19 ServiceReference1.student1 stu = new ServiceReference1.student1(); 20 Console.WriteLine(stu.Str); 21 Console.ReadKey(); 22 23 24 } 25 } 26 }
ServiceReference1(其实是一个命名空间)就是刚才添加的服务引用的名称。当输入ServiceReference1.的时候,后面就会出现智能感知到的这个命名空间能引用的类型、接口等内容(有时候没有出现如你所愿的类型时,右键点WcfService1项目选择重新生成,然后右键点ConsoleApp项目的ServiceReference1选择更新服务引用,这个很容易忘掉,每对服务端做了什么修改都要这么来一下)。图如下:
里面还定义了一个什么通信管道的东西,这个东西估计就是把服务端提供的可工客户端引用的方法封装成了一个类,通过这个类就可以调用那些方法了,详情见代码。
最后按F5运行,没什么问题吧?没有输出?这是正常的,如果你想看到输出结果,可以先对Str属性赋值(stu.Str = "123";)。嘿嘿,那就算完成了。
最后还有两个问题:
1、在IService1接口里自定义了两个类 student1 和student2,为什么输入ServiceReference1.后的智能感知里只有 student1类型而没有student2类型?因为在IService1接口里有一个方法 public student1 student1Class() ,这个方法的返回值是 studnet1类型,这就是差别。。。。就是你想在客户使用在服务端里自定义的类型,除了用[DataContract]声明之外,还要有一个服务契约(方法)的返回值是这个类型的。至于为什么,我也不知道。。求答案
2、student1类里的Str属性有一个Get{return str;}方法,而字段str是有默认值的,为什么在Program.cs里定义了一个student1类的变量后,获取Str属性的值并输出时是空白?也就是说属性的默认值发生了丢失!为什么?不知道,求答案。。。