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

WCF 實戰教學 (2):Contract 契約深度解析 — ServiceContract、OperationContract、DataContract、DataMember、ServiceKnownType

     編輯:關於.NET

前言

Contract (契約) 是 WCF 服務與用戶端之間的「合約」,定義了「提供什麼操作」、「交換什麼資料」、「如何序列化」。掌握五大契約屬性 (ServiceContract、OperationContract、DataContract、DataMember、ServiceKnownType) 是設計健壯、可版本演進、跨平台互通服務的基石。本篇透過「人員管理」實例,完整示範契約定義、繼承多型序列化、WSDL 對應關係與用戶端呼叫。

五大契約屬性速覽

屬性作用層級核心功能關鍵屬性
ServiceContract介面/類別定義服務邊界、命名空間、名稱、回呼契約Namespace, Name, ConfigurationName, CallbackContract
OperationContract方法定義單一作業、名稱、單向、非同步模式Name, IsOneWay, AsyncPattern, ProtectionLevel
DataContract類別/結構宣告型別參與序列化、命名空間、名稱Namespace, Name, IsReference
DataMember屬性/欄位標示序列化成員、順序、必要性、預設值處理Name, Order, IsRequired, EmitDefaultValue
ServiceKnownType服務/作業/資料契約宣告多型衍生型別,讓序列化器識別子類別Type, Method, DeclaringType

完整程式碼範例

1. 資料契約 — 基底與衍生類別 (多型序列化核心)

using System.Runtime.Serialization;

namespace WCF.ServiceLib.Contract
{
    /// 基底人員類別
    [DataContract(Name = "PersonModel", Namespace = "https://webabcd.cnblogs.com")]
    public class Person
    {
        [DataMember(Name = "PersonName", Order = 0, IsRequired = false, EmitDefaultValue = false)]
        public string Name { get; set; }

        [DataMember(Name = "PersonAge", Order = 1, IsRequired = false, EmitDefaultValue = false)]
        public int Age { get; set; }
    }

    /// 學生類別,繼承 Person
    [DataContract(Name = "StudentModel", Namespace = "https://webabcd.cnblogs.com")]
    public class Student : Person
    {
        [DataMember(Name = "School", Order = 0, IsRequired = false, EmitDefaultValue = false)]
        public string School { get; set; }
    }
}

2. 服務契約 — 宣告 ServiceKnownType 解決多型序列化

using System.ServiceModel;
using System.Runtime.Serialization;

namespace WCF.ServiceLib.Contract
{
    /// 
    /// 服務契約:指定命名空間、名稱、配置名稱
    /// ServiceKnownType:告知序列化器 Student 為 Person 的已知衍生型別
    /// 
    [ServiceContract(
        Namespace = "https://webabcd.cnblogs.com",
        Name = "IPersonManager",
        ConfigurationName = "ConfigurationNameTest")]
    [ServiceKnownType(typeof(Student))]  // 關鍵:缺少此行,傳入 Student 會序列化失敗
    public interface IPersonManager
    {
        /// 
        /// OperationContract.Name 對應 WSDL 中的 operation name
        /// MessageParameter.Name 對應訊息參數名稱
        /// 
        [OperationContract(Name = "GetPersonName")]
        string GetName([MessageParameter(Name = "person")] Person p);
    }
}

3. 服務實作

namespace WCF.ServiceLib.Contract
{
    public class PersonManager : IPersonManager
    {
        public string GetName(Person p) => $"Name: {p.Name}";
    }
}

4. 宿主與設定檔 (IIS 裝載)

<!-- PersonManager.svc -->
<%@ ServiceHost Language="C#" Debug="true" Service="WCF.ServiceLib.Contract.PersonManager" %>
<!-- Web.config -->
<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="WCF.ServiceLib.Contract.PersonManager" behaviorConfiguration="ContractBehavior">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="ConfigurationNameTest" /> <!-- 對應 ServiceContract.ConfigurationName -->
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ContractBehavior">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

5. 用戶端呼叫 (ASP.NET Web Form)

// PersonManager.aspx.cs
public partial class Contract_PersonManager : System.Web.UI.Page
{
    protected void btnGetName_Click(object sender, EventArgs e)
    {
        var proxy = new Contract.PersonManagerClient();
        // 建立 Student 實例 (衍生自 Person)
        var student = new Contract.StudentModel 
        { 
            PersonName = txtName.Text,
            School = "資訊工程學系" 
        };
        
        // 呼叫時 Proxy 會自動處理多型序列化 (因服務端已宣告 ServiceKnownType)
        var result = proxy.GetPersonName(student);
        ClientScript.RegisterStartupScript(GetType(), "msg", $"alert('{result}');", true);
        proxy.Close();
    }
}

用戶端 Web.config

<client>
  <endpoint address="https://localhost:3502/ServiceHost/Contract/PersonManager.svc"
            binding="basicHttpBinding"
            contract="Contract.IPersonManager" />
</client>

關鍵概念深度解析

1. DataMember.Order — 控制 XML 序列化順序

WCF 使用 DataContractSerializer,預設依字母序排序。透過 Order 可強制指定順序,基底類別成員 Order 必須小於衍生類別,否則反序列化會失敗或資料錯位。

[DataMember(Order = 0)] public string Name { get; set; }  // 基底先序列化
[DataMember(Order = 1)] public int Age { get; set; }

// 衍生類別從 0 重新開始,但實際 XML 會接在基底之後
[DataMember(Order = 0)] public string School { get; set; }

2. ServiceKnownType 的三種宣告方式

  1. 屬性裝飾:[ServiceKnownType(typeof(Student))] 在服務契約上 (最常用)
  2. 設定檔:<serviceKnownType type="Namespace.Student, Assembly" /> 在 `` 區段
  3. 動態方法:[ServiceKnownType("GetKnownTypes", typeof(Helper))] 由靜態方法回傳 `IEnumerable`

3. KnownType vs ServiceKnownType

  • KnownTypeAttribute:裝飾在 資料契約類別 上,影響該類別所有使用情境
  • ServiceKnownTypeAttribute:裝飾在 服務契約/作業 上,僅影響特定服務範圍
  • 建議:資料契約庫共用時用 KnownType,服務專屬多型用 ServiceKnownType

4. IsRequired 與 EmitDefaultValue 的版本演進策略

屬性預設值建議設定說明
IsRequiredfalsefalsetrue 會強制用戶端必須提供,版本升級新增欄位會破壞相容
EmitDefaultValuetruefalsefalse 可減少傳輸體積 (不傳輸 null/0/false),且利於版本容錯

常見錯誤與除錯

  1. 「Type 'Student' with data contract name 'StudentModel' is not expected」:缺少 ServiceKnownType(typeof(Student)) 或 KnownType。
  2. 序列化順序錯誤導致資料錯位:檢查基底與衍生類別 Order 設定是否遞增。
  3. WSDL 中操作名稱不符預期:確認 OperationContract(Name="...") 與 MessageParameter(Name="...") 設定。
  4. 命名空間不一致:ServiceContract.Namespace、DataContract.Namespace、用戶端 Proxy 命名空間需一致,否則會產生多餘的 `xmlns` 前綴。

最佳實務檢核清單

  • ☐ 所有資料契約類別標記 [DataContract],成員標記 [DataMember]
  • ☐ 明確指定 Name、Namespace、Order,避免依賴預設值
  • ☐ 多型情境完整宣告 KnownType / ServiceKnownType
  • ☐ IsRequired = false、EmitDefaultValue = false 利於版本演進
  • ☐ 服務契約、作業契約命名語意清晰,對應業務領域術語
  • ☐ 共用組件 (Shared Assembly) 方式部署契約,避免用戶端重複產生 Proxy
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved