契约
在WCF中,所有的WCF服务都会被公开成为契约。契约是服务的功能的标准描述方式,通常情况下WCF包含四种类型的契约,这些契约如下所示。
q 服务契约(Service Contract):服务契约定义了客户端能够执行的操作,服务契约是WCF中使用最为广泛的一种契约。
q 数据契约(Data Contract):数据契约定义了客户端与服务器之间交互的数据类型。
q 错误契约(Fault Contract):错误契约定义了操作中出现的异常,包括定义服务出现的错误并传递返回给客户端。
q 消息契约(Message Contract):消息契约允许服务直接与消息交互,但是WCF很少使消息契约。
WCF使用特性ServiceContractAttribute标识服务契约,而使用OperationContractAttribute标识服务方法。示例代码如下所示。
[ServiceContract] //标识服务契约
publicinterface IService1 //实现接口
{
[OperationContract] //方法声明
string GetData(int value); //创建方法
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
// 任务: 在此处添加服务操作
}
上述代码使用ServiceContractAttribute标识服务契约,而使用OperationContractAttribute标识服务方法,OperationContract只能用于方法,指明客户端是否能够调用此方法。使用OperationContract标识可以标识私有方法以使用SOA的方式进行构架,虽然这样是可以实现客户端调用,但是作为面向对象的设计是不推荐使用该方法的。由于能够使用ServiceContractAttribute来标识服务契约,开发人员能够自定义标识指定相应的方法是否能够被客户端调用,示例代码如下所示。
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite); //标识方法
string Post(string content);
在上述代码中的Post方法不会成为契约。WCF允许开发人员使用DataContractAttribute、DataMemberAttribute来标识自定义数据类型和属性,示例代码如下所示。
[DataMember] //设置DataMember
string stringValue = "Hello "; //创建string变量
[DataMember] //设置DataMember
public bool BoolValue //设置属性
{
get { return boolValue; }
set { boolValue = value; } //设置属性默认值
}
[DataMember] //设置DataMember
public string StringValue //设置属性
{
get { return stringValue; }
set { stringValue = value; } //设置属性默认值
}
上述代码使用了DateMember定义了属性和相应的字段,这样就可以在服务方法中传递复杂的数据体了。
WCF应用
在了解了基本的WCF概念后,先不用着急继续了解WCF应用体系,通过创建WCF应用可以深入的了解服务、地址和契约的概念。WCF还允许开发人员创建和声明契约,通过契约的声明,客户端可以通过远程调用以实现自身的程序。
18.3.1 创建WCF应用
在Visual Studio2008中,可以方便的创建WCF应用。在菜单栏中选择【文件】选项,在下拉菜单中单击【新建项目】选项,在弹出的【新建项目】窗口中选择WCF,如图18-7所示。
图18-7 创建WCF服务库
创建WCF服务库后,应用程序会自动生成Server1.cs和IServer1.cs接口,IServer1.cs接口示例代码如下所示。
using System;
using System.Collections.Generic;
using System.Linq; //使用必要的命名空间
using System.Runtime.Serialization; //使用必要的命名空间
using System.ServiceModel; //使用必要的命名空间
using System.Text;
namespace _18_2
{
// 注意: 如果更改此处的接口名称“IService1”,也必须更新 App.config 中对“IService1”的引用。
[ServiceContract]
publicinterface IService1
{
[OperationContract] //声明契约
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
// 任务: 在此处添加服务操作
}
// 使用下面示例中说明的数据协定将复合类型添加到服务操作
[DataContract]
publicclass CompositeType //创建类
{
boolboolValue = true; //创建字段
[DataMember]
string stringValue = "Hello "; //声明string变量
[DataMember]
public bool BoolValue //创建属性
{
get { return boolValue; }
set { boolValue = value; }
}
[DataMember]
public string StringValue //创建属性
{
get { return stringValue; }
set { stringValue = value; }
}
}
}
上述代码创建了一个IServer1接口,并通过ServiceContractAttribute标识服务契约,同样也通过DataContractAttribute、DataMemberAttribute来标识自定义数据类型和属性,示例代码如下所示。
[ServiceContract] //标识服务契约
publicinterface IService1 //创建接口
[DataContract]
publicclass CompositeType
创建接口后则需要实现接口,接口的实现在Server.cs文件内,示例代码如下所示。
publicclass Service1 : IService1 //实现接口
{
public string GetData(int value) //实现接口方法
{
return string.Format("You entered: {0}", value); //返回string值
}
public CompositeType GetDataUsingDataContract(CompositeType composite) //实现接口方法
{
if (composite.BoolValue) //实现方法代码
{
composite.StringValue += "Suffix"; //添加相应数据
}
return composite; //返回值
}
上述代码实现了IServer1接口中的方法,单击【运行】按钮或快捷键【F5】,WCF应用程序就能够运行,如图18-8所示。
从图18-8中可以看出,在Server1.cs文件中实现的方法都能够在测试客户端中看到,单击测试客户端中相应的方法就能够在客户端测试调用服务器端的方法,如图18-9所示。
图18-8 服务测试客户端 图18-9 测试方法调用
双击GetData方法后,在右侧选项卡中就会分为两层,这两层分别为请求和响应。在请求框中可以在值那一栏编写需要传递的值,编写完毕后单击【调用】按钮就能够实现服务器端方法的调用并在响应框中呈现相应的值。
18.3.2 创建WCF方法
一个简单的WCF应用程序运行后,就会发现其实WCF并没有想象中的复杂。WCF允许开发人员通过使用ServiceContractAttribute标识服务契约,同样开发人员还能够创建服务契约以提供客户端的方法的调用。在IServer1接口中首先需要定义该方法,示例代码如下所示。
intGetSum(DateTime time); //定义接口方法
[OperationContract] //标识调用
string GetShopInformation(string address);
上述代码声明了两个方法,这两个方法分别为GetSum和GetShopInformation,,GetSum用于获取某一天麦当劳餐厅中出售总量,而GetShopInformation用于获取麦当劳地址和一些商店的信息,GetSum和GetShopInformation具体实现如下所示。
public int GetSum(DateTime time) //实现方法
{
int BreadNum = 10; //声明必要字段
int Milk = 5; //声明必要字段
int HotDryNuddle = 20; //声明必要字段
int today = BreadNum + Milk + HotDryNuddle; //实现计算
return today; //返回值
}
public string GetShopInformation(string address) //实现方法
{
if (address == "武汉") //判断地址
{
return "武汉麦当劳连锁店"; //返回相应结果
}
else if (address == "北京") //判断地址
{
return "北京麦当劳连锁店"; //返回相应结果
}
else if (address == "上海") //判断地址
{
return "上海麦当劳连锁店"; //返回相应结果
}
else
{
return "没有该连锁店"; //返回默认结果
}
}
在GetSum方法中,用于获取当天的销售总量,而GetShopInformation实现了商店信息的反馈,运行后如图18-10所示。
图18-10 创建方法
从图18-10中可以看出,GetShopInformation已经在测试客户端中服务器项目中显式了,并且输入了“武汉”这个信息,就能够返回“武汉麦当劳连锁店”。而GetSum方法并没有呈现在服务器项目中,这是因为GetSum方法并没有使用[OperationContract]标识进行声明,所以不会作为契约的一部分,若需要在客户端调用GetSum方法就必须使用[OperationContract]标识进行声明,示例代码如下所示。
[OperationContract]
intGetSum(DateTime time); //标识客户端方法
WCF应用程序运行后如图18-11所示。
图18-11 使用[OperationContract]标识
开发人员能够使用WCF快速的创建WCF应用程序并从客户端调用该方法,这样就能够在客户端隐藏服务器方法并且让客户端只关注逻辑实现而不需要关注底层是如何进行消息通信的。
18.4 WCF消息传递
通过了解了WCF的一些基本概念并创建和编写WCF应用中的相应方法,实现了WCF服务和客户端之间的调用,就能够理解WCF应用是如何进行通信的。了解了一些基本的WCF概念后,还需要深入了解WCF消息的概念。
18.4.1 消息传递
客户端与服务器之间是通过消息进行信息通信的,通过使用消息,客户端和服务器之间能够通过使用消息交换来实现方法的调用和数据传递。
1.Request/Reply消息传递模式
Request/Reply模式是默认的消息传递模式,该模式调用服务器的方法后需要等待服务的消息返回,从而获取服务器返回的值。Request/Reply模式是默认模式,在声明时无需添加其模式的声明,示例代码如下所示。
[OperationContract]
string GetShopInformation(string address); //默认模式
上述代码就使用了一个默认的Request/Reply模式进行消息传递,GetShopInformation方法同样需要实现,示例代码如下所示。
public string GetShopInformation(string address)
{
if (address == "武汉") //判断地址
{
return "武汉麦当劳连锁店"; //返回相应结果
}
else if (address == "北京") //判断地址
{
return "北京麦当劳连锁店"; //返回相应结果
}
else if (address == "上海") //判断地址
{
return "上海麦当劳连锁店"; //返回相应结果
}
else
{
return "没有该连锁店"; //返回默认结果
}
}
GetShopInformation方法返回一个string的值给客户端,客户端调用服务器的方法时,首先会向服务器发送消息,以告诉服务器客户端需要调用一个方法,当服务器接收消息后会返回消息给客户端。在这一段过程中,客户端会等待服务器端的相应,当客户端接受到服务器的相应后,则会呈现在客户端应用程序中。如图18-12所示。
图18-12 Request/Reply模式
2.One-way消息传递模式
One-way模式和Request/Reply模式不同的是,如果使用One-way模式定义一个方法,该方法被调用后会立即返回。使用One-way模式修饰的方法必须是void方法,如果该方法不是void修饰的方法或者包括out/ref等参数,则不能使用One-way模式进行修饰,示例代码如下所示。
[OperationContract(IsOneWay = true)] //标识One-way模式
voidOutputString(); //定义方法
该方法使用了One-way模式,则不能有参数的输出,只允许void关键字修饰该方法,OutpuString方法的具体实现如下所示。
public void OutputString() //实现方法
{
Console.WriteLine("IsOneWay=true");
}
运行WCF应用后,执行OutpuString方法后结果如图18-13所示。
图18-13 One-way模式
WCF的消息传递模式不仅包括这两种模式,还包括duplex模式,duplex是WCF消息传递中比较复杂的一种模式,由于篇幅限制,本书不再进行详细的介绍。
18.4.2 消息操作
由于WCF的客户端和服务器之间都是通过消息响应和通信的,那么在WCF应用的运行过程中,消息是如何在程序之间进行操作的,这就需要通过XML文档来获取相应的结果。在客户端和服务器之间出现信息通信,并且客户端调用了服务器的方法时,就会产生消息,如GetSum方法。GetSum方法在接口中的代码如下所示。
[OperationContract] //标识方法
intGetSum(DateTime time); //定义方法
在GetSum方法的实现过程中,只需要进行简单的操作即可,示例代码如下所示。
public int GetSum(DateTime time) //实现方法
{
int BreadNum = 10; //声明必要字段
int Milk = 5; //声明必要字段
int HotDryNuddle = 20; //声明必要字段
int today = BreadNum + Milk + HotDryNuddle; //实现计算
return today; //返回值
}
上述代码执行后,客户端会调用服务器的GetSum方法,服务器接受响应再返回给客户端相应的值,如图18-14和图18-15所示。
图18-14 执行服务器方法 图18-15 返回的XML格式文档
在运行后,测试客户端能够获取请求时和响应时的XML文档,其中请求时产生的XML文档如下所示。
<s:Envelope
xmlns:a="http://www.w3.org/2005/08/addressing"xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IService1/GetSum</a:Action>
<a:MessageID>urn:uuid:dcc8a76e-deaf-45c4-a80c-2034b965d001</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
</s:Header>
<s:Body>
<GetSum xmlns="http://tempuri.org/">
<time>2008-10-03T17:30:00</time>
</GetSum>
</s:Body>
</s:Envelope>
从上述代码可以看到在Action节中,使用了相应的方法GetSum,在WCF服务库编程中可以通过使用OperationContract.Action捕获相应的Action消息,示例代码如下所示。
[OperationContract(Action = "GetSum", ReplyAction ="GetSum")]
Message MyProcessMessage(Message m);
MyProcessMessage实现示例代码如下所示。
public Message MyProcessMessage(Message m)
{
CompositeType t = m.GetBody<CompositeType>(); //获取消息
Console.WriteLine(t.StringValue); //输出消息
return Message.CreateMessage(MessageVersion.Soap11,
"Add",new CompositeType("Hello World!")); //返回消息
}
上述代码将操作转换为消息后发送,开发人员可以通过Windows应用程序或ASP.NET应用程序获取修改后消息的内容。在进行消息的操作时,WCF还允许开发人员使用MessageContractAttribute / MessageHeaderAttribute 来控制消息格式,这比 DataContractAttribute 要更加灵活。
18.5 使用WCF服务
创建了一个WCF服务之后,为了能够方便的使用WCF服务,就需要在客户端远程调用服务器端的WCF服务,使用WCF服务提供的方法并将服务中方法的执行结果呈现给用户,这样保证了服务器的安全性和代码的隐秘性。
18.5.1 在客户端添加WCF服务
为了能够方便的在不同的平台,不同的设备上使用执行相应的方法,这些方法不仅不能够暴露服务器地址,同样需要在不同的客户端上能呈现相同的效果,这些方法的使用和创建不能依赖本地的应用程序,为了实现跨平台的安全应用程序开发就需要使用WCF。
创建了WCF服务,客户端就需要进行WCF服务的连接,如果不进行WCF服务的连接,则客户端无法知道在哪里找到WCF服务,也无法调用WCF提供的方法。首先需要创建一个客户端,客户端可以是ASP.NET应用程序也可以是WinForm应用程序。右击解决方案管理器,单击【项目】,在下拉菜单中选择【添加新项】,分别为该项目添加一个ASP.NET应用程序和一个WinForm应用程序,如图18-16和图18-17所示。
图18-16 添加Win Form应用程序 图18-17 添加ASP.NET应用程序
添加完成后在项目中就会出现这两个项目,分别为这两个项目添加WCF引用,右击当前项目,在下拉菜单中单击【添加服务引用】选项,在弹出窗口中单击【发现】按钮,即可发现WCF服务,如图18-18所示。添加完成后WCF服务就会被挂起,等待客户端对WCF服务中的方法进行调用,如图18-19所示。
图18-18 添加服务引用 图18-19 WCF服务主机已经启动
分别为ASP.NET应用程序和Win Form应用程序添加WCF引用后,就可以在相应的应用程序中使用WCF服务提供的方法了。
18.5.2 在客户端使用WCF服务
当客户端添加了WCF服务的引用后,就能够非常方便的使用WCF服务中提供的方法进行应用程序开发。在客户端应用程序的开发中,几乎看不到服务器端提供的方法的实现,只能够使用服务器端提供方的方法。对于客户端而言,服务器端提供的方法是不透明的。
1.ASP.NET客户端
在ASP.NET客户端中,可以使用WCF提供的服务实现相应的应用程序开发,例如通过地名获取麦当劳的商店的信息,而不想要在客户端使用数据库连接字串等容易暴露服务器端的信息,通过使用WCF服务提供的方法能够非常方便的实现这一点。Aspx页面看代码如下所示。
<body>
<formid="form1" runat="server">
<div>
输入地名:<asp:TextBox ID="TextBox1"runat="server"></asp:TextBox>
<br />
<br />
获得的结果:<asp:TextBox ID="TextBox2"runat="server"></asp:TextBox>
<br />
<br />
<asp:Button ID="Button1" runat="server"οnclick="Button1_Click" Text="检索" />
</div>
</form>
</body>
上述代码在页面中拖放了两个Textbox控件分别用于用户输入和用户结果的返回,并拖放了一个按钮控件用于调用WCF服务中的方法并返回相应的值。.cs页面代码如下所示。
protected void Button1_Click(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(TextBox1.Text))
{
//开始使用WCF服务
ServiceReference1.Service1Client ser = newWeb.ServiceReference1.Service1Client();
TextBox2.Text = ser.GetShopInformation(TextBox1.Text); //实现方法
}
else
{
TextBox2.Text = "无法检索,字符串为空"; //输出异常提示
}
}
上述代码创建了一个WCF服务所提供的类的对象,通过调用该对象的GetShopInformation方法进行本地应用程序开发。上述代码运行后如图18-20和图18-21所示。
图18-20 实现检索功能 图18-21 实现异常处理
2.Win Form客户端
在Win Form客户端中使用WCF提供的服务也非常的方便,其使用方法基本同ASP.NET相同,这也说明了WCF应用的开发极大的提高了开发人员在不同客户端之间的开发效率,节约了开发成本。在Win Form客户端中拖动一些控件作为应用程序开发提供基本用户界面,示例代码如下所示。
private void InitializeComponent()
{
this.textBox1 = new System.Windows.Forms.TextBox(); //创建textBox
this.dateTimePicker1 = new System.Windows.Forms.DateTimePicker(); //创建TimePicker
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(13, 13); //实现textBox属性
this.textBox1.Name = "textBox1"; //实现textBox属性
this.textBox1.Size = new System.Drawing.Size(144, 21); //实现textBox属性
this.textBox1.TabIndex = 0; //实现textBox属性
//
// dateTimePicker1
//
this.dateTimePicker1.Location = newSystem.Drawing.Point(166, 13); //实现TimePicker属性
this.dateTimePicker1.Name = "dateTimePicker1"; //实现TimePicker属性
this.dateTimePicker1.Size = new System.Drawing.Size(114, 21); //实现TimePicker属性
this.dateTimePicker1.TabIndex = 1; //实现TimePicker属性
this.dateTimePicker1.ValueChanged //实现TimePicker属性
+= new System.EventHandler(this.dateTimePicker1_ValueChanged);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); //实现Form属性
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; //实现Form属性
this.ClientSize = new System.Drawing.Size(292, 62); //实现Form属性
this.Controls.Add(this.dateTimePicker1); //添加Form控件
this.Controls.Add(this.textBox1); //添加Form控件
this.Name = "Form1"; //实现Form属性
this.Text = "Form1"; //实现Form属性
this.ResumeLayout(false);
this.PerformLayout();
}
上述代码在Win From窗体中创建了一个TextBox控件和一个DataTimePicker控件,并向窗体注册了dateTimePicker1_ValueChanged事件,当DataTimePicker控件中的值改变后,则会输出相应天数的销售值。在前面的WCF服务中,为了实现销售值统计,创建了一个GetSum方法,在Win From窗体中无需再实现销售统计功能,只需要调用WCF服务提供的方法即可,示例代码如下所示。
private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
{
ServiceReference1.Service1Client ser = newWindowsForm.ServiceReference1.Service1Client();
textBox1.Text =ser.GetSum(Convert.ToDateTime(dateTimePicker1.Text)).ToString();
}
上述代码使用了WCF服务中提供的GetSum方法进行了相应天数的销售额的统计,运行后如图18-22所示。
图18-22 Win From客户端使用WCF服务
创建和使用WCF服务不仅能够实现不同客户端之间实现相同的功能,还通过WCF应用提供了一个安全性、可依赖、松耦合的开发环境,对于其中任何一种客户端的实现,都不会暴露服务器中的私密信息,并且对于其中的某个客户端进行任何更改,也不会影响其他客户端,更不会影响到WCF服务器,这对应用程序开发和健壮性提供了良好的环境。