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

前言

  • 从我们学习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 = 20;
            user.ID = 1;
            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文件格式如下:

  

  示例代码如下:

        public Message GetDataXml()
        {
            string path = Environment.CurrentDirectory.Substring(0, 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类示例

  • 解决方案如下:

  

  • 工程结构说明如下:
  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 = 20;
            user.ID = 1;
            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(0, 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>1</ID>
  <Name>20</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();
        }
    }
}
  • 运行空消息代码,显示结果如下:

  

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

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

  

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

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

  

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

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

  

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

总结

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

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

WCF初探-23:WCF中使用Message类(下)

前言 在上一篇WCF中使用Message类(上)中,文章介绍了WCF中使用Message类的基本知识和怎样创建消息,本文是承接上一篇文章,如果想要更好的阅读本文,请先阅读上一篇文章.在这篇文章中,我将介绍怎样来操作消息. 从WCF中使用Message类(上)中,我们知道了消息的基本结构,针对不同的情况,我们对消息进行了创建.在创建消息后,我们还可以对消息进行写入.读取.复制等操作,以便我们在不同的任务环境下更好的运用消息传输机制. 通过Message类提取消息正文的几种方式 Message 类支

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

前言 在上一篇WCF序列化和反序列化中,文章介绍了WCF序列化和反序列化的机制,虽然WCF针对序列化提供了默认的DataContractSerializer序列化引擎,但是WCF还支持其他的序列化引擎,那就是XmlSerializer序列化引擎.本文将详细介绍XmlSerializer类在WCF中具体的使用方式. XmlSerializer类概述 XmlSerializer 类不是 WCF 的专用类.ASP.NET Web 服务同样使用该类作为序列化引擎. XmlSerializer 类支持的类

WCF初探-28:WCF中的并发

理解WCF中的并发机制 在对WCF并发机制进行理解时,必须对WCF初探-27:WCF中的实例化进行理解,因为WCF中的并发特点是伴随着服务实例上下文实现的.WCF的实例上下文模型可以通过InstanceContext的属性来进行设置,WCF中的并发就是指一个实例上下文处理请求消息的能力,当需要在一个实例上下文中处理多个消息请求时就会产生并发.所以当InstanceContextMode的值为PerSession或Single的时候就会产生并发的情况,这时我们可以通过设置ConcurrencyMo

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

理解WCF中的实例化机制 “实例化”是指对用户定义的服务对象以及与其相关的 InstanceContext 对象的生存期的控制.也就是说我们的客户端程序在调用服务端方法时,需要实例化一个服务端代理类对象,实例化就是对这个对象的生命周期的管理(比如:代理服务对象的创建,对象调用服务端方法后需要对其进行的回收处理). 实例化行为(使用 System.ServiceModel.ServiceBehaviorAttribute.InstanceContextMode 属性进行设置)控制如何创建 Inst

WCF初探-26:WCF中的会话

理解WCF中的会话机制 在WCF应用程序中,会话将一组消息相互关联,从而形成对话.会话”是在两个终结点之间发送的所有消息的一种相互关系.当某个服务协定指定它需要会话时,该协定会指定所有调用(即,支持调用的基础消息交换)必须是同一对话的一部分.如果某个协定指定它允许使用会话但不要求使用会话,则客户端可以进行连接,并选择建立会话或不建立会话.如果会话结束,然后在同一个通道上发送消息,将会引发异常. WCF中的会话机制通过设置服务协定(ServiceContract)上的SessionMode的枚举值

从零开始学WCF(七)Message类

Message类是WCF的基本类 客户端与服务之间的所有通信最终都会产生要进行发送和接收的Message实例 通常不会与Message类直接进行交互.相反,您需要使用WCF服务模型构造(如数据协定,消息协定和操作协定)来描述传入消息和传出消息. 在以下情况下可能需要使用Message类 需要一种替代方式来创建传出的消息内容(例如,从磁盘上的文件直接创建消息),而不是序列化.net framework对象. 需要一种替代方式来使传入的消息内容(例如,需要将XSLT转换应用于原始XML内容),而不是

WCF基础之Message类

客户端和服务端的通信都是通过接收和发送的Message实例建立起来的,大多数情况我们通过服务协定.数据协定和消息协定来构造传入和传出消息的. 一般什么时候使用Message类呢?不需要将消息序列化或者反序列化为.NET对象,无论消息内容如何,都进行常规处理.比如:读取磁盘中的文件并作为消息发送等等. 操作协定中的Message类的使用和消息协定相似,都只能有至多一个参数,而且必须为Message类或者消息协定,返回值必须是void.Message类或者消息协定. 创建消息有很多种,其重载如下:

WCF初探-15:WCF操作协定

前言: 在前面的文章中,我们定义服务协定时,在它的操作方法上都会加上OperationContract特性,此特性属于OperationContractAttribute 类,将OperationContract应用于方法,以指示该方法实现作为服务协定(由 ServiceContractAttribute 属性指定)一部分的服务操作.OperationContractAttribute 属性声明方法是服务协定中的操作. 只有具有 OperationContractAttribute 属性的方法可

WCF初探-2:手动实现WCF程序

1.前言 上一篇,我们通过VS自带的模板引擎自动生成了一个wcf程序,接下来我们将手动实现一个wcf程序.由于应用程序开发中一般都会涉及到大量的增删改查业务,所以这个程序将简单演示如何在wcf中构建简单的增删改查服务.我们知道WCF是一组通讯服务框架,我将解决方案按大范围划分为服务端,客户端通过服务寄宿程序产生的代理来调用服务端的公开给客户端消费的方法.总个解决方案由五个项目工程: Service:定义服务契约接口和实现服务契约,此项目类型为类库项目 Common:通用层定义数据访问的帮助类,此