消息契约描述了发送给一个服务以及从一个服务接收的SOAP消息的结构,并且允许你检测和控制SOAP消息头和消息体中大部分细节。而且数据契约能够让使用XML元数据定义(XSD)标准的系统之间互通,消息契约能够让任何通过SOAP通信的系统互通。
使用消息契约能够通过直接访问SOAP消息头和消息体提供对发送给一个服务以及从一个服务接收的SOAP消息的完全控制。这允许使用简单或复杂的类型来定 义SOAP部分的精确内容。就好比当你需要对数据序列化的完全控制时你可以从DataContractSerializer转换到 XmlSerializer,当你需要对SOAP消息完全控制时你可以从DataContracts转换到MessageContracts.
如果你想脱离操作签名来进行信息传输,那么通过SOAP消息头传输消息是有用的。比如,会话或者相关信息可以在消息头中传递,而不是向操作中添加额外的参 数或者在数据本身中添加作为属性的信息。另外一个例子是关于安全的,当你想实现一个自定义安全协议(绕过WS-Security)而且在自定义的SOAP 消息头中传递证书或证明。第三个例子,还是关于安全的,在你可能想签名,加密一些或者全部头信息的地方,对SOAP消息头进行签名和加密。这些所有的情况 都可以使用消息契约来处理。这个技术的不利因素是客户端与服务端必须手动从SOAP消息头添加并收集信息,而不是让数据契约和操作契约关联的序列化类来为 你实现。
[MessageContract] 属性定义了SOAP消息结构。这个属性并没有太多修饰,因为它的目的在于定义消息的边界而不是消息内容本身。唯一的修饰是关于多个消息体如何包裹成一个单 独的SOAP消息,确定了是否包括全部,如果是这样的话,还需要确定Warpper的名字和命名空间。
类型化的消息使用[MessageHeader]和[MessageBodyMember]属性来描述SOAP消息头和消息体的结构。客户端和服务端可以 使用序列化对象引用这个数据。可以与SOAP消息头关联额外的信息,比如名字和命名空间,不管是否消息会被延迟,或者哪一方是最后的端点及收件方。额外的 信息也可以与SOAP消息体关联,比如名字和命名空间。如果使用多个消息体,MessageContract可以定义这些部分的顺序。消息头和消息体都可 以有简单或者复杂的类型定义。
非类型化消息不使用任何属性来描述它们的内容。它完全使用运行时代码来让内容有意义。这对于直接使用XML消息信息集是非常有用的,在这种情况 下你可能想要把WCF丢在一边而去完全使用代码对文件对象模型编程(Document Object Model)。与服务操作一起使用的非类型化消息接收并返回实现XML信息集的消息类型。
类型化消息
列表2.28 显示了一个类型化消息契约,StockPrice.消息头包含了一个简单类型,DateTime,消息体包含了一个复杂类型,PriceDetails. PriceDetails 类必须是可序列化的,或者通过使用一个[DataContract]属性,或者像这里显示的那样,使用[Serializable]属性。这个例子仅有一 个消息头和一个消息体,但是可以有很多消息头和消息体。
你可能想定义很多消息头或者消息体如果它们会被客户端软件的不同层级使用。举个例子,一个层级需要关联SOAP消息头中的消息来为一个请求消息 生成Response. 同样另外一个层级可能想要确定消息以便于它可以适当的转发消息。在这种情况下,两个消息头有各自的目的,所以没有理由来将它们合并到一个结构中去。
注意服务操作接收并发送消息类型。当使用消息契约时,输入输出参数必须是标记了[MessageContract]属性的消息。更特殊的,操作必须精确的 包含一个输入参数而且必须返回确定唯一的结果,它们都是消息,因为从操作发送出的请求和接收到的回复消息将直接映射它们的SOAP表示。额外的,基于消息 的编程和基于参数的编程不能混为一谈,所以你不能把一个数据契约作为一个操作的输入参数而且让它返回一个消息契约作为结果,或者确定一个消息契约作为一个 操作的输入参数而且让它返回一个数据契约作为结果。你可以讲类型化消息和非类型化消息混用,但是不是消息契约和数据契约。在你从服务端生成WSDL时将消 息契约和数据契约混用会导致一个运行时错误。
为了使用[MessageContract]生成代表类型化消息的客户端代理代码,你需要检查添加服务引用的高级对话框中的总是生成消息契约选项,如图片2.9所示。
对应的,你可以在svcutil.exe 上使用/messageContract或者/mc 选项。这使得svcutil.exe生成带公共方法的代理来接受类型化消息,以便于客户端可以调用面向方法的方法。如果你使用不带/mc选项的 svcutil.exe方法或者你使用添加服务引用却没有选中总是生成消息契约,将会使用接收参数的公共方法生成代理,而且将会内部调用基于消息的操作。 在任何一种情况中,线上都会发送同样的XML消息。
列表2.28 定义一个类型化消息契约
namespace EssentialWCF
{
[Serializable]
public class PriceDetails
{
public string Ticker;
public double Amount;
}
[MessageContract]
public class StockPrice
{
[MessageHeader]
public DateTime CurrentTime;
[MessageBodyMember]
public PriceDetails Price;
}
[MessageContract]
public class StockPriceReq
{
[MessageBodyMember]
public string Ticker;
}
[ServiceContract]
public interface IStockService
{
[OperationContract]
StockPrice GetPrice(StockPriceReq req);
}
public class StockService : IStockService
{
#region IStockService Members
public StockPrice GetPrice(StockPriceReq req)
{
StockPrice resp = new StockPrice();
resp.Price = new PriceDetails();
resp.Price.Ticker = req.Ticker;
resp.Price.Amount = 94.85;
return resp;
}
#endregion
}
}
列表2.29 显示了当SOAP消息从服务端返回到客户端时传输的XML消息。注意[MessageHeader]元素,CurrentTime,在SOAP消息头中而[MessageBodyMember]元素,Price,在SOAP消息体中。
列表2.29 生成使用类型化消息契约的SOAP Response
非类型化消息
在一些场景中,设计阶段你可能不知道在客户端和服务端传输的消息结构。例如,信息将会被编入到消息本身,比如在设计阶段确定的路由信息和服务操 作。或者在客户端和服务端的一个软件(或硬件)层次来操作SOAP消息并期待特殊的数据格式。对这些情况来说,非类型化操作契约将会非常有用。
非类型化操作契约允许客户端和服务端几乎可以在SOAP消息体中传输任何内容,只要这个内容可以被用来通信的绑定栈编码。消息的内容对WSDL来说是绝对 透明的淫威没有XSD来定义数据。客户端和服务端使用System.ServiceModel.Channels.Message类来创建,读取和写入消 息。
列表2.30 显示了一个使用message类型作为输入输出的操作契约。注意消息的GetBody方法是一个普通的把消息体 反序列化成类型的方法。这个方法使用一个XMLReader 来读取SOAP消息 的<body>元素。因为它使用一个XML读取器,<body>尽可以被读取一次;如果你想多于一次的读 取<body>元素,你应该使用消息的CreateBufferCopy方法。SOAP回复端操作的名字是请求操作的名字在后面加 上”Response”后缀。这可以通过[OperationContract]属性的(ReplyAction=)来重载。
Message类由很多创建,读取和写入消息内容的方法。客户端负责在消息发送给服务端之前创建消息而服务端负责创建一个返回消息。在发送消息之前,内容 必须被放到消息体中。这可以通过CreateMessage,WriteMessage或者WriteBody方法来实现。
列表2.30 定义并且实现非类型化消息契约
[ServiceContract(Namespace="http://EssentialWCF")]
public class StockService2
{
[OperationContract]
private Message GetPrice(Message req)
{
string ticker = req.GetBody<String>();
Message resp = Message.CreateMessage(req.Version,req.Headers.Action + "Response", ticker + "|" + "94.85");
return resp;
}
}
客户端代码类似于服务端代码,使用CreateMessage创建消息,使用合适的版本来匹配绑定,然后使用GetBody方法来读取服务端返 回的结果。注意CreateMessage方法使用三个参数:版本,操作和字符串消息。当创建消息时,消息版本必须与用来与服务端通信的绑定兼容,通过信 道中的MessageVersion属性来定义。操作,比如http://EssentialWCF/StockService/GetPrice, 被SOAP和WCF基础用来转发消息给服务端适当的操作。列表2.31显示了与列表2.30中服务代码进行通信的客户端代码。
列表2.31 使用一个非类型化消息契约的客户端初始化通信
列表2.32 显示了由服务端返回给列表2.31中的请求的SOAP消息。注意SOAP消息头中的操作在后面有一个”Response”, SOAP消息体是一个非XML格式的字符串。
列表2.32 使用一个非类型化消息契约的SOAP回复消息
使用非类型化消息的SOAP消息头
无论你是否正在使用类型化的或者非类型化的消息,你可能想在SOAP消息头中传输消息而不是SOAP消息体。一个通用的需求是与消息一起传送会话状态或者上下文信息。所以,除了创建额外的包装消息,SOAP消息头是一个方便的而且易于理解的传输消息的结构。
如果你在使用类型化消息,WCF通过列表2.28中描述的[MessageHeader]属性来显式支持它。如果使用一个非类型化消息,然而,你需要显式添加一个非类型化消息头。
列表2.33 显示了实现了一个非类型化消息操作然后从消息头中读取数据的服务契约。注意消息头中的数据,timeZone,被一行代码访问。
列表2.33 使用一个非类型化消息契约的访问消息头的服务
[ServiceContract]
public class StockService2
{
[OperationContract]
private Message GetPrice(Message req)
{
string timeZone = OperationContext.Current.IncomingMessageHeaders.GetHeader<String>("TimeZone", "http://EssentialWCF/");
string ticker = req.GetBody<String>();
Message resp = Message.CreateMessage(req.Version, req.Headers.Action + "Response",timeZone + "|" + ticker + "|" + "94.85");
return resp;
}
}
列表2.34 描述了一个客户端如何添加一个SOAP消息头到将要发送给服务端的非类型化消息。首先使用CreateMessage创建一条消息,使用构造函数把数据放 到消息中。然后一个类型化MessageHeader被创建;在这种情况下它是一个字符串,数据通过构造函数放到消息头中。下一步,一个非类型化 MessageHeader被从类型化MessageHeader中创建,最后非类型化MessageHeader被添加到要发送给服务的消息中。
列表2.34 客户端向一个非类型化消息中插入消息头
列表2.35 显示了由客户端代码生成的SOAP消息。注意插入到合适命名空间消息头的TimeZone元素。
列表2.35 客户端插入消息头到一个非类型化消息中
============
转载自