首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WCF 深度解析:訊息契約與基於訊息契約的序列化機制

     編輯:關於.NET

在 WCF 的四大核心契約——服務契約、資料契約、訊息契約與錯誤契約中,訊息契約扮演著極為關鍵的角色。它不關注服務操作的定義,也不僅限於資料結構的描述,而是專注於類型成員與 SOAP 訊息元素(Header/Body)之間的精確映射關係。本文將深入剖析訊息契約的定義、屬性配置,並透過實際程式碼示範 WCF 如何利用 MessageFormatter 完成方法呼叫與訊息的雙向轉換。

一、訊息契約的核心定義與三大屬性

訊息契約同樣定義在資料類型上,但它關注的是「資料在 SOAP 訊息中的具體呈現」。它由三個核心屬性構成:

  • MessageContractAttribute:標記於類別或結構,宣告此類型為訊息契約
  • MessageHeaderAttribute:標記於屬性或欄位,指定該成員對應 SOAP Header
  • MessageBodyMemberAttribute:標記於屬性或欄位,指定該成員對應 SOAP Body

1. MessageContractAttribute:控制訊息封裝與保護層級

此屬性提供兩類關鍵設定:

  • 保護層級:ProtectionLevel 與 HasProtectionLevel,對應 System.Net.Security.ProtectionLevel 列舉,可選 None、Sign、EncryptAndSign。
  • 主體封裝控制:IsWrapped、WrapperName、WrapperNamespace。預設 IsWrapped=true,會在 Body 成員外層包裹一個以類型命名的根節點(預設命名空間 https://tempuri.org/)。設為 false 則移除此包裹層;也可自訂根節點名稱與命名空間。

程式碼範例:

[MessageContract]
public class Customer
{
    [MessageHeader(Name = "CustomerNo", Namespace = "https://www.artech.com/")]
    public Guid ID { get; set; }

    [MessageHeader(Name = "CustomerName", Namespace = "https://www.artech.com/")]
    public string Name { get; set; }

    [MessageBodyMember(Namespace = "https://www.artech.com/")]
    public string Address { get; set; }
}

對應的 SOAP 訊息結構:

<s:Envelope ...>
  <s:Header>
    <h:CustomerName xmlns:h="https://www.artech.com/">Foo</h:CustomerName>
    <h:CustomerNo xmlns:h="https://www.artech.com/">2f62405b-...</h:CustomerNo>
  </s:Header>
  <s:Body>
    <Customer xmlns="https://tempuri.org/">
      <Address xmlns="https://www.artech.com/">...</Address>
    </Customer>
  </s:Body>
</s:Envelope>

若設定 [MessageContract(IsWrapped = false)],Body 將直接呈現 <Address>;若自訂 WrapperName="Cust"、WrapperNamespace="https://www.artech.com/",則根節點變為 <Cust xmlns="https://www.artech.com/">。

2. MessageHeaderAttribute:精細控制 SOAP Header 行為

繼承自 MessageContractMemberAttribute,擁有 Name、Namespace、ProtectionLevel 等基礎屬性,並新增三個 SOAP 專用屬性:

  • Actor / Role:指定處理該 Header 的目標 SOAP 節點。SOAP 1.1 用 actor,SOAP 1.2 用 role。
  • MustUnderstand:對應 mustUnderstand 屬性,強制接收端必須理解並處理。
  • Relay:對應 relay 屬性,決定是否轉發至下一個 SOAP 節點。

範例中設定 MustUnderstand=true、Relay=true、Actor="https://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver",生成的 Header 將攜帶 s:role、s:mustUnderstand="1"、s:relay="1"。SOAP 1.1 下則對應 actor="https://schemas.xmlsoap.org/soap/actor/ultimateReceiver"。

3. MessageBodyMemberAttribute:主體成員排序

僅擁有 Order 屬性,控制 Body 內成員的出現順序(預設字母序)。Header 成員無序,故 MessageHeaderAttribute 無 Order 屬性——這也是為何它不叫 MessageHeaderMemberAttribute 的原因。

二、實戰演示:MessageFormatter 如何將方法呼叫格式化為訊息

WCF 內部由 ClientMessageFormatter(客戶端)與 DispatchMessageFormatter(服務端)負責序列化/反序列化。預設使用 DataContractSerializerOperationFormatter。本節透過反射建立 Formatter,實際演示訊息契約物件如何轉換為 SOAP 訊息。

步驟一:定義訊息契約與資料契約

[MessageContract]
public class Order
{
    [MessageHeader(Namespace = "https://www.artech.com/")]
    public Guid OrderID { get; set; }

    [MessageHeader(Namespace = "https://www.artech.com/")]
    public DateTime Date { get; set; }

    [MessageBodyMember]
    public OrderDetails Details { get; set; }
}

[CollectionDataContract(ItemName = "Detail", Namespace = "https://www.artech.com/")]
public class OrderDetails : List<OrderDetail> { }

[DataContract(Namespace = "https://www.artech.com/")]
public class OrderDetail
{
    [DataMember] public Guid ProductID { get; set; }
    [DataMember] public int Quantity { get; set; }
}

步驟二:透過反射建立 DataContractSerializerOperationFormatter

因該類別為 internal,需用反射調用其建構函數:

static TFormatter CreateMessageFormatter<TFormatter, TContract>(string operationName)
{
    var contractDesc = ContractDescription.GetContract(typeof(TContract));
    var operationDesc = contractDesc.Operations.First(op => op.Name == operationName);
    var formatterType = Type.GetType("System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
    var ctor = formatterType.GetConstructor(new[] { typeof(OperationDescription), typeof(DataContractFormatAttribute), typeof(DataContractSerializerOperationBehavior) });
    return (TFormatter)ctor.Invoke(new object[] { operationDesc, new DataContractFormatAttribute(), null });
}

步驟三:序列化與反序列化實戰

static Message SerializeRequest<TContract>(MessageVersion version, string opName, params object[] values)
{
    var formatter = CreateMessageFormatter<IClientMessageFormatter, TContract>(opName);
    return formatter.SerializeRequest(version, values);
}

static void DeserializeRequest<TContract>(Message msg, string opName, object[] parameters)
{
    var formatter = CreateMessageFormatter<IDispatchMessageFormatter, TContract>(opName);
    formatter.DeserializeRequest(msg, parameters);
}

定義服務契約 IOrderManager,操作 ProcessOrder(Order order)。執行序列化後產生的 SOAP 訊息:

<s:Envelope ...>
  <s:Header>
    <a:Action ...>https://tempuri.org/IOrderManager/ProcessOrder</a:Action>
    <h:Date xmlns:h="https://www.artech.com/">2008-12-21T00:00:00+08:00</h:Date>
    <h:OrderID xmlns:h="https://www.artech.com/">cd94a6f0-...</h:OrderID>
  </s:Header>
  <s:Body>
    <Order xmlns="https://tempuri.org/">
      <Details xmlns:d4p1="https://www.artech.com/" ...>
        <d4p1:Detail><d4p1:ProductID>...</d4p1:ProductID><d4p1:Quantity>666</d4p1:Quantity></d4p1:Detail>
        <d4p1:Detail><d4p1:ProductID>...</d4p1:ProductID><d4p1:Quantity>999</d4p1:Quantity></d4p1:Detail>
      </Details>
    </Order>
  </s:Body>
</s:Envelope>

反序列化後成功還原物件,輸出:

Oder ID: cd94a6f0-7e21-4ace-83f7-2ddf061cfbbe
Date: 12/21/2008
Detail Count: 2

此實驗完整重現了 WCF 管線中「方法呼叫 ⇄ SOAP 訊息」的核心轉換邏輯,證明訊息契約賦予開發者對 SOAP 結構的完全掌控力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved