Programming WCF Services翻譯筆記(五)

來源:互聯網
上載者:User

本書的第3章主要講解了有關資料契約的知識。“從抽象層面看,WCF能夠託管CLR類型(介面和類)並將它們公開為服務,也能夠以本地CLR介面和類的方式使用服務。WCF服務的操作接收和返回諸如int和string的CLR類型,WCF用戶端則傳遞和處理返回的CLR類型。然而,CLR類型卻屬於.NET的特定技術。由於面向服務的一個核心原則就是在跨越服務邊界時,服務不能夠暴露它們的實現技術。因此,不管用戶端採用了何種技術,它都能夠與服務互動。顯然,這就意味著WCF不允許在跨越服務邊界時公開CLR資料類型。我們需要找到一種辦法,實現CLR資料類型與標準的與平台無關的表示形式之間的轉換。這樣的表示形式就是基於XML的樣式或資訊集(Infoset)。此外,服務需要一種正式的方法聲明兩者之間的轉換。這個方法就是本章所要介紹的主題——資料契約。本章的第一部分介紹了資料契約啟用類型封送(Type Marshaling)與轉換的方法,以及如何通過基礎架構處理類的層級與資料契約的版本控制。第二部分則介紹了如何將不同的.NET類型,例如枚舉、委託、資料表以及集合,作為資料契約使用。”

資料契約是服務之間傳遞的資料。由於必須支援跨進程,乃至於跨機器的傳遞,WCF必須對資料進行特殊的處理,否則無法實現資料的傳遞。在.NET Remoting與Web Service中,對資料的處理方式通常是利用序列化的方法,WCF同樣沿襲了這一做法,但為了更好的體現面向服務的特質,又特別引入了資料契約(DataContract)。此外,WCF還引入了訊息契約(MessageContract),但本書沒有介紹。

WCF的序列化使用了.NET平台自身支援的序列化機制,因此這裡不再重複。

.NET提供的序列化機制雖然足以應付SOA的要求,但仍然存在許多不足之處。本書總結了Serializable的缺陷:
“Serializable所指代的涵義是類型的所有成員都是可序列化的,這些成員是組成類型資料樣式的一部分。然而,更好的方式是能夠提供一種明確參與(Opt-In)途徑,只有那些契約的開發人員明確包含的成員才應該放到資料契約中。Serializable特性強制要求資料類型是可序列化的,從而使得類型可以被用作契約操作的參數,但它卻無法實作類別型的服務特性(具有成為WCF巨集指令引數的能力)與序列化能力之間的職責分離。Serializalbe特性不支援類型名和成員名的別名,也無法將一個新類型映射為預定義的資料契約。由於Serializable特性可以直接操作成員欄位,使得封裝了欄位訪問的屬性形同虛設。訪問欄位的最好辦法是通過屬性添加它們的值,而Serializable卻破壞了屬性的封裝性。最後,Serializable特性並沒有直接支援版本控制(Versioning),而版本控制的資訊卻是格式器期望擷取的。無疑,它導致了版本控制的處理變得舉步維艱。”

WCF提供的資料契約DataContract基本上解決了以上的問題。通常,DataContract必須與DataMember結合使用。只有應用了DataMember特性的屬性才被公開到中繼資料中。雖然DataMember特性也可以應用到對象的欄位上,但WCF並不推薦這樣做,原因與類的設計原則相同。

資料契約與服務契約相似,資料成員或資料契約的訪問限定與WCF之間並沒有因果關係。資料契約完全可以包含私人資料成員等內部類型:
[DataContract]
struct Contact
{
   [DataMember]
   string m_FirstName;

   [DataMember]
   string m_LastName;
}

即使DataMember特性被直接應用到欄位上,在匯入的用戶端定義仍然會以屬性來表示。如下的資料契約定義:
[DataContract]
struct Contact
{
   [DataMember]
   public string FirstName;

   [DataMember]
   public string LastName;
}
匯入的用戶端定義為:
[DataContract]
public partial struct Contact
{
   string FirstNameField;
   string LastNameField;

   [DataMember]
   public string FirstName
   {
      get
      {
         return FirstNameField;
      }
      set
      {
         FirstNameField = value;
      }
   }

   [DataMember]
   public string LastName
   {
      get
      {
         return LastNameField;
      }
      set
      {
         LastNameField = value;
      }
   }
}
它會將欄位名作為屬性名稱,而匯入的定義中,則在屬性名稱後加上Field尾碼作為欄位名。但我們也可以手工修改用戶端的定義。

如果資料契約的資料成員為私人的,匯入的用戶端定義會自動修改為公有的。“當DataMember特性應用到屬性上時(不管是服務還是用戶端),該屬性必須具有get和set訪問器。如果沒有,在調用時就會拋出InvalidDataContractException異常。因為當屬性自身就是資料成員時,WCF會在序列化和還原序列化時使用該屬性,使開發人員能夠將定製邏輯應用程式到屬性中。”

“不要將DataMember特性既應用到屬性上,又應用到相對應的欄位上,這會導致匯入的成員定義重複。”

如果服務端的資料被標記為Serializable特性,在匯入這樣的定義時,會使用DataContract。而且“對於每一個可序列化的成員,不管是公有的還是私人的,都是資料成員。” 

傳統的格式器不能序列化只標記了DataContract特性的類型。要序列化這樣的類型,必須同時應用DataContract特性和Serializable特性。對於如此類型產生的傳輸型表示形式(Wire Representation),就好似僅僅應用了DataContract特性一般,同時,我們仍然需要為成員添加DataMember特性。

在WCF的資料契約中,很明顯地體現出WCF還不能夠完全支援物件導向的設計思想。在第2章對服務契約的描述中,對契約的繼承層級的處理方式來看,已經體現了這一缺陷的端倪。而對於資料契約而言,更是進一步暴露了這樣的缺陷。

首先WCF並不支援Liskov替換原則(LSP),“預設情況下,我們不能用資料契約的子類去替換基類。” 考慮如下的服務契約:
[ServiceContract]
interface IContactManager
{
   //Cannot accept Customer object here:
   [OperationContract]
   void AddContact(Contact contact);

   //Cannot return Customer objects here:
   [OperationContract]
   Contact[] GetContacts(  );
}
假定用戶端同時定義了一個Customer類:

[DataContract]
class Customer : Contact
{
   [DataMember]
   public int OrderNumber;
}
以下代碼能夠成功通過編譯,但在運行時卻會失敗:

Contact contact = new Customer(  );
contact.FirstName = "Juval";
contact.LastName = "Lowy";

ContactManagerClient proxy = new ContactManagerClient(  );
//Service call will fail:
proxy.AddContact(contact);
proxy.Close(  );
因為在這個例子中,我們傳遞了一個Customer對象,而不是Contact對象。由於服務無法識別Customer對象,也就無法還原序列化它所接收到的Contact對象。

雖然WCF引入了Known Types(已知類型)來解決這一問題,然而對於理解物件導向思想的設計者而言,這樣的設計無疑會引入父類與子類之間的耦合度。因為在我們設計父類的時候,就必須事Crowdsourced Security Testing道子類的定義。當我們需要擴充子類時,還需要修改父類的定義。

WCF引入的服務已知類型,比較已知類型而言,有一定程度的改善。因為它可以將父類與子類在層級上的耦合度縮小到方法級上。但這樣的耦合,依然是不可接受的。例如:
[DataContract]
class Contact
{...}

[DataContract]
class Customer : Contact
{...}
 
[ServiceContract]
interface IContactManager
{
   [OperationContract]
   [ServiceKnownType(typeof(Customer))]
   void AddContact(Contact contact);

   [OperationContract]
   Contact[] GetContacts(  );
}
當然,服務已知類型也可以應用到契約介面上,此時,該契約以及實現該契約的所有服務包含的所有操作都能夠接收已知的子類。

為瞭解決這一問題,WCF提供了配置已知類型的方法。例如:
<system.runtime.serialization>
   <dataContractSerializer>
      <declaredTypes>
         <add type = "Contact,Host,Version=1.0.0.0,Culture=neutral,
                                                              PublicKeyToken=null">
            <knownType type = "Customer,MyClassLibrary,Version=1.0.0.0,
                                             Culture=neutral,PublicKeyToken=null"/>
         </add>
      </declaredTypes>
   </dataContractSerializer>
</system.runtime.serialization>

注意上述的設定檔中,我們配置的已知類型必須是類型的fullname。包括命名空間、版本號碼、Culture等。雖然這種方式可以避免在增加子類的情況下,修改代碼、重新編譯和重新部署,但無疑加重了開發人員的負擔,尤其是對設定檔的管理以及後期的維護。

不過,“如果已知類型對於另一個程式集而言是內部(internal)類型,要添加一個已知類型,只有使用設定檔聲明它。”

總之,在WCF中要實現物件導向的多態,還未能做到最佳。如果能夠將KnownType特性應用到子類上,為子類指名它所繼承的父類,無疑更加利於類的擴充。遺憾的是WCF未能做到這一點。

如果資料契約本身實現了一個介面,情況就變得有趣了。從服務端的定義來看,這樣的資料契約仍然可以通過服務已知類型在服務契約上指定實現了資料契約介面的子資料契約類型。例如,資料契約Contact類實現了介面IContact:
interface IContact
{  
   string FirstName
   {get;set;}
   string LastName
   {get;set;}
}
[DataContract]
class Contact : IContact
{...}

那麼在處理資料契約Contact的服務契約中,如果契約的操作需要以抽象方式,定義IContact類型的參數,就必須使用ServiceKnownType特性指名其實作類別Contact,如下所示:
[ServiceContract]
[ServiceKnownType(typeof(Contact))]
interface IContactManager
{
   [OperationContract]
   void AddContact(IContact contact);

   [OperationContract]
   IContact[] GetContacts(  );
}

注意,此時不能利用KnownType特性,將其直接應用到IContact介面上,因為匯出的中繼資料無法包含介面本身。

服務端的定義無疑符合面向介面編程思想,除了增加了ServiceKnownType之外,整個設計還算優雅。然而根據這樣的定義所匯出的服務契約,卻未免顯得差強人意,如下所示:
[ServiceContract]
public interface IContactManager
{
    [OperationContract]
    [ServiceKnownType(typeof(Contact))]
    [ServiceKnownType(typeof(object[]))]
    void AddContact(object contact);
   
    [OperationContract]
    [ServiceKnownType(typeof(Contact))]
    [ServiceKnownType(typeof(object[]))]
    object[] GetContacts(  );
}

匯出定義中,將應用到契約的ServiceKnownType特性應用到了每個操作上,並且為每個操作都指定了具體的資料契約子類以及一個object[]類型。特別要注意,在操作的傳回值與參數中,原來的IContact類型全部被轉換為了object類型。原因在於,用戶端並沒有IContact介面的定義。基於object的契約定義無疑不具備型別安全。

解決辦法自然是在用戶端中增加IContact介面的定義。如此,用戶端定義就可以修改為:
[ServiceContract]
public interface IContactManager
{
    [OperationContract]
    [ServiceKnownType(typeof(Contact))]
    void AddContact(IContact contact);
   
    [OperationContract]
    [ServiceKnownType(typeof(Contact))]
    IContact[] GetContacts(  );
}

但是,我們不能以具體的資料契約類型Contact,來替換原來的object類型。因為替換為具體的資料契約類型,則用戶端的服務契約就與服務端的服務契約不相容了。所以,下面的定義是錯誤的:
[ServiceContract]
public interface IContactManager
{
    [OperationContract]
    void AddContact(Contact contact);
   
    [OperationContract]
    Contact[] GetContacts(  );
}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.