WCF初探-22:WCF中使用Message类(上)

时间:2023-01-20 10:24:36

前言

  • 从我们学习WCF以来,就一直强调WCF是基于消息的通信机制。但是由于WCF给我们做了高级封装,以至于我们在使用WCF的时候很少了解到消息的内部机制。由于WCF的架构的可扩展性,针对一些特殊情况,WCF为我们提供了Message类来深度定制消息结构,以便我们拓展WCF的通信机制。
  • 在之前的文章中,我们针对一些常用的WCF传递数据的方式进行了说明,比如数据协定和消息协定等。他们传递的数据最终都会转化为消息的实例。具体参照:

         WCF初探-16:WCF数据协定之基础知识 

         WCF初探-19:WCF消息协定

Message类概述

  • Message 类是 WCF的基本类。客户端与服务之间的所有通信最终都会产生要进行发送和接收的 Message 实例。我们通常不会与 Message 类直接进行交互。相反,我们需要使用 WCF 服务模型构造(如数据协定、消息协定和操作协定)来描述传入消息和传出消息。但是,在某些高级方案中,可以直接使用 Message 类进行编程。Message 类提供一种方法,使网络中的发送方和接收方之间可对任意信息进行通信。使用它可以传递信息、建议或要求执行一系列操作或请求数据。
  • Message 类用作消息的抽象表示形式,但其设计在很大程度上依赖于 SOAP 消息。Message 包含三个主要信息部分:消息正文、消息头和消息属性。
  1. 消息正文:消息正文用于表示消息的实际数据负载。消息正文始终表示为 XML Infoset。这并不意味着在 WCF 中创建或接收的所有消息都必须为 XML 格式。这要由通道堆栈来确定如何解释消息正文。通道堆栈可能会将消息正文作为 XML 发出、将转换为某种其他格式(比如Json),甚至可能会完全忽略该消息正文(比如空消息)。当然,对于 WCF 提供的大多数绑定,消息正文在 SOAP 信封的正文部分中都表示为 XML 内容。
  2. 消息头:消息可以包含标头。标头在逻辑上由与名称、命名空间和几个其他属性相关联的 XML Infoset 组成。在 Message 上使用 Headers 属性可以访问消息头。每个标头由一个 MessageHeader 类表示。消息头通常在使用配置的通道堆栈处理 SOAP 消息时映射到 SOAP 消息头。
  3. 消息属性:消息可以包含属性。属性是任何与字符串名称关联的 .NET Framework 对象。通过 Message 上的 Properties 属性可以访问这些属性。与消息正文和消息头不同(通常分别映射到 SOAP 正文和 SOAP 标头),消息属性通常不与消息一起发送或接收。消息属性主要作为一种通信机制,用于在通道堆栈中的各个通道之间以及通道堆栈和服务模块之间传递有关消息的数据。
  • 总之,Message 是一种通用的数据容器,但其设计严格遵循 SOAP 协议中消息的设计方式。就像 SOAP 中一样,消息同时具有消息正文和标头。消息正文包含实际负载数据,而标头包含其他已命名的数据容器。用于读取和写入消息正文与标头的规则是不同的,例如,标头总是在内存中进行缓冲,并且可以按任意顺序访问任意次,而正文仅能读取一次且可以进行流式处理。通常,使用 SOAP 时,消息正文被映射到 SOAP 正文,而消息头被映射到 SOAP 标头。

Message类的使用场景及限制

  • 在以下情况下可能需要使用 Message 类:
  1. 需要一种替代方式来创建传出的消息内容(例如,从磁盘上的文件直接创建消息),而不是序列化 .NET Framework 对象。
  2. 需要一种替代方式来使用传入的消息内容(例如,需要将 XSLT 转换应用于原始 XML 内容),而不是反序列化为 .NET Framework 对象。
  3. 无论消息内容怎样都需要使用常规方式来处理消息(例如,在生成路由器、负载平衡器或发布-订阅系统时对消息进行路由或转发)。
  • 可以将 Message 类用作操作的输入参数和/或操作的返回值。只要在操作中的任何位置使用了 Message,就必须遵从以下限制:
  1. 操作不能具有任何 out 或 ref 参数。
  2. 不能有一个以上的 input 参数。如果该参数存在,其类型必须为 Message 或消息协定。
  3. 返回类型必须为 void、Message 或消息协定类型。

使用Message类创建消息

 

  • 创建基本消息
  1. 所有 CreateMessage 重载都采用一个类型为 MessageVersion 的版本参数,该参数指示要用于消息的 SOAP 和 WS-Addressing 版本。如果要使用与传入消息相同的协议版本,则可以使用 OperationContext 实例(从 Current 属性获取)上的 IncomingMessageVersion 属性。大多数 CreateMessage 重载还具有一个字符串参数,该参数指示要用于消息的 SOAP 操作。可以将版本设置为 None 以禁用 SOAP 信封生成;消息仅包含正文。示例代码如下: 
        public Message GetDataEmpty()
{
MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver, "http://tempuri.org/IUserInfo/GetDataEmptyResponse");
}
  • 从对象创建消息
  1. 另一种重载采用一个附加的 Object 参数;此重载所创建的消息的正文是给定对象的序列化表示。对象可以是DataContract或者MessageContract。示例代码如下:
        public Message GetDataObject()
{
User user = new User();
user.Name = "JACK";
user.Age = ;
user.ID = ;
user.Nationality = "CHINA"; MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver, "http://tempuri.org/IUserInfo/GetDataObjectResponse", user);
}
  • 从 XML 读取器创建消息
  1. CreateMessage 重载采用一个 XmlReader 或一个 XmlDictionaryReader 而不是对象作为正文。在这种情况下,消息的正文会包含从传递的 XML 读取器中进行读取而产生的 XML。比如我们有一个名称为test.xml文件存放着User对象。Xml文件格式如下:

  WCF初探-22:WCF中使用Message类(上)

  示例代码如下:

        public Message GetDataXml()
{
string path = Environment.CurrentDirectory.Substring(, Environment.CurrentDirectory.IndexOf("bin")) + "test.xml";
FileStream stream = new FileStream(path, FileMode.Open);
XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver, "http://tempuri.org/IUserInfo/GetDataXmlResponse", xdr);
}
  • 创建错误消息
  1. 可以使用某些 CreateMessage 重载创建 SOAP 错误消息。其中一个最基本的重载采用一个用于描述错误的 MessageFault 对象作为参数。其他重载是为方便起见而提供的。第一个这样的重载采用一个 FaultCode 和一个原因字符串作为参数,并使用 MessageFault.CreateFault(该方法使用这些信息)创建一个 MessageFault。另一个重载采用一个详细信息对象作为参数,并将该对象与错误代码和原因一起传递给 CreateFault。示例代码如下:
        public Message GetDataFault()
{
FaultException<FaultMessage> fe = new FaultException<FaultMessage>
(new FaultMessage("错误时间:" + System.DateTime.Now.ToString(), "输入的字符串大于10个字符"));
MessageFault fault = fe.CreateMessageFault();
MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver, fault, "http://tempuri.org/IUserInfo/GetDataFaultResponse");
}

WCF中使用Message类示例

  • 解决方案如下:

  WCF初探-22:WCF中使用Message类(上)

  • 工程结构说明如下:
  1. service:类库程序,WCF服务端程序。IUserInfo.cs定义了四个返回值为Message的操作协定,GetDataEmpty()返回空消息、GetDataObject()返回从对象创建的消息、GetDataXml()返回从XML文件读取创建的消息、GetDataFault()返回创建的错误消息。定了类型为User的消息协定(用于传输消息体数据)和类型为FaultMessage的错误协定(用于传输消息的错误数据)。IUserInfo.cs的代码如下:
using System.ServiceModel;
using System.Runtime.Serialization;
using System.ServiceModel.Channels; namespace Service
{
[ServiceContract]
public interface IUserInfo
{
[OperationContract]
Message GetDataEmpty(); [OperationContract]
Message GetDataObject(); [OperationContract]
Message GetDataXml(); [OperationContract]
Message GetDataFault();
} [MessageContract]
public class User
{
[MessageBodyMember]
public int ID { get; set; }
[MessageBodyMember]
public string Name { get; set; }
[MessageBodyMember]
public int Age { get; set; }
[MessageBodyMember]
public string Nationality { get; set; }
} [DataContract]
public class FaultMessage
{
private string _errorTime;
private string _errorMessage; [DataMember]
public string ErrorTime
{
get { return this._errorTime; }
set { this._errorTime = value; }
} [DataMember]
public string ErrorMessage
{
get { return this._errorMessage; }
set { this._errorMessage = value; }
} public FaultMessage(string time, string message)
{
this._errorTime = time;
this._errorMessage = message;
}
}
}

  

  UserInfo.cs的代码如下:

using System.ServiceModel.Channels;
using System.ServiceModel;
using System.IO;
using System;
using System.Xml; namespace Service
{
public class UserInfo:IUserInfo
{
public Message GetDataEmpty()
{
MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver, "http://tempuri.org/IUserInfo/GetDataEmptyResponse");
} public Message GetDataObject()
{
User user = new User();
user.Name = "JACK";
user.Age = ;
user.ID = ;
user.Nationality = "CHINA"; MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver, "http://tempuri.org/IUserInfo/GetDataObjectResponse", user);
} public Message GetDataXml()
{
string path = Environment.CurrentDirectory.Substring(, Environment.CurrentDirectory.IndexOf("bin")) + "test.xml";
FileStream stream = new FileStream(path, FileMode.Open);
XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver, "http://tempuri.org/IUserInfo/GetDataXmlResponse", xdr);
} public Message GetDataFault()
{
FaultException<FaultMessage> fe = new FaultException<FaultMessage>
(new FaultMessage("错误时间:" + System.DateTime.Now.ToString(), "输入的字符串大于10个字符"));
MessageFault fault = fe.CreateMessageFault();
MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
return Message.CreateMessage(ver, fault, "http://tempuri.org/IUserInfo/GetDataFaultResponse");
}
}
}

  2. Host:控制台应用程序,服务承载程序。添加对程序集Service的引用,完成以下代码,寄宿服务。Program.cs代码如下:

  
using System;
using System.ServiceModel;
using Service; namespace Host
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(UserInfo)))
{
host.Opened += delegate { Console.WriteLine("服务已经启动,按任意键终止!"); };
host.Open();
Console.Read();
}
}
}
}

  由于wsHttpBinding默认启用安全机制,为了简便的观察消息体结构,在本示例中设置wsHttpBinding的security mode="None",App.config的代码如下:

<?xml version="1.0"?>
<configuration>
<system.serviceModel> <services>
<service name="Service.UserInfo" behaviorConfiguration="mexBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:1234/UserInfo/"/>
</baseAddresses>
</host>
     <endpoint address="" binding="wsHttpBinding" contract="Service.IUserInfo" bindingConfiguration="bindConfig"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services> <behaviors>
<serviceBehaviors>
<behavior name="mexBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors> <bindings>
<wsHttpBinding>
<binding name="bindConfig">
<security mode="None" />
</binding>
</wsHttpBinding>
</bindings>
</system.serviceModel>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>

  test.xml文件的内容如下:

<?xml version="1.0" encoding="utf-8" ?>
<User>
<ID></ID>
<Name></Name>
<Age>JACK</Age>
<Nationality>CHINA</Nationality>
</User>

  运行Host.exe程序,成功寄宿服务后,我们通过svcutil.exe工具生成客户端代理类和客户端的配置文件svcutil.exe是一个命令行工具,

  位于路径C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin下,我们可以通过命令行运行该工具生成客户端代理类

  • 在运行中输入cmd打开命令行,输入 cd C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin
  • 输入svcutil.exe /out:f:\UserInfoClient.cs /config:f:\App.config http://localhost:1234/UserInfo

  3. Client:控制台应用程序,客户端调用程序。将生成的UserInfoClient.cs和App.config复制到Client的工程目录下,完成客户端调用代码。Program.cs的代码如下:

using System;
using System.ServiceModel.Channels; namespace Client
{
class Program
{
static void Main(string[] args)
{
UserInfoClient proxy = new UserInfoClient(); //显示创建基本消息(空消息)
//Message message = proxy.GetDataEmpty();
//Console.WriteLine(message.ToString()); //显示从对象创建消息
//Message message = proxy.GetDataObject();
//Console.WriteLine(message.ToString()); //显示从XML读取器创建消息
//Message message = proxy.GetDataXml();
//Console.WriteLine(message.ToString()); //显示创建错误消息
Message message = proxy.GetDataFault();
Console.WriteLine(message.ToString()); Console.Read();
}
}
}
  • 运行空消息代码,显示结果如下:

  WCF初探-22:WCF中使用Message类(上)

  从运行结果可以看出一个基本的消息结构为<s:Envelope></ s:Envelope >,其中包含消息头<s:Header>< s:Header >和消息正文<s:Body></ s:Body >

  • 运行从对象创建的消息,显示结果如下:

  WCF初探-22:WCF中使用Message类(上)

  从运行结果可以看出类型为User的MessageContract转化为了消息的正文部分

  • 运行从XML读取器创建的消息,显示结果如下:

  WCF初探-22:WCF中使用Message类(上)

  从运行结果可以看出读取文件test.xml的内容转化为了消息正文部分

  • 运行创建的错误消息,显示结果如下:

  WCF初探-22:WCF中使用Message类(上)

  从运行结果可以看出类型为FaultMessage的DataContract转化为了消息的正文部分,并且嵌套在了错误消息结构<s:Fault>下的<s:Detail>中。

总结

  • 通过以上示例,我们了解到了消息的基本结构,这个不再是操作WCF测试客户端,而是我们操作Message基类来描述消息结构。
  • 尽管上面出现创建消息的几种方式,但我们知道消息都是通过Message的CreateMessage方法的重载来制定和创建消息,并且消息版本(MessageVersion)和消息Action必须指定。Action一般为服务协定的命名空间+服务协定借口名称+操作协定+Response

WCF初探-22:WCF中使用Message类(上)的更多相关文章

  1. WCF初探-23:WCF中使用Message类&lpar;下&rpar;

    前言 在上一篇WCF中使用Message类(上)中,文章介绍了WCF中使用Message类的基本知识和怎样创建消息,本文是承接上一篇文章,如果想要更好的阅读本文,请先阅读上一篇文章.在这篇文章中,我将 ...

  2. 在Eclipse中设置Java类上面的注释&lpar;包含作者、日期等&rpar;

    希望在Eclipse中,让Java类上面的注释像下面这样,改如何设置呢? 在Eclipse中,点击菜单中的Window-->Preferences,打开如下窗口:

  3. wcf中的Message类

    客户端->服务端—>客户端 客户端代码: using (new OperationContextScope(client.InnerChannel))            {       ...

  4. WCF初探文章列表

    WCF初探-1:认识WCF WCF初探-6:WCF服务配置 WCF初探-2:手动实现WCF程序 WCF初探-7:WCF服务配置工具使用 WCF初探-3:WCF消息交换模式之单向模式 WCF初探-8:W ...

  5. WCF初探-25:WCF中使用XmlSerializer类

    前言 在上一篇WCF序列化和反序列化中,文章介绍了WCF序列化和反序列化的机制,虽然WCF针对序列化提供了默认的DataContractSerializer序列化引擎,但是WCF还支持其他的序列化引擎 ...

  6. WCF初探-28:WCF中的并发

    理解WCF中的并发机制 在对WCF并发机制进行理解时,必须对WCF初探-27:WCF中的实例化进行理解,因为WCF中的并发特点是伴随着服务实例上下文实现的.WCF的实例上下文模型可以通过Instanc ...

  7. WCF初探-27:WCF中的实例化

    理解WCF中的实例化机制 “实例化”是指对用户定义的服务对象以及与其相关的 InstanceContext 对象的生存期的控制.也就是说我们的客户端程序在调用服务端方法时,需要实例化一个服务端代理类对 ...

  8. WCF初探-26:WCF中的会话

    理解WCF中的会话机制 在WCF应用程序中,会话将一组消息相互关联,从而形成对话.会话”是在两个终结点之间发送的所有消息的一种相互关系.当某个服务协定指定它需要会话时,该协定会指定所有调用(即,支持调 ...

  9. WCF基础之Message类

    客户端和服务端的通信都是通过接收和发送的Message实例建立起来的,大多数情况我们通过服务协定.数据协定和消息协定来构造传入和传出消息的. 一般什么时候使用Message类呢?不需要将消息序列化或者 ...

随机推荐

  1. 使用JDBC进行批处理

    在实际的项目开发中,有时候需要向数据库发送一批SQL语句执行,这时应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率. JDBC实现批处理有两种方式:statement和pr ...

  2. Apache提示You don&&num;39&semi;t have permission to access &sol; on this server问题解决

    测试时遇到将一本地目录设置为一apache的虚拟主机,在httpd-vhosts.conf文件中进行简单设置,然后在hosts文件中将访问地址指向本地,启动apache,进行访问,却出现了You do ...

  3. Hdu3714-Error Curves&lpar;三分&rpar;

    Josephina is a clever girl and addicted to Machine Learning recently. She pays much attention to a m ...

  4. 浅谈UE4引擎

    首先要说的是,游戏开发是一项高度复杂的代码开发工作,编程语言只是最基本的知识,它涉及的内容还有计算机图形学.3D数学.物理学等复杂的学科.但是若需要学完这么多知识才能开发游戏,恐怕许多人都已经断气了, ...

  5. jiVMware的网络配置Linux

    需求需要配置VMware的虚拟Linux的ip以达到本地可以访问,而且虚拟机Linux可以上网: 第一方案:选择桥接模式 思路:因为桥接可以,使得虚拟机Linux把本地当做一座桥一样连接到路由器,然后 ...

  6. commandjs、AMD、CMD之间的故事

    commandjs:同步加载,只运行一次,后面使用第一次加载时运行的结果(存于缓存中),用于服务器 AMD:define(id ?,dependencies ?,factory) 异步加载,用于浏览器 ...

  7. 2&colon; Eclipse反编译工具Jad及插件JadClipse配置

    Jad是一个Java的一个反编译工具,是用命令行执行,和通常JDK自带的java,javac命令是一样的.不过因为是控制台运行,所以用起来不太方便.不过幸好有一个eclipse的插件JadClipse ...

  8. SharePoint 2019 离线安装准备工具

    前言 最近需要安装SharePoint 2019,然而,服务器并没有网络,所以需要离线安装. 离线安装的步骤很简单,就是把所以得准备工具下载下来,然后修改好命令,使用命令安装即可. 准备工具下载路径 ...

  9. Vue获取DOM元素的属性值

    项目中需要做一个小弹层,如下图: 我需要知道点击元素距离顶部的值,再计算弹层的top值,如下图: 在vue中如何获取到DOM元素距离窗口顶部的值呢? 1.通过$event获取 html: <di ...

  10. VS 中常用的一些快捷键

    一.代码自动对齐 CTRL+K+F     二.撤销/反撤销 1.撤销---使用组合键“Ctrl+Z”进行撤销操作 2.反撤销---使用组合键“Ctrl+Y”进行反撤销操作 三.调用智能提示 使用组合 ...