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

前言

 

  • 從咱們學習WCF以來,就一直強調WCF是基於消息的通訊機制。可是因爲WCF給咱們作了高級封裝,以致於咱們在使用WCF的時候不多瞭解到消息的內部機制。因爲WCF的架構的可擴展性,針對一些特殊狀況,WCF爲咱們提供了Message類來深度定製消息結構,以便咱們拓展WCF的通訊機制。
  • 在以前的文章中,咱們針對一些經常使用的WCF傳遞數據的方式進行了說明,好比數據協定和消息協定等。他們傳遞的數據最終都會轉化爲消息的實例。具體參照:

         WCF初探-16:WCF數據協定之基礎知識 html

         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();
            }
        }
    }
}
View Code

  因爲wsHttpBinding默認啓用安全機制,爲了簡便的觀察消息體結構,在本示例中設置wsHttpBinding的security mode="None",App.config的代碼以下:ide

<?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的代碼以下:this

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
相關文章
相關標籤/搜索