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)。一个是实现服务契约的代价,一个则是将服务契约合并或集成为一个高内聚应用程序的代价。
 figure-2-1.gif
图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)去下载。

posted @ 2007-09-14 10:32  张逸  阅读(4013)  评论(5编辑  收藏  举报