[MSDN]WCF(7)消息协定
说明:本内容来自微软的webcast,讲师为徐长龙。为了用手机阅读方便点,抄录存为txt。
本次课程内容包括
- 消息协定概述
- 在消息协定内部使用自定义类型
- 对消息部分进行签名和加密
- 控制标头和正文部分的名称和命名空间
- 控制是否包装SOAP正文部分
- SOAP 标头属性
- SOAP 正文部分的顺序
- 消息协定版本管理
消息协定概述
- 通常,定义消息的架构时使用数据协定就足够了
- 有时必须精确控制如何将类型映射到通过网络传输的 SOAP 消息。
- 对于这种情况,最常见的方案是插入自定义 SOAP 标头。另一种常见的方案是定义消息头和正文的安全属性,也就是说,确定是否对这些元素进行数字签名和加密。消息样式的操作可提供这种控制。
- 消息样式的操作最多具有一个参数和一个返回值,其中参数和返回值的类型都是消息类型;也就是说,这两种类型可直接序列化为制定的 SOAP 消息结构。
- 可以使用 MessageContractAttribute 标记的任何类型或 Message 类型
定义消息协定
- 若要为某一些类型定义消息洗协定(即定义该类型和 SOAP 信封之间的映射),请对该类型应用 MessageContractAttribute。然后对该类型中要成为 SOAP 标头的成员应用 MessageHeaderAttribute ,并对要成为消息的 SOAP 正文部分的成员应用 MessageBodyMemberAttribute。
- 可以对所有字段、属性和事件应用 MessageHeaderAttribute 和 MessageBodyMemberAttribute,而不管这些字段、属性和事件是公用的、私有的、受保护的还是内部的
在消息协定内部使用自定义类型
- 每个单独的消息头和消息正文部分均使用为消息所使用的服务协定选择的序列化引擎进行序列化(转换为 XML)。
- 默认序列化引擎 XmlFormatter 可以显式处理(通过具有System.Runtime.Serialization.DataContractAttribute)或隐式处理(通过作为基元类型而具有System.SerializableAttribute等)具有数据协定的任何类型。
在消息协定内部使用数组
- 可以采用两种方式在消息协定中使用重复元素的数组
- 直接在数组上使用 MessageHeaderAttribute 或 MessageBodyMemberAttribute
[MessageContract]
public class BankingDepostitLog
{
[MessageHeader] public int numRecords
[MessageHeader] public DepositRecord records[];
[MessageHeader] public int branchID;
}
- 使用 MessageHeaderArrayAttribute
[MessageContract]
public class BankingDepositLog
{
[MessageHeader] public int numRecords
[MessageHeaderArray] public DepositRecord records[];
[MessageHeader] public int branchID;
}
对消息部分进行签名和加密
- 消息协定可以指示消息头和/或正文是否应进行数字签名和加密
- 通过在 MessageHeaderAttribute 和 MessageBodyMemberAttribute 属性(attribute)上设置 System.ServiceModel.MessageContractMemberAttribute.ProtectionLevel 属性(property)来完成
- System.Net.Security.ProtectionLevel
- None (不加密或签名)
- Sign (仅数字签名)
- EncryptAndSign (加密并数字签名)
- 默认值为 None
- 若要让这些安全功能起作用,必须正确配置绑定和行为。如果在没有正确配置的情况小使用这些安全功能(例如,在不提供凭据的情况下试图对消息进行签名),则会在验证时引发异常
- 对于消息头,会分别为每个消息头确定其保护级别
- 对于消息正文部分,保护级别可理解为“最低保护级别”。无论包含几个正文部分,正文都只有一个保护级别。正文的保护级别由所有正文部分的最高 ProtectionLevel 属性设置确定。不过,您应该将每个正文部分的保护级别设置为实际要求的最低保护级别。
[MessageContract]
public class PatientRecord
{
[MessageHeader(ProtectionLevel=None)] public int recordID;
[MessageHeader(ProtectionLevel=Sign)] public string patientName;
[MessageHeader(ProtectionLevel=EncryptAndSign)] public string SSN;
[MessageBodyMember(ProtectionLevel=None)] public string comments;
[MessageBodyMember(ProtectionLevel=Sign)] public string diagnosis;
[MessageBodyMember(ProtectionLevel=EncryptAndSign)] public string medicalHistory;
}
- recordID 标头未受保护,patientName 为 signed,SSN进行了加密和签名。至少有一个正文部分 medicalHistory 已应用 EncryAndSign, 因此将对整个消息正文进行加密和签名,即使 comments 和 diagnosis 正文部分指定了较低的保护级别。
控制标头和正文部分的名称和命名空间
- 在消息协定的 SOAP 表示形式中,每个标头和正文部分都映射为一个具有名称和命名空间的 XML 元素
- 通过操纵
System.ServiceModel.MessageContractMemberAttribute.Name 和 System.ServiceMode.MessageContractMemberAttribute.Namespace (在 MessageHeaderAttribute 和 MessageBodyMemberAttribute 属性的父类上)可以更改这些默认值。
[MessageContract]
public class BankingTransaction
{
[MessageHeader] public Operation operation;
[MessageHeader(Namespace="htttp://schemas.contoso.com/auditing/2005")]
public bool IsAudited;
[MessageBodyMember(Name="transactionData")] public BankingTransactionData theData;
}
- IsAudited 标头位于代码中指定的命名空间中,表示 theData 成员的正文部分由名为 transactionData 的 XML 元素表示
控制是否包装 SOAP 正文部分
- 默认情况小,SOAP 正文部分会在包装元素内部进行序列化
[MessageContract]
public class HelloGreetingMessage
{
private string localGreeting;
[MessageBodyMember(Name="Salutations",Namespace="http://www.examples.com")]
public string Greeting
{
get{return localGreeting;}
set{localGreeting=value;}
}
}
/*
The following is the request message,edited for clarity.
<s:Envelope>
<s:Header>
<a:Action>http://GreetingMessage/Action</a:Action>
<a:To s:mustUnderstand="1"></a:To>
</s:Header>
<s:Body u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wsssecurrity-utility-1.0.xsd">
<HelloGreetinMessage xmlns="Microsoft.WCF.Documentation">
<Saltations xmlns="http://www.examples.com">Hello.</Salutations>
</HelloGreetingMessage>
</s:Envelope>
*/
- 若要取消包装元素,请将 IsWrapped 属性设置为 false。
- 若要控制包装元素的名称和命名空间,请使用 WrapperName 和 WrapperNamespace 属性
SOAP 标头属性
- SOAP 标准定义了下列可存在于标头上的属性:
- Actor/Role (在 SOAP1.1 中为 Actor, 在 SOAP1.2中为 Role)
- 指定要使用给定标头的节点的统一资源标识符(URI)。
- MustUnderstand
- 指定处理标头的节点是否必须理解该标头
- Relay
- 指定要将标头中继到下游节点
- WCF 不会对传入消息的这些属性执行任何处理(MustUnderstand除外)
- 静态方式将这些属性设置为任何需要的值
[MessageContract]
public class BankingTransaction
{
[MessageHeade] public Operation operation;
[MessageHeader(Actor="http://auditingservice.contoso.com",MustUnderstand=true)]
public bool IsAudited;
[MessageBodyMember] public BankingTransactionData theData;
}
- 通过代码以动态方式控制这些属性
[MessageContract]
public class BankingTransaction
{
[MessageHeader] public MessageHeader<bool> IsAudited;
[MessageHeader] public Operation operation;
[MessageBodyMember] public BankingTransactionData theData;
}
//... application code;
BankingTransaction bt = new BankingTransaction();
bt.IsAudited = new MessageHeader<bool>();
bt.IsAudited.Content=false; // Set IsAudited header value to "false"
bt.IsAudited.Actor="http://auditingservice.contoso.com";
bt.IsAudited.MustUnderstand=true;
- 如果同时使用动态和静态控制机制,则静态设置用作默认设置,但可以在以后使用动态机制重写
SOAP 正文部分的顺序
- 默认情况小,正文元素采用字母顺序
- 可以通过 System.ServiceModel.MessageBodyMemberAttribute.Order 属性进行控制
- 在消息协定中,基类型正文成员不排列在派生类型正文成员之前
[MessageContract]
public class BankingTransaction
{
[MessageHeader] public Operation operation;
[MessageBodyMember(Order=1)] public Account sourceAccount;
[MessageBodyMember(Order=2)] public Account targetAccount;
[MessageBodyMember(Order=3)] public int amount;
}
消息协定版本管理
- 更改消息协定
- 应用程序的新版本可能会向消息中添加额外的标头。在从新版本应用程序向旧版本应用程序发送消息是,系统必须处理额外的标头;同样,反方向操作时系统必须处理缺少的标头
- 下面的规则适用于标头的版本管理:
- WCF 不反对缺少标头,相应的成员将保留其默认值。
- WCF 还忽略意外的额外标头。此规则的一种例外情况是在传入的 SOAP 消息中,额外标头的 MustUnderstand 属性设置为 true。在这种情况下,由于存在一个无法处理单必须理解的标头,因此会引发异常。
- 消息正文具有类似的版本管理规则,即忽略缺少和附加的消息正文部分。
性能注意事项
- 每个消息头和消息正文部分相互独立地进行序列化。因此,可以为每个标头和正文部分重新声明相同的命名空间。为了提高性能,特别是对于消息在网络上的大小,请将多个标头和正文部分合并成一个标头或正文部分。
下面性能差,多个正文
[MessageContract]
public class BankingTransaction
{
[MessageHeader] public Operation operation;
[MessageBodyMember] public Account sourceAccount;
[MessageBodyMember] public Account targetAccount;
[MessageBodyMember] public int amount;
}
下面性能好,一个正文,被合并了。
[MessageContract]
public class BankingTransaction
{
[MessageHeader] public Operation operation;
[MessageBodyMember] public OperationDetails details;
}
[DataContract]
public class OperationDetails
{
[DataMember] public sourceAccount;
[DataMember] public Account targetAccount;
[DataMember] public int amount;
}