从零开始学WCF(2):设计和实现服务协定
消息
- 消息是一个独立的数据单元,它可能由几个部分组成,包括消息正文和消息头
服务
- 服务是一个构造,它公开一个或多个终结点,其中每个终结点都公开一个或多个服务操作
终结点
- 终结点是用来发送或接受消息(或执行这两种操作)的构造。
- 终结点包括:
- 一个定义消息可以发送到的目的地的位置(地址 Address)
- 一个描述消息应如何发送的通信机制的规范(绑定 Binding)
- 对于可以在该位置发送或接受(或两者皆可)的一组消息的定义(服务协定)该定义还描述了可以发送何种消息。
- WCF服务作为一个终结点集合向外界公开
创建服务协定
- 类或接口都可以定义服务协定
- 建议使用接口,因为接口可以直接对服务协定建模
- 服务协定接口具有所有托管接口的所有优点:
- 服务协定接口可以扩展任何数量的其他服务协定接口。
- 一个类可以通过实现服务协定接口来实现任意数量的服务协定。
- 可以通过改变接口实现来修改服务协定的实现,而让服务协定保持不变。
- 可以通过实现旧接口和新接口来确定服务的版本。老客户端连接到原始版本,而新客户端则可以连接到较新的版本。
定义服务协定
- 在类或接口上使用ServiceContractAttribute属性标记
定义服务操作
- 在方法上使用OprerationContractAttribute属性对其进行标记
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode = SessionMode.Required, CallbackContract = typeof(ICalculatorDuplexCallback))] public interface ICalculatorDuplex { [OperationContract(IsOneWay = true)] void Clear(); [OperationContract(IsOneWay = true)] void AddTo(double n); [OperationContract(IsOneWay = true)] void SubtractFrom(double n); [OperationContract(IsOneWay = true)] void MultiplyBy(double n); [OperationContract(IsOneWay = true)] void DivideBy(double n); }
参数和返回值
- 每个操作都一个返回值,即使它们为Void。可以使用局部方法将对对象的引用从一个对象传递到另一个对象,但与局部方法不同的是,服务操作不会传递对象的引用,它们传递的只是对象的副本。
- 这一点很重要,这是因为参数或返回值中使用的每个类型都必须是可序列化的,换言之,该类型的对象必须能够转换为字节流,并能够从字节流转换为对象。
- 默认情况下基元类型是可序列化的。
创建服务协定-服务操作的消息模式
- 请求/答复
- 通过请求/答复模式,请求发送方(客户端应用程序)将接受与请求相关的答复。
- 除非指定其他基础模式,否则,即使服务操作返回void,也属于请求/答复消息交换。
- 操作的结果是:除非客户端异步调用操作,否则客户端将停止处理,直到收到返回消息。
缺点:如果执行操作需要很长时间,则会降低客户端性能和影响能力。
优点:响应消息中可返回SOAP错误,这表明可能在通信或处理中发生了一些和服务有关的错误状况。
- 单向
- 如果WCF服务应用程序的客户端不必等待操作完成,并且不处理SOAP错误,则该错误可以指定为单向消息模式。
- 单向操作是客户端调用操作并在WCF将消息网络后继续进行处理的操作。 通常这意味着,除非在出站消息中发送的数据及其庞大,否则客户端几乎立即继续运行(除非发送数据时出错)。此种类型的的消息交换模式支持从客户端到服务应用程序的类似于事件的行为。
- 若要返回Void的操作指定单向消息交换,请将IsOneWay属性设置为True,默认为False。
- 此方法与前面的请求/答复示例相同,但是,将 IsOneWay属性设置为 true意味着尽管方法相同,服务操作也不会发送返回消息,而客户端将在出站消息抵达通道层时立即返回。
[OperationContract(IsOneWay=true)] void TestMethod(string strInput);
- 双工
- 双工模式的特地是,无论使用单向消息发送还是请求/答复消息发送方式,服务和客户端都能够独立的向对方发送消息。对于必须直接与客户端通信或向消息交换的任意一方提供异步体验(包括类似于事件的行为)的服务来说,这种双向通讯形式非常有用
- 由于存在与客户端通信的附加机制,双向模式比请求/答复或单向模式要略微复杂。
- 若要设计双工协定,还必须设计回调协定,并将该回调协定的类型非配给标记服务协定的ServiceContractAttribute的CallBackContract属性
- 若要实现双工模式,您必须创建第二个接口,该接口包含在该客户端调用的方法声明。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] public class CalculatorService : ICalculatorDuplex { double result; string equation; ICalculatorDuplexCallback callback = null; public CalculatorService() { result = 0.0D; equation = result.ToString(); callback = OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>(); } 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 MultiplyBy(double n) { result *= n; equation += " * " + n.ToString(); callback.Equals(result); } public void DivideBy(double n) { result /= n; equation += " / " + n.ToString(); callback.Equals(result); } }
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode = SessionMode.Required, //因为是连续的调用,所以是SessionMode.Required CallbackContract = typeof(ICalculatorDuplexCallback))] public interface ICalculatorDuplex { [OperationContract(IsOneWay = true)] void Clear(); [OperationContract(IsOneWay = true)] void AddTo(double n); [OperationContract(IsOneWay = true)] void SubtractFrom(double n); [OperationContract(IsOneWay = true)] void MultiplyBy(double n); [OperationContract(IsOneWay = true)] void DivideBy(double n); } public interface ICalculatorDuplexCallback { [OperationContract(IsOneWay = true)] void Equals(double result); [OperationContract(IsOneWay = true)] void Equation(string eqn); }
创建数据协定
- 面向服务的应用程序(例如Windows Communciation Foundation 应用程序)设计与微软平台和非微软平台上的最大可能数量的客户端应用程序进行互操作
- 为了获得最大可能的互操作性,建议您使用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; } } }
- 数据协定是可选的样式协定:除非您显示应用数据协定属性,否则不会序列化任何类型或数据成员。(只有打标签的类型和属性才会被序列化)
- 数据协定与托管代码的访问类型无关;可以对私有数据成员进行序列化,并将其发送到其他位置,以便可以公开访问它们。(即使是private的属性,只要打标签一样可以序列化)
- WCF处理用于启用操作功能的基础SOAP的消息的定义,并处理数据类型到消息正文的序列化和从消息正文进行的反序列化,数据类型一旦序列化,您就无须在设计操作时考虑基础消息交互基础结构。(无须管它是怎样序列化的,结构如何,打了标签就能够序列化反序列化)
- 可以使用其他序列化机制。标准ISerializable,SerializableAttribute和IXmlSerializable机制都可用于处理数据类型到基础SOAP消息的序列化,这些消息可将数据类型从一个应用程序带到另一个应用程序。
Out和Ref参数
- 大部分情况下您可以使用in参数out和ref参数。由于out和ref参数都指示数据是从操作返回的,类似如下的操作签名会指定需要请求/答复操作,即使操作签名返回void也是如此
- 使用out或ref参数要求操作具有基础响应消息,才可以将已修改的对象返回。如果操作是单向操作,则将在运行时引发InvalidOperationException异常。
[OperationContract] string GetData(int value,ref string strRef,out string strOut);