Programming WCF Services翻译笔记(四)
本書的第2章主要講解了服務契約。內容:“本章首先會討論如何通過操作重載與契約層級,為兩種迥然不同的編程模型建立關聯。然后,本章會介紹一些簡單而又強大的設計和分離服務契約的技術與指導原則。在本章末尾,還演示了如何通過編程方式在運行時實現與契約元數據的交互。”
操作重載
C++與C#均支持操作的重載,但在WCF的編程模型中,卻并不支持這種技術。坦白說,在WCF的編程模型,對于面向對象的支持都是比較弱的,包括后面要介紹的繼承體系與多態,都存在許多問題。因此,在服務端我們不能定義這樣的服務契約:
[ServiceContract]
interface ICalculator
{
?? [OperationContract]
?? int Add(int arg1,int arg2);
?? [OperationContract]
?? double Add(double arg1,double arg2);
}
雖然在編譯時能夠通過,然而一旦在裝載宿主時,就會拋出InvalidOperationException異常。以ICalculator契約為例,WCF會認為是零個操作。
解決的辦法是利用OperationContract特性的Name屬性,例如:
[ServiceContract]
interface ICalculator
{
?? [OperationContract(Name = "AddInt")]
?? int Add(int arg1,int arg2);
?? [OperationContract(Name = "AddDouble")]
?? double Add(double arg1,double arg2);
}
不過采用這種方式,存在的問題是生成的代理會將Name屬性指定的名稱作為代理操作的方法名。這對于編程者而言,并非好的方式。所幸我們可以手動對生成的代理進行修改,將它修改為與服務契約一致的操作名。由于,此時通過Name指定了操作的別名,因此,避免了裝載宿主拋出的異常。
契約的繼承
即使父接口標記了[ServiceContract],子接口仍然需要標記[ServiceContract],因為ServiceContractAttribute是不可繼承的。服務類對服務契約的實現,與傳統的C#編程沒有什么區別。例如:
[ServiceContract]
interface ISimpleCalculator
{
?? [OperationContract]
?? int Add(int arg1,int arg2);
}
[ServiceContract]
interface IScientificCalculator : ISimpleCalculator
{
?? [OperationContract]
?? int Multiply(int arg1,int arg2);
}
class MyCalculator : IScientificCalculator
{
?? public int Add(int arg1,int arg2)
?? {
????? return arg1 + arg2;
?? }
?? public int Multiply(int arg1,int arg2)
?? {
????? return arg1 * arg2;
?? }
}
公開終結點的時候,可以對最底層的契約接口公開一個單獨的終結點:
<service name=”MyCalculator”>
?? <endpoint>
?????? <address=”http://localhost:8001/MyCalculator/”>
?????? <binding=”basicHttpBinding”>
?????? <contract=” IScientificCalculator”>
?? </endpoint>
</service>
客戶端在導入如上的服務契約時,會取消服務契約的繼承層級,并利用OperationContract特性中的Action與ReplyAction屬性,保留原來定義每個操作的契約名。但為了使客戶端編程能夠與服務編程保持一致,最好是恢復客戶端的契約層級。方法并無什么太玄妙的地方,無非就是根據服務契約層級對客戶端契約進行手工修改。修改后的客戶端契約及其代理的定義如下:
[ServiceContract]
public interface ISimpleCalculator
{
?? [OperationContract]
?? int Add(int arg1,int arg2);
}
public partial class SimpleCalculatorClient : ClientBase<ISimpleCalculator>,
????????????????????????????????????????????? ISimpleCalculator
{
?? public int Add(int arg1,int arg2)
?? {
????? return Channel.Add(arg1,arg2);
?? }
?? //Rest of the proxy
}
[ServiceContract]
public interface IScientificCalculator : ISimpleCalculator
{
?? [OperationContract]
?? int Multiply(int arg1,int arg2);
}
public partial class ScientificCalculatorClient :
?????????????????????????? ClientBase<IScientificCalculator>,IScientificCalculator
{
?? public int Add(int arg1,int arg2)
?? {
????? return Channel.Add(arg1,arg2);
?? }
?? public int Multiply(int arg1,int arg2)
?? {
????? return Channel.Multiply(arg1,arg2);
?? }
?? //Rest of the proxy
}
作者在書中還提出了所謂的代理鏈(Proxy Chaining)技術,實質上就是使得分別實現不同層級接口的代理類形成一個IS-A的繼承關系。如上的定義,就可以使ScientificCalculatorClient繼承自SimpleCalculatorClient,而不是繼承ClientBase<IScientificCalculator>:
public partial class SimpleCalculatorClient : ClientBase<IScientificCalculator>,
????????????????????????????????????????????? ISimpleCalculator
{
?? public int Add(int arg1,int arg2)
?? {
????? return Channel.Add(arg1,arg2);
?? }
?? //Rest of the proxy
}
public partial class ScientificCalculatorClient : SimpleCalculatorClient,
????????????????????????????????????????????????? IScientificCalculator
{
?? public int Multiply(int arg1,int arg2)
?? {
????? return Channel.Multiply(arg1,arg2);
?? }
?? //Rest of the proxy
}
只有這樣,如下代碼才是正確的:
SimpleCalculatorClient proxy1 = new SimpleCalculatorClient(? );
SimpleCalculatorClient proxy2 = new ScientificCalculatorClient(? );
ScientificCalculatorClient proxy3 = new ScientificCalculatorClient(? );
服務契約的分解與設計
契約分離與接口隔離原則(ISP,Interface Segregation Principle)的基本精神是一致的。ISP原則建議使用多個專門的接口,而不是使用單個接口,這樣可以防止接口污染,有利于接口重用。契約分解同樣如此,但它還要受到實現契約代價的約束。
書中提供了服務契約的分解準則。“合理的契約分解可以實現深度特化、松散耦合、精細調整以及契約的重用。這些優勢有助于改善整個系統。總的來說,契約分解的目的就是使契約包含的操作盡可能少。”
設計面向服務的系統時,需要平衡兩個影響系統的因素(參見圖2-1)。一個是實現服務契約的代價,一個則是將服務契約合并或集成為一個高內聚應用程序的代價。
?
圖2-1? 平衡服務的個數與規模
定義服務契約時,還要注意到書中所謂的準屬性操作(Property-Like Operation)的使用。一言以蔽之,就是如果涉及到對對象狀態的管理(在C#中一般體現為屬性),則這樣的操作不宜被公開為服務操作。原因在于:“客戶端應該只負責調用操作,而由服務去管理服務對象的狀態。”
契約查詢
要查詢契約,首先需要了解元數據的信息,WCF提供了如下的幾個輔助類,位于System.ServiceModel.Description命名空間:
public enum MetadataExchangeClientMode
{
?? MetadataExchange,
?? HttpGet
}
class MetadataSet : ...
{...}
public class ServiceEndpointCollection : Collection<ServiceEndpoint>
{...}
public class MetadataExchangeClient
{
?? public MetadataExchangeClient(? );
?? public MetadataExchangeClient(Binding mexBinding);
?? public MetadataExchangeClient(Uri address, MetadataExchangeClientMode mode);
?? public MetadataSet GetMetadata();
?? public MetadataSet GetMetadata(EndpointAddress address);
?? public MetadataSet GetMetadata(Uri address,MetadataExchangeClientMode mode);
?? //More members
}
public abstract class MetadataImporter
{
?? public abstract ServiceEndpointCollection ImportAllEndpoints(? );
?? //More members
}
public class WsdlImporter : MetadataImporter
{
?? public WsdlImporter(MetadataSet metadata);
?? //More members
}
public class ServiceEndpoint
{
?? public EndpointAddress Address
?? {get;set;}
?? public Binding Binding
?? {get;set;}
?? public ContractDescription Contract
?? {get;}
?? //More members
}
public class ContractDescription
{
?? public string Name
?? {get;set;}
?? public string Namespace
?? {get;set;}
?? //More members
}
書中提供了元數據的查詢方法,同時還實現了一個專門用于操作元數據的MetadataHelper類。
public static class MetadataHelper
{
?? public static bool QueryContract(string mexAddress,Type contractType);
?? public static bool QueryContract(string mexAddress,string contractNamespace, string contractName);
?? //More members
}
可以為MetadataHelper類提供我們希望查詢的契約類型,或者提供該契約的名稱與命名空間:
string address = "...";
bool contractSupported = MetadataHelper.QueryContract(address,typeof(IMyContract));
具體的實現可以參見書中的描述,完整的實現代碼可以到作者的網站(http://www.idesign.net)去下載。
總結
以上是生活随笔為你收集整理的Programming WCF Services翻译笔记(四)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Javascript实现浏览器菜单命令
- 下一篇: 正则表达式的威力--轻松消除HTML代码