设计和实现服务协定
创建服务协定—WCF术语
消息
消息是一个独立的数据单元,它可能由几个部分组成,包括消息正文和消息头。
服务
服务是一个构造,它公开一个或多个终结点,其中每个终结点都公开一个或多个方法。
终结点
终结点是用来发送或接收消息(或执行这两种操作)的构造。终结点包括一个定义消息可以发送到目的地的位置(地址,一个描述消息如何发送的通信机制规范(绑定)以及对于可以再该位置发送或接收(或者两者皆可)的一组消息的定义(服务协定)——该定义还描述了可以发送何种消息。
WCF服务作为一个终结点集合向外界公开。
创建服务协定
定义服务协定
在类或接口上使用ServiceContractAttribute属性标记
定义服务操作
在方法上使用OperationContractAttribute属性对其进行标记
参数和返回值
每个操作都有一个返回值和一个参数,即使它们为void。可以使用局部方法将对对象的引用从一个对象传递到另一个对象,但与局部方法不同的是,服务操作不会传递对对象的引用,它们传递的是对象的副本。这是因为参数或返回值中使用的每个类型都必须是可序列化的,换言之,该类型的对象必须是能够转换为字节流,并能够从字节流转换为对象。
默认情况下,基元类型是可序列化的,.NET Framework中的很多类型都是可序列化的。
服务操作的消息模式1——请求/答复
通过请求/答复模式,请求发送方(客户端应用程序)将接收与请求相关的答复。这是默认的模式,因为它既支持传入操作(一个或多个参数传递到该参数中),也支持返回操作(将一个或多个输出值传回给调用方)
[OpertaionContract]
String SayHello(string name);
注意:除非指定其他的基础消息模式,否则,即使服务操作返回void,也属于请求/答复消息交换。
操作结果是:除非客户端异步调用操作,否则客户端将停止处理,直到收到返回消息,即使该消息正常情况下为空也是如此。
优点
响应消息中可返回SOAP错误,这表明可能在通信或处理中发生了一些与服务有关的错误状况。
缺点
如果执行操作需要很长的时间,则会降低客户端性能和响应能力。
代码演示
1) 新建一个WCF服务库
2) 默认生成文件目录如下:
注:ISayHelloService和SayHelloService为修改过的文件名
3) 在ISayHelloService.cs文件中声明SayHello函数
[ServiceContract]
public interface ISayHelloService
{
[OperationContract]
string SayHello(string name);
}
4) 在SayHelloService.cs文件中实现该接口
public class SayHelloService : ISayHelloService
{
public string SayHello(string name)
{
return "Hello " + name;
}
5) F5运行该WCF项目,会弹出调试框,可进行调试。如下:
请求/答复模式调试效果图
服务操作的消息模式2——单向
如果WCF服务应用程序的客户端不必等待操作完成,并且不处理SOAP错误,则该操作可以指定单向的消息模式。
单向操作是客户端调用操作,并在WCF将消息写入网络后继续进行处理的操作。通常意味着,除非在出站消息中发送的数据极其庞大,否则客户端几乎立即继续运行(除非发送出错)。此种类型的消息交换模式支持从客户端到服务应用程序的类似事件的行为,即:及时响应。
若要为返回void的操作指定单向消息交换,将OperationContact的IsOneWay属性设置为true即可,默认为false。如下所示:
[OperationContract(IsOneWay=true)]
String SayHello(string name);
此方法与前面的请求/答复示例相同,但是将IsOneWay属性设置为true意味着尽管方法相同,服务操作也不会发送返回消息,而客户端将在出站消息抵达通道层时立即返回。
服务操作的消息模式3——双工
双工模式的特点是,无论使用单向消息发送还是请求/答复消息发送方式,服务和客户端均能够独立的向对方发送消息。对于必须直接与客户端通信或向消息交换的任意一方提供异步体验(包括类似于事件的行为)的服务来说,这种双向通信形式非常有用。
由于存在与客户端通信的附加机制,双向模式比请求/答复或单向模式要略为复杂。
若要设计双工协定,还必须设计回调协定,并将该回调协定的类型分配给标记服务的ServiceContractAttribute属性(attribute)的CallbackContract属性(property)。
若要实现双工模式,必须创建第二个接口,该接口包含在客户端调用方法声明。
代码演示
1) 在原有的文件结构基础上添加如下文件:
ICalculatorService.cs接口文件和CalculatorService.cs类文件
项目目录文件如下:
2) 接口文件ICalculatorService.cs文件,主要定义了算术操作接口,和一个回调接口,代码如下:
[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples",
SessionMode=SessionMode.Required,
CallbackContract=typeof(ICalculatorCallBackService))]
//添加ServiceContract服务协定标记,并为其属性赋值,其属性含义为:
NameSpace:Web服务描述语言中<portType>元素的命名空间,
SessionMode:设置协定模式为必须要求会话绑定,
CallbackContract:设置其回调协定(本例中即获取的回调接口的类型)
interface ICalculatorService//服务端算术操作接口,均为单向模式
{
[OperationContract(IsOneWay = true)]
void Clear();
[OperationContract(IsOneWay = true)]
void AddTo(double n);
[OperationContract(IsOneWay = true)]
void SubtractFrom(double n);
[OperationContract(IsOneWay = true)]
void MutiplyBy(double n);
[OperationContract(IsOneWay = true)]
void DivideBy(double n);
}
interface ICalculatorCallBackService//客户端要实现的回调接口
{
[OperationContract(IsOneWay = true)]
void Equals(double result);
[OperationContract(IsOneWay = true)]
void Equation(string result);
}
3) 实现接口ICalculatorService的接口文件CalculatorService.cs类文件
该类文件主要实现了ICalculatorService接口中定义的类。具体代码如下:
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
//该类添加了ServiceBehavior属性,用来指定服务协定实现的内部执行行为,并设置其属性InstanceContextMode为PerSession,即为每一个会话创建一个新的InstancContext对象
public class CalculatorService:ICalculatorService
//实现ICalculatorService的所有方法
{
double result;
string equation;
ICalculatorCallBackService callback = null;//定义一个接口对象
public CalculatorService()
{
result = 0.0D;
equation = result.ToString();
callback = OperationContext.Current.GetCallbackChannel<ICalculatorCallBackService>();
//获取调用当前操作的客户端实例的通道,实例化上边定义接口对象
}
public void Clear()
{
callback.Equation(equation + "=" + result.ToString());
result = 0.0D;
equation = result.ToString();
}
public void AddTo(double n)
{
result += n;
equation +="+"+ n.ToString();
callback.Equals(result);
}
public void SubtractFrom(double n)
{
result -= n;
equation += "-" +n.ToString();
callback.Equals(result);
}
public void MutiplyBy(double n)
{
result *= n;
equation += "*" + n.ToString();
callback.Equals(result);
}
public void DivideBy(double n)
{
result /= n;
equation += "/" + n.ToString();
callback.Equals(result);
}
}
4) 修改配置文件
因为我们的这个项目 是在上一个项目上添加的,所以需要在配置文件中再进行一些添加。
在<services></services>节点内添加一个新的<service></service>节点,如下:
<service name="WCF.CalculatorService" behaviorConfiguration="WCF.CalculatorBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8732/Design_Time_Addresses/WCF/CalculatorService/"/>//服务地址
</baseAddresses>
</host>
<endpoint address="" binding="wsDualHttpBinding" contract="WCF.ICalculatorService"/>//算术操作协定
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>//元数据协定
</service>
5) 在解决方案中添加一个控制台应用程序,Program.cs中定义一个CallbackHandler类,该类实现了服务端的回调接口ICalculatorServiceCallback,代码如下
public class CallbackHandler :ICalculatorServiceCallback
{
public void Equals(double n)
{
Console.WriteLine("Result{0}", n);
}
public void Equation(string equ)
{
Console.WriteLine("Equation({0})", equ);
}
}
Program类具体代码如下:
class Program
{
static void Main(string[] args)
{
InstanceContext instanceContext = new InstanceContext(new CallbackHandler());//创建一个服务实例的上下文信息对象,用来为初始化算术服务的客户端实例做准备
CalculatorServiceClient calculator = new CalculatorServiceClient(instanceContext);//创建一个算术服务的客户端实例,并在下边调用服务端的方法进行计算,在计算方法中又调用客户端的回调方法,显示操作结果
Console.WriteLine("Hello Olive,Let's begin");
Console.WriteLine();
double value = 100.00D;
calculator.AddTo(value);
value = 50.00D;
calculator.SubtractFrom(value);
value = 12.00D;
calculator.MutiplyBy(value);
value = 3.00D;
calculator.DivideBy(value);
calculator.Clear();
Console.WriteLine();
Console.ReadLine();
}
}
创建数据协定
面向服务的应用程序(例如:WCF程序)设计为与Microsoft平台和非Microsoft平台上的最大可能数量的客户端应用程序进行互操作。
为了获得最大可能的互操作性,建议使用DataContractAttribute和DataMemberAttribute属性对类型进行标记,以创建数据协定。
数据协定是服务协定的一部分,用于描述您的服务操作交换的数据。
如下代码所示:
[DataContract]
public class CompositeType
{
bool boolValue = true;
string stringValue = "Hello ";
[DataMember]
public bool BoolValue
{
get { return boolValue; }
set { boolValue = value; }
}
[DataMember]
public string StringValue
{
get { return stringValue; }
set { stringValue = value; }
}
}
数据协定是可选的样式协定:除非您显示应用数据协定属性,否则不会序列化任何类型或数据成员。
数据协定与托管代码的访问范围无关:可以对私有数据成员进行序列化,并将其发送到其他位置,以便可以公开访问它们。
WCF处理用于启用操作功能的基础SOAP消息的定义,并处理数据类型到消息正文的序列化和从消息正文进行的反序列化。数据类型一旦序列化,就无需在设计操作时考虑基础消息交换基础结构。
可以使用其他序列化机制。标准ISerializable,SerializableAttribute和IXmlSerializable机制都可以用于处理数据类型到基础SOAP消息的序列化,这些消息可将数据类型从一个应用程序带到另一个应用程序。
Out和Ref参数
大部分情况下,可以使用in参数out和ref参数,由于out和ref参数都指示数据是从操作返回的,类似如下的操作签名会指定需要请求/答复操作,即使操作签名返回void也是如此。
使用out或ref参数要求操作具有基础响应消息,才可以将已修改的对象传回。如果操作是单向操作,则将在运行时引发InvalidOperationException异常。