从零开始学WCF(6)---消息协定
-
消息协定概述
- 通常,定义消息的架构时使用数据协定就足够了
- 有时必须精确控制如何将类型映射到通过网络传输的SOAP消息
- 对于这种情况,最常见的方案是插入自定义SOAP标头。另一种常见方案是定义消息头和正文的安全属性,也就是说,确定是否对这些元素进行数字签名和加密。消息样式的操作可提供这种控制。
- 消息样式的操作最多具有一个参数和一个返回值,其中参数和返回值的类型都是消息类型,也就是说,这两种类型可直接序列化为指定的SOAP消息结构。
- 可以是用MessageContractAttribute标记的任何类型或Message类型。
定义消息协定
- 若要为某一类型定义消息协定(即定义该类型和SOAP信封之间的映射),请对该类型应用MessageContractAttribute。然后对该类型中要成为SOAP标头的成员应用MessageHeaderAttribute,并对要成为消息的SOAP正文部分的成员应用MessageBodyMemberAttribute。
- 可以对所有字段,属性和事件应用MessageHeaderAttribute和MessageBodyMemberAttribute,而不管这些字段,属性,和事件是共有的,私有的,受保护的还是内部的。
Server
namespace Microsoft.ServiceModel.Samples { // Define a service contract. [ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")] public interface ICalculator { [OperationContract(Action="http://test/MyMessage_action", ReplyAction="http://test/MyMessage_action")] MyMessage Calculate(MyMessage request); } // Custom message. [MessageContract] public class MyMessage { private string operation; private double n1; private double n2; private double result; //Constructor - create an empty message. public MyMessage() {} //Constructor - create a message and populate its members. public MyMessage(double n1, double n2, string operation, double result) { this.n1 = n1; this.n2 = n2; this.operation = operation; this.result = result; } //Constructor - create a message from another message. public MyMessage(MyMessage message) { this.n1 = message.n1; this.n2 = message.n2; this.operation = message.operation; this.result = message.result; } [MessageHeader] public string Operation { get { return operation; } set { operation = value; } } [MessageBodyMember] public double N1 { get { return n1; } set { n1 = value; } } [MessageBodyMember] public double N2 { get { return n2; } set { n2 = value; } } [MessageBodyMember] public double Result { get { return result; } set { result = value; } } [MessageHeader(MustUnderstand=true)] public string str; } // Service class which implements the service contract. public class CalculatorService : ICalculator { // Perform a calculation. public MyMessage Calculate(MyMessage request) { MyMessage response = new MyMessage(request); switch (request.Operation) { case "+": response.Result = request.N1 + request.N2; break; case "-": response.Result = request.N1 - request.N2; break; case "*": response.Result = request.N1 * request.N2; break; case "/": response.Result = request.N1 / request.N2; break; default: response.Result = 0.0D; break; } return response; } } }
Client
class Client { static void Main() { // Create a client with given client endpoint configuration CalculatorClient client = new CalculatorClient(); // Perform addition using a typed message. MyMessage request = new MyMessage(); request.N1 = 100D; request.N2 = 15.99D; request.Operation = "+"; MyMessage response = ((ICalculator)client).Calculate(request); Console.WriteLine("Add({0},{1}) = {2}", request.N1, request.N2, response.Result); // Perform subtraction using a typed message. request = new MyMessage(); request.N1 = 145D; request.N2 = 76.54D; request.Operation = "-"; response = ((ICalculator)client).Calculate(request); Console.WriteLine("Subtract({0},{1}) = {2}", request.N1, request.N2, response.Result); // Perform multiplication using a typed message. request = new MyMessage(); request.N1 = 9D; request.N2 = 81.25D; request.Operation = "*"; response = ((ICalculator)client).Calculate(request); Console.WriteLine("Multiply({0},{1}) = {2}", request.N1, request.N2, response.Result); // Perform multiplication using a typed message. request = new MyMessage(); request.N1 = 22D; request.N2 = 7D; request.Operation = "/"; response = ((ICalculator)client).Calculate(request); Console.WriteLine("Divide({0},{1}) = {2}", request.N1, request.N2, response.Result); //Closing the client gracefully closes the connection and cleans up resources client.Close(); Console.WriteLine(); Console.WriteLine("Press <ENTER> to terminate client."); Console.ReadLine(); } }
在消息协定内部使用自定义类型
- 每个单独的消息头和消息正文部分均使用为消息所使用的服务协定选择的序列化引擎进行序列化(转换为XML)
- 默认序列化引擎XMLFormatter可以显示处理(通过具有System.Runtime.Serialization.DataContractAttibute)或隐式处理(通过作为具有基元类型而具有System.SerializableAttribute等)具有数据协定的任何类型。
在消息协定内部使用数组
- 可以采用两种方式在消息协定中使用重复元素的数组
- 直接在数组上使用MessageHeaderAttribute或MessageHeaderArrayAttribute
对消息部分进行签名和加密
- 消息协定可以指示消息头和/或正文是否应进行数字签名和加密
- 通过在MessageHeaderAttribute和MessageBodyMemberAttribute属性(Attribute)上设置System.ServiceModel.MessageContractMemberAttribute.ProtectionLevel属性(property)来完成。
- System.Net.Security.ProtectioLevel
- None(不加密或签名)---默认值
- Sign(仅数字签名)
- EncryptAndSign(加密并数字签名)
- 若要让这些安全功能起作用,必须正确配置绑定和行为。如果在没有正确配置的情况下使用这些安全功能(例如:在不提供凭据的情况下试图对消息进行签名),则会在验证时发生异常。
- 对于消息头,会分别为每个消息头确定其保护级别。
- 对于消息正文部分,保护级别可理解为“最低保护级别”。无论包含几个正文部分,正文都只有一个保护级别。正文的保护级别由所有正文部分的最高ProtectionLevel属性设置确定。不过您应该将每个正文部分的保护级别设置为实际要求的最低保护级别。
控制标头和正文部分的名称和命名空间
- 在消息协定的SOAP表示形式中,每个标头和正文部分都映射为一个具有名称和命名空间的XML元素
- 通过操作MessageContractMemberAttribute.Name和MessaeContractMemberAttribute.NameSpace可以更改这些默认值。
SOAP标头属性
- SOAP标准定义了下列可存在于标头上的属性:
- Actor/Role(在SOAP1.1中为Actor,在SOAP1.2中为Role):指定要使用给定标头的节点统一资源标示符(URL)
- MustUnderstand:指定处理标头的节点是否必须理解该标头。
- Relay:指定要将标头中继到下游节点。
- WCF不会对传入消息的这些属性执行任何处理(MustUnderStand除外)
消息协定版本管理
- 更改消息协定
- 应用程序的新版本可能会向消息中添加额外的标头。在从新版本应用程序向旧版本应用程序发送消息时,系统必须处理额外的标头,同样,反向操作时系统必须处理缺少的标头
- 下面的规则适用于消息管理
- WCF不反对缺少标头,相应的成员将保留其默认值。
- WCF还忽略意外的额外标头,此规则的一种例外情况是在传入的SOAP消息中,额外标头的MustUnderstand属性设置为True。在这种情况下,由于存在一个无法处理但必须理解的标头,因此会引发异常。
- 消息正文具有类似的版本管理规则,即忽略缺少和附加的消息正文部分。
Server:
// Copyright (c) Microsoft Corporation. All Rights Reserved.
using System;
using System.ServiceModel.Channels;
using System.ServiceModel;
using System.Runtime.Serialization;
namespace Microsoft.ServiceModel.Samples
{
// Define a service contract.
[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")]
public interface ICalculator
{
[OperationContract(Action="http://test/MyMessage_action", ReplyAction="http://test/MyMessage_action")]
MyMessage Calculate(MyMessage request);
}
// Custom message.
[MessageContract]
public class MyMessage
{
private string operation;
private double n1;
private double n2;
private double result;
private ExtType et;
private string strNew;
//Constructor - create an empty message.
public MyMessage() {}
//Constructor - create a message and populate its members.
public MyMessage(double n1, double n2, string operation, double result)
{
this.n1 = n1;
this.n2 = n2;
this.operation = operation;
this.result = result;
}
//Constructor - create a message from another message.
public MyMessage(MyMessage message)
{
this.n1 = message.n1;
this.n2 = message.n2;
this.operation = message.operation;
this.result = message.result;
}
[MessageHeader]
public string Operation
{
get { return operation; }
set { operation = value; }
}
[MessageBodyMember]
public double N1
{
get { return n1; }
set { n1 = value; }
}
[MessageBodyMember]
public double N2
{
get { return n2; }
set { n2 = value; }
}
[MessageBodyMember]
public double Result
{
get { return result; }
set { result = value; }
}
[MessageBodyMember]
public ExtType ExtType
{
get { return et; }
set { et = value; }
}
[MessageHeader(MustUnderstand=true)]
public string NewString
{
get { return strNew; }
set { strNew = value; }
}
}
[DataContract]
public class ExtType
{
public ExtType() { }
public ExtType(string _name1,string _name2)
{
name1 = _name1;
name2 = _name2;
}
private string name1;
private string name2;
[DataMember]
public string Name1
{
get { return name1; }
set { name1 = value; }
}
[DataMember]
public string Name2
{
get { return name2; }
set { name2 = value; }
}
}
// Service class which implements the service contract.
public class CalculatorService : ICalculator
{
// Perform a calculation.
public MyMessage Calculate(MyMessage request)
{
MyMessage response = new MyMessage(request);
switch (request.Operation)
{
case "+":
response.Result = request.N1 + request.N2;
break;
case "-":
response.Result = request.N1 - request.N2;
break;
case "*":
response.Result = request.N1 * request.N2;
break;
case "/":
response.Result = request.N1 / request.N2;
break;
default:
response.Result = 0.0D;
break;
}
if (!request.ExtType.Equals(null))
response.ExtType = new ExtType(request.ExtType.Name1 + "11", request.ExtType.Name2 + "22");
return response;
}
}
}
Client:
// Copyright (c) Microsoft Corporation. All Rights Reserved.
using System;
using System.ServiceModel;
namespace Microsoft.ServiceModel.Samples
{
//The service contract is defined in generatedClient.cs, generated from the service by the svcutil tool.
//Client implementation code.
class Client
{
static void Main()
{
// Create a client with given client endpoint configuration
CalculatorClient client = new CalculatorClient();
// Perform addition using a typed message.
MyMessage request = new MyMessage();
request.N1 = 100D;
request.N2 = 15.99D;
request.Operation = "+";
ExtType et = new ExtType();
et.Name1 = "wang";
et.Name2 = "xiaoming";
request.ExtType = et;
request.str = "testString";
MyMessage response = ((ICalculator)client).Calculate(request);
Console.WriteLine("Add({0},{1}) = {2}", request.N1, request.N2, response.Result);
Console.WriteLine("after process {0},{1}", response.ExtType.Name1, response.ExtType.Name2);
// Perform subtraction using a typed message.
//request = new MyMessage();
//request.N1 = 145D;
//request.N2 = 76.54D;
//request.Operation = "-";
//response = ((ICalculator)client).Calculate(request);
//Console.WriteLine("Subtract({0},{1}) = {2}", request.N1, request.N2, response.Result);
//// Perform multiplication using a typed message.
//request = new MyMessage();
//request.N1 = 9D;
//request.N2 = 81.25D;
//request.Operation = "*";
//response = ((ICalculator)client).Calculate(request);
//Console.WriteLine("Multiply({0},{1}) = {2}", request.N1, request.N2, response.Result);
//// Perform multiplication using a typed message.
//request = new MyMessage();
//request.N1 = 22D;
//request.N2 = 7D;
//request.Operation = "/";
//response = ((ICalculator)client).Calculate(request);
//Console.WriteLine("Divide({0},{1}) = {2}", request.N1, request.N2, response.Result);
//Closing the client gracefully closes the connection and cleans up resources
client.Close();
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
}
}