WCF学习心得------(七)消息协定
第七章 消息协定
7.1 消息协定概述
通常情况下,在定义消息的架构时只使用数据协定就足够,但是有时需要精确控制如何将类型映射到通过网络传输的SOAP消息。对于这种情况,通常解决方案是插入自定义的SOAP标头。
此外还可以定义消息头和正文的安全属性,通过确定是否对这些元素进行数字签名和加密,消息样式的操作可提供这种控制。消息样式的操作最多具有一个参数和一个返回值,其中参数和返回值的类型都是消息类型,即这两种类型可直接序列化为指定的SOAP消息结构。
消息协定可以是用MessageContractAttribute标记的任何类型或Message类型。
如下所示:
[OperationContract]
BankingTransactionResponse Process(BankingTransaction bt);
[OperationContract]
Void Store(BankingTransaction bt);
以上示例的两个消息样式的操作均为有效的操作。
那么该如何定义消息协定呢?请看以下示例:
[MessageContract]
Public class BankingTranscation
{
[MessageHeader]
Public Operation operation;
[MessageHeader]
Public DateTime transactionDate;
[MessageBodyMember]
Private Account sourceAccount;
}
通过上边的示例我们可以看出,若要为某一类型定义消息协定即定义该类型和SOAP信封之间的映射,请对该类型的应用MessageContractAttribute属性,然后对类型中药成为SOAP标头的成员使用MessageHeaderAttribute属性,对成为消息的SOAP正文部分成员使用MessageBodyMemberAttribute属性即可。
此外还需要注意的是,可以对所有的字段/属性/事件应用MessageHeaerAttribute和MessageBodyMemeberAttribute,且不受字段/属性/事件的访问限制。
演示示例:
- 新建一个WCF应用服务程序。修改后的目录结构如下:

- 在ICalculatorService.cs文件中,定义访问接口,消息协定类MyMessage
具体代码如下:
[ServiceContract(Namespace="Http://Microsoft.WCF.Message")]
public interface ICalculatorService
{
[OperationContract(Action = "Http://Test/MyMessage action", ReplyAction = "Http://Test/MyMessage action")]
MyMessage Calculator(MyMessage request);
}
[MessageContract]
public class MyMessage
{
private string operation;
private double n1;
private double n2;
private double result;
public MyMessage(){}
public MyMessage(string operation ,double n1,double n2,double result)
{
this.n1=n1;
this.n2=n2;
this.result=result;
this.operation=operation;
}
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;
2. 在CalculatorService.svc文件中,定义CalculatorService类,该类实现上一步中定义的ICalculatorService接口。具体代码如下:
public class CalculatorService : ICalculatorService
{
public MyMessage Calculator(MyMessage request)
{
MyMessage myMessage = new MyMessage(request);
switch (myMessage.Operation)
{
case "+":
myMessage.Result = myMessage.N1 + myMessage.N2;
break;
case "-":
myMessage.Result = myMessage.N1 - myMessage.N2;
break;
case "*":
myMessage.Result = myMessage.N1 * myMessage.N2;
break;
case "/":
myMessage.Result = myMessage.N1 / myMessage.N2;
break;
default:
myMessage.Result = 0.0D;
break;
}
return myMessage; }
}
3. 对配置文件进行一些配置设置,代码如下:
在<System.serviceModel>节点下新增如下内容:
<services>
<service name="Message.CalculatorService" behaviorConfiguration="Message.CalculatorServiceBehaviors">
<endpoint binding="wsHttpBinding"
contract="Message.ICalculatorService"/>
<endpoint binding="mexHttpBinding"
contract="IMetadataExchange"
address="mex"/>
</service>
</services>
4. 将上一步生成文件进行发布,部署,然后利用C盘Windows SDKs文件夹下的Svcutil.exe工具生成客户端调用类和App.Config配置文件。(关于Svcutil.exe工具的使用请参考上一篇文章)
5. 新建控制台应用程序,作为调用服务的客户端,并添加上一步生产的客户端调用类文件和配置文件。项目目录如下:

6. 在Client项目的Program.cs文件中,调用服务。代码如下:
static void Main(string[] args)
{
CalculatorServiceClient calculator = new CalculatorServiceClient();
MyMessage request = new MyMessage("+","Olive", 1.2, 3.4, 0);
MyMessage respons = ((ICalculatorService)calculator).Calculator(request);
Console.WriteLine("N1={0},N2={1},Operation is:{2},Result={3}", respons.N1, respons.N2, respons.Operation,respons.Result);
Console.WriteLine("Calculator is Over!");
Console.Read();
}
7. 演示结果如下:

总结:该例主要用来演示如何创建消息协定,并调用消息服务。
7.2 在消息协定内部使用自定义类型
每个单独的消息头和消息正文部分均使用为消息所使用的服务协定选择的序列化引擎进行序列化,默认的序列化引擎XmlFormatter可以显示的处理或隐式出来具有消息协定的任何类型。
此外,还可以在消息协定内部使用数组,可以通过直接在数组上使用MessageHeaderAttribute或MessageBodyMemberAttribute来修饰数组类型的字段或属性等,当然也可以通过MessageHeaderArrayAttribute来修饰。
示例如下:
[MessageContract]
Public class MyMessage
{
[MessageHeader]
Public int numRecords;
[MessageHeader]
Public int[] Counts;
MessageHeaderArray]
Public int [] Records;
}
这两种不同的修饰方法,产生的结果如下:
<MyMessage>
<numRecords>116</numRecords>
<Counts>
<int>1</int>
<int>2</int>
</Counts>
<Records>3</Records>
<Record>4</Records>
</MyMessage>
通过生成的结果可知,使用MessageHeaderAttribute属性修饰的字段比通过MessageHeaderArrayAttribte属性修饰的字段多了一个消息信封<Counts>但是这两种修饰方式产生的效果是一样的。
演示示例:
这个演示示例我们直接在上一个示例中添加一些东西就可以了。
- 在上一个示例中的ICalculatorService.cs文件中添加如下数据协定
[DataContract]
public class ExtType
{
public ExtType() { }
public ExtType(string _name1,string _name2)
{
this.name1=_name1;
this.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;}
}
2. 然后在该文件的消息协定类MyMessage中添加如下属性:
[MessageBodyMember]
public ExtType ExtType
{
get { return et; }
set { et = value; }
}
3. 重新生成发布,然后又Svcutil.exe工具重新生成客户端类和配置文件,并在客户端项目中将原有的Client.cs文件和App.config文件删除,添加刚刚生成的新的文件。
4. 在客户端项目中的Program.cs添加如下代码:
static void Main(string[] args)
{
CalculatorServiceClient calculator = new CalculatorServiceClient();
MyMessage request = new MyMessage();
request.N1 = 1.2;
request.N2 = 3.4;
request.Result = 0;
request.Operation = "+";
request.NewString = "Spartacus";
Message1.ExtType et = new Message1.ExtType();
et.Name2 = "Only";
et.Name1 = "For";
request.ExtType = et;
MyMessage respons = ((ICalculatorService)calculator).Calculate(request);
Console.WriteLine("N1={0},N2={1},Operation is:{2},Result={3}", respons.N1, respons.N2, respons.Operation, respons.Result);
Console.WriteLine("The ExtType's Name1 is : {0} ,and Name2 is : {1}", respons.ExtType.Name1, respons.ExtType.Name2);
Console.WriteLine("Calculator is Over!");
Console.Read();
}
5. 编译运行,结果如下:

7.3 对消息部分进行签名和加密
消息协定可以通过在MessageHeaderAttribute和MessageBodyMemberAttribute属性上设置MessageContractMemberAttribute.ProtectionLevel属性的不同的值,来指示消息头或正文是否应该进行数字签名和加密。
System.Net.Security.ProtectionLevel有三个枚举值,分别为:
None:不加密或签名
Sign:仅数字签名
EncryptAndSign:加密并数字签名
除了通过设置ProtectionLevel属性值来进行签名和加密以外,还有以下几点需要注意:
- 必须正确配置绑定和行为,如果没有正确配置就使用这些安全功能,则验证时会引发异常。
- 对于消息头会分别为每个消息头确定其保护级别。
- 对象消息正文部分,保护级别为“最低保护级别”,正文的保护级别由所有正文的最高ProtectionLevel属性值确定,在设置时应设置为最低保护级别。
演示示例:
[MessageContract]
Public class PatientRecord
{
[MessageHeader(ProtectionLevel=None)]
Public int recordID;
[MessageHeader(ProtectionLevel=Sign)]
Public string patientName;
[MessageBodyMember(ProtectionLevel=None)]
Public string comments;
[MessageBodyMember(ProtectionLevel=EncryptAndSign)]
Public string SSN;
}
注释:
上边的示例代码中,我们可以得知,recordID标头未受保护,patientName标头未signed,因为消息正文部分SSN已经应用EncryptAndSign,所以所有的消息正文部分都进行了加密和签名。
7.4 控制标头和正文部分的名称和命名空间
在消息协定的SOAP表示形式中,每个标头和正文部分都映射为一个具有名称和命名空间的Xml元素,如果想修改默认的名称可以通过System.ServiceModel.MessageContractMemberAttribute.Name和NameSpace属性实现。
示例如下:
[MessageContract]
Public class BankingTransaction
{
[MessageHeader(NameSpace=”http://www.cnblogs.com/Olive116”)]
Public bool IsGoodMan;
[MessageBodyMember(Name=”Author”)]
Public string writer;
}
注释:IsGoodMan 标头位于代码中指定的命名空间中,正文部分writer由名为Author的Xml元素表示。
这里的Name和NameSpace属性和数据协定中的Name和Namespace属性有些相似的地方。
7.5 控制是否包装SOAP正文部分
默认的情况下,SOAP的正文部分会在包装元素内部进行序列化,如果想要取消包装,可以通过将IsWrapped属性设置为false来实现,若要控制包装元素的名称和命名空间,可以使用WrapperName和WrapperNamespace属性来实现。
7.6 SOAP标头属性
SOAP标准定义了下列可能存在于标头上的属性:
l Actor/Role(SOAP 1.1中为Actor,SOAP 1.2中为Role)
--指定使用给定标头节点的统一资源标识符(URL)
l MustUnderstand
--指定处理标头的节点是否必须理解该标头
l Relay
--指定要将标头中继到下游节点
除了MustUnderstand节点以外,WCF不会对传入消息的这些属性作任何的处理。
SOAP标头属性的设置有以下三种方式:
- 通过静态方式设置标头属性
示例如下:
[MessageContract]
Public class BankingTransaction
{
[MessageHeader(Actor=”Http://www.cnblogs.com/Olive116”,MustUnderstand=true)]
Public bool IsAudited;
}
2. 通过动态方式设置标头属性
[MessageContract]
Public class BankingTransaction
{
[MessageHeader]
Public bool IsAudited;
}
……
……
BankingTransaction bt=new BankingTransaction();
bt.IsAudited=true;
bt.IsAudited.Actor=”Http://www.cnblogs.com/Olive116”;
bt.IsAudited.MustUnderstand=true;
3. 通过动静态结合的方式设置标头属性,此时,标头属性默认为静态设置的值,但是可以在以后使用动态机制进行重写.
示例如下:
[MessageContract]
Public class BankingTransaction
{
[MessageHeader(MustUnderstand=false)]
Public bool IsAudited;
}
…… ……
BankingTransaction bt=new BankingTransaction();
bt.IsAudited=true;
bt.IsAudited.Actor=”Http://www.cnblogs.com/Olive116”;
bt.IsAudited.MustUnderstand=true;
7.7 SOAP正文部分顺序
默认情况下SOAP正文采用字母顺序,但是可以通过System.ServiceModel.MessageBodyMemberAttribute.Order属性进行设置,
这里需要特别注意的是以数据协定不同,在消息协定中,基类型正文成员不排列在派生类正文成员之前。
示例如下:
[MessageContract]
Public class BankingTransaction
{
[MessageHeader]
Public bool IsAudited;
[MessageBodyMember(Order=1)]
Public string MemberOrder1;
[MessageBodyMember(Order=2)]
Public string MemberOrder2;
[MessageBodyMember(Order=3)]
Public string MemberOrder3;
}
生成消息正文的顺序分别为MemberOrder1,MemberOrder2,MemberOrder3.
7.8 消息协定版本管理
应用程序的新版本可能会向消息中添加额外的标头,在新版本应用程序向旧版本应用程序发送消息时,系统必须处理额外的标头,同样反向操作时系统必须处理缺少的标头。
标头版本管理的一些规则:
l WCF不反对缺少标头,相应的成员将保留其默认值
l WCF会忽略意外的额外标头,但是额外标头中如果有MustUnderstand属性且值为true,在这种情况下,由于存在无法处理但必须理解的标头,因此会引发异常。
消息正文的版本管理规则跟标头的版本管理规则大体上是类似的,即忽略缺少和附加的消息正文部分。
此外还有一些消息的性能注意事项,一般来讲每个消息头和正文部分相互独立的进行序列化,因此可以为每个标头和正文重新声明相同的命名空间,为提高性能,可以将多个标头和正文部分合并成一个标头或正文部分。
示例如下:
[MessageContract]
Public class BankingTransaction
{
[MessageHeader]
Public Operation operation;
[MessageBodyMember]
Public string BodyMember1;
[MessageBodyMember]
Public string BodyMember2;
[MessageBodyMember]
Public string BodyMember3;
}
这样的性能是很差的,我们可以对此进行稍加修饰对消息正文进行合并,如下:
[MessageContract]
Public class BankingTransaction
{
[MessageHeader]
Public Operation operation;
[MessageBodyMember]
Public BodyMemberDetails details;
}
[DataContract]
Public class BodyMemberDetails
{
[DataMember]
Public string BodyMember1;
[DataMember]
Public string BodyMember2;
[DataMember]
Public string BodyMember3;
}
WCF学习心得------(七)消息协定的更多相关文章
- WCF学习心得----(四)服务承载
WCF学习心得----(四)服务承载 这一章节花费了好长的时间才整理个大概,主要原因是初次接触这个东西,在做练习实践的过程中,遇到了很多的问题,有些问题到目前还没有得以解决.所以在这一章节中,有一个承 ...
- WCF学习心得----(三)服务承载
WCF学习心得----(三)服务承载 这一章节花费了好长的时间才整理个大概,主要原因是初次接触这个东西,在做练习实践的过程中,遇到了很多的问题,有些问题到目前还没有得以解决.所以在这一章节中,有一个承 ...
- WCF学习心得----(五)生成客户端
WCF学习心得----(五)生成客户端 1. 通过Svcutil.exe工具直接生成客户端 1.1 将服务承载于IIS上 1.1.1 在IIS中新建网站,所示效果如下图: 1.1.2 ...
- WCF学习心得------(六)数据协定
--前言 最近各种事忙的把之前的WCF学习给耽误了一些,今天抽时间把之前的学习内容给总结了一下,因为知识点比较细碎没有做太多的练习示例,只是对其中关键的知识点做了总结,希望可以对大家有所帮助. 第六章 ...
- WCF学习心得------(三)配置服务
配置服务 配置服务概述 在设计和实现服务协定后,便可以进行服务的配置.在其中可以定义和自定义如何向客户段公开服务,包括指定可以找到服务的地址,服务用于发送和接受消息的传输和消息编码,以及服务需要的安全 ...
- WCF 学习总结5 -- 消息拦截实现用户名验证(转)
WCF建立在基于消息的通信这一概念基础上.通过方法调用(Method Call)形式体现的服务访问需要转化成具体的消息,并通过相应的编码(Encoding)才能通过传输通道发送到服务端:服务操作执行的 ...
- WCF学习心得------(二)设计和实现服务协定
设计和实现服务协定 创建服务协定—WCF术语 消息 消息是一个独立的数据单元,它可能由几个部分组成,包括消息正文和消息头. 服务 服务是一个构造,它公开一个或多个终结点,其中每个终结点都公开一个或多个 ...
- WCF学习笔记之消息交换模式
在WCF通信中,有三种消息交换模式,OneWay(单向模式), Request/Reponse(请求回复模式), Duplex(双工通信模式)这三种通信方式.下面对这三种消息交换模式进行讲解. 1. ...
- WCF学习心得
之前很经常听说WCF,不过没有怎么接触过,直到最近才真正使用到WCF,虽然也只是皮毛而已,在此也做个记录总结吧. 下图是我使用WCF的练手项目,由于是使用VS2010直接创建的WCF服务应用程序,VS ...
随机推荐
- Word embedding
https://en.wikipedia.org/wiki/Word_embedding 简言之,就是讲词汇或短语映射成实值特征向量.
- 前端学习资源(js)
JavaScript JavaScript | MDN JavaScript 秘密花园 JavaScript 标准参考教程(alpha) 给 JavaScript 初心者的 ES2015 实战 Col ...
- 《JS高程》数据类型学习笔记
认认真真看完了<JavaScript高级程序设计>第3章的基本概念,原来一直不明白的知识点都在这里面啊...T_T...基础真的很重要,很重要,很重要... 现在终于明白了读书的技巧,书读 ...
- day3-Python集合、函数、文件操作,python包的概念
本节大纲: 1 python程序由包(package).模块(module)和函数组成.包是由一系列模块组成的集合.模块是处理某一类问题的函数和类的集合. 2 包就是一个完成特定任务的工具箱. 3 包 ...
- sticky bit
• Sticky Bit这个Sticky Bit当前只针对目录有效,对文件没有效果.SBit对目录的作用是:“在具有SBit的目录下,用户若在该目录下具有w及x权限,则当用户在该目录下建立文件或目录时 ...
- UI学习笔记---第五天
target...action设计模式 代理设计模式 手势识别器 target...action设计模式 耦合是衡量一个程序写的好坏的标准之一,耦合是衡量模块与模块之间关联程度的指标 &quo ...
- 最大连续子序列和问题(Maximum Consecutive Subsequence Sum)
该算法的定义是:给出一个int序列,元素有正有负,找出其中的最大连续子序列的和. 例如:-2,11,-4,13,-5-2,:最大和为20(11,-4, 13). 怎么考虑这个问题呢? 要充分利用,连续 ...
- Windows7下QT5开发环境搭建 分类: QT开发 2015-03-09 23:44 65人阅读 评论(0) 收藏
Windows7下QT开法环境常见搭配方法有两种. 第一种是:QT Creator+QT SDK: 第二种是:VS+qt-vs-addin+QT SDK: 以上两种均可,所需文件见QT社区,QT下载地 ...
- <C Traps and Pitfalls>笔记
//------------------------------------------------------------------------------ 2.1 理解函数的声明: 编写一个独立 ...
- HTML 5 参考手册
HTML5是用于取代1999年所制定的 HTML 4.01 和 XHTML 1.0 标准的 HTML (标准通用标记语言下的一个应用)标准版本:现在仍处于发展阶段,但大部分浏览器已经支持某些 HTML ...