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 ...
随机推荐
- Google Analytics SEO 实时 网站 访问量 统计
/*************************************************************************** * Google Analytics SEO ...
- HDU 1004 Let the Balloon Rise(AC代码)
#include <stdio.h> #include <string.h> ][]; ]={}; int main() { int n,i,j,k,max,loc; ){ m ...
- EASYRECOVERY_3.3.29包含注册机、都教授数据恢复含注册码
用EASYRECOVERY恢复过U盘,和回收站永久清空的东西.效果很好.但疑惑doc,docx,后者恢复效果特别好 都教授没用过,别人买的,没用,很贵. 每次用,都找不着,还要重新淘宝买.这次分享给大 ...
- Qt简介
一.Qt与Qt Creator简介 Qt是一个跨平台应用程序和 UI 开发框架.使用 Qt 您只需一次性开发应用程序,无须重新编写源代码,便可跨不同桌面和嵌入式操作系统部署这些应用程序. ...
- 解决:导入第三方jar包后,仍然出现java.lang.NoClassDefFoundError的错误
最近,在运行某个Android工程的时候,一直抛出java.lang.NoClassDefFoundError异常. 按照异常所给出的信息,应该是程序使用到的第三方jar包出了问题. 但是,这些第三方 ...
- Java-->将txt文件的所有行反转
--> 这里和上次代码不同,对同一文件进行操作,所以要用到一个第三方容器来存储数据 package com.dragon.java.filereverseline; import java.io ...
- 334. Increasing Triplet Subsequence
Given an unsorted array return whether an increasing subsequence of length 3 exists or not in the ar ...
- init/main.c
/* * linux/init/main.c * * Copyright (C) 1991, 1992 Linus Torvalds */ #include <stdarg.h> #inc ...
- java03实验截图
- leetcode 124. Binary Tree Maximum Path Sum ----- java
Given a binary tree, find the maximum path sum. For this problem, a path is defined as any sequence ...