65.9K
CodeProject 正在变化。 阅读更多。
Home

WS-Transfer for WCF (Indigo)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (26投票s)

2005年7月24日

CPOL

23分钟阅读

viewsIcon

141877

downloadIcon

473

本文描述了 Indigo 驱动应用程序的 WS-Transfer 的设计、实现和使用。

目录

注意:本文是使用 .NET Framework 3.0 - 2006 年 6 月 CTP 编写的。

引言

Web 服务传输 (WS-Transfer) 代表了一个规范,用于捕获资源的当前或预期状态,并在组件之间传输该表示。它是分布式异构系统中的架构元素的抽象。WS-Transfer 将资源的实现和维护从其表示中封装起来。WS-Transfer 使用基于 SOAP 协议的组件之间的统一接口,允许在网络上交换资源的状态。我还建议您阅读 [2] - 表征状态传输 (REST) 文档,该文档详细描述了面向资源的架构风格。

让我简要描述一下主要架构元素——WS-Transfer 的关键,以便更好地理解和在应用程序模型中的定位。我们可以基于这些元素创建“服务总线上资源交换状态”的逻辑模型。

Resource

资源代表 WS-Transfer 规范中的关键抽象。资源可以是任何可命名的事物/信息。例如,本文、网页、图像、服务信息、文件句柄、资源集合和/或资源标识符;它也可以是 SOAP 消息、SOAP 头等。因此,WS-Transfer 是一种面向资源的架构风格,其中资源可以是静态信息,例如标题文档的图像,或动态信息,例如天气、股市、资源句柄等。

资源标识符

资源标识符表示资源的逻辑实体,与特定“简单”资源关联的值。资源标识符可以识别资源或其部分。例如,本文是信息资源的示例。它由单词、图片、代码片段等组成。Code Project 工厂将将其状态存储在众所周知的标识符下。另一个很好的例子是 Google 搜索引擎:作为网站访问者,您将搜索键键入 Google 工厂。在这种情况下,该键表示您启动的资源状态。根据此状态,工厂将返回资源标识符的表示,即资源部分的集合(资源标识符)。

表示

表示是资源状态元数据的具体形式,描述(编码)数据。它是资源的体现。表示可以包含在在端点之间传输的消息中。例如:SOAP 消息负载是使用众所周知的 XML 格式文本的资源状态的表示。

接口

WS-Transfer 定义了一种获取资源的基于 XML 的表示的机制;如何使用交互协议创建、发送或接收它。这种交互分为以下功能组

  • 资源工厂:负责从 XML 表示创建资源状态并返回其标识符。
  • 资源操作:负责操作资源状态,例如:获取、放置和删除资源状态。

请注意,WS-Transfer 不定义资源状态的生命周期和维护。实际上,如果客户端拥有资源状态及其标识符存在的端点地址,这并不意味着操作会成功。例如:资源状态将暂时不可用,例如,当文件系统正在恢复时,等等。

WS-Transfer 有四种操作

  • 创建:指定资源的创建,并初始化其表示(也可以为空)。这是一个工厂接口(此组中只有一个),具有双向消息传递。第一个请求将创建资源并初始化其状态,第二个请求——其响应将返回带有资源状态表示的资源标识符的地址。请注意,资源状态也可以是有关下一个资源工厂、处理程序、资源描述符、图像正文等的信息。
  • 获取:指定一种消息协议,用于根据地址头中包含的资源标识符以 XML 表示形式获取资源状态。
  • 放置:指定一种消息协议,用于通过新的资源表示更新(替换)资源状态。如果资源与其初始表示不匹配,则可能会拒绝此更新。
  • 删除:指定一种消息协议,用于删除资源状态。

如上所述,资源工厂将返回资源状态存在的服务的地址。此资源地址隐式用于资源操作,因此客户端无需了解其表示,它将按原样使用它。看起来资源地址是资源操作的逻辑令牌。

让我用下图描述资源与其组件(客户端)之间的 WS-Transfer 交互

客户端(Web 应用程序、Web 服务等)需要知道资源工厂的端点,例如 EP 1。向此端点发送带有已启动资源表示的创建消息将强制工厂创建资源状态。例如,LAX 的天气信息。为了获取此状态(信息/资源),客户端需要知道其地址和标识符,它将通过创建响应在消息体中接收到这些信息。这是第一步。

第二步是处理资源操作。客户端将通过调用资源操作获取此资源地址。例如,get 操作。操作响应将返回资源状态的基于 XML 的表示,例如,洛杉矶国际机场的天气状况。基于资源工厂(服务),客户端可以发送其他操作,例如 put 和 delete。在我们的示例中,资源操作服务将不实现 put 和/或 delete 接口。客户端无法更新洛杉矶国际机场的天气状况,这确实有道理,不是吗?顺便说一句,如果能有这样的服务那就太好了。

因此,根据上图,我们知道第一个调用需要发送到工厂,然后下一个调用发送到操作服务。由资源工厂和操作服务决定如何创建资源状态,以及其生命周期和操作。此外,资源工厂和操作可以由同一个服务处理。

这种两步设计模式在逻辑上是正确的;因此,客户端知道该资源类型的资源工厂“在哪里”。第一步调用的响应将包含有关资源地址的明确详细信息(句柄/标识符/等)。例如 - 将字节写入文件系统

 FileStream factory = new System.IO.FileStream(myResource, 
                                         FileMode.Create); 
 factory.Write(myResourceStateRepresentation, 0, 100);
 factory.Close();

上述类比中的第一行调用文件工厂创建资源 - 文件流。返回值是资源地址,该地址可以接受写入(放置)等操作。资源将在该操作期间使用新的表示进行更新。之后,工厂可以关闭并处于新的资源状态。

WS-Transfer 交互基于 REST [2] 架构风格(看起来像是其 SOAP 实现),其中四个接口可以处理资源状态的创建和操作。资源状态中存在 CRUD(创建/检索/更新/删除)样式,与数据库层中的数据库记录相同。

顺便说一下,这种架构风格也存在于 CPU 硬件领域,参见 CISC (Complex Instruction Set Computer) 与 RISC (Reduced Instruction Set Computer)。

无论如何,基于以上描述,让我深入到一个更实际的例子中,WS-Transfer 在多层和多层架构中扮演着重要角色,例如将逻辑业务层从由不同协议、绑定等驱动的物理源中封装出来。

示例 1 - 查看图像

我们以从不同存储库查看文档图像为例。下图显示了模型的层次结构,其中前端业务层 (FEBL) 逻辑上连接到“服务总线”,该服务总线理解对资源状态表示的 WS-Transfer 访问。总线上的服务,例如 ImagingService 和 SourceService,提供资源描述符的创建——用于将逻辑业务信息映射到每个源适配器和资源操作所需的物理参数,以获取资源状态表示。StorageService 可用于存储图像的资源描述符和/或图像主体。

由 WS-Transfer 驱动的服务总线能够将可以托管在公共进程(例如 IIS)或部署在企业网络上的层分离。表示层及其位于 FEBL 中的业务预处理器,基于逻辑业务信息和元数据(例如文档标识符、类型等),以透明的方式访问物理图像。请注意,服务总线代表服务的逻辑层,具有基于 WS-* (wsProfileBinding) 的统一接口。其他非 WS 规范接口可以通过适配器处理。

下图显示了与 WS-* 驱动服务总线(特别是 WS-Transfer)的交互协议。应用层 (FEBL) 将带有逻辑资源描述符 (LRD) 的封装资源发送到位于 EP1 的知名资源工厂。LRD 表示资源工厂与其消费者之间此资源类型的数据契约表示。从 WS-Transfer 规范的角度来看,它可以被视为具有其资源状态的资源。基于 LRD,资源工厂将创建资源状态。在我们的例子中,响应将表示一个物理资源描述符 (PRD),它是资源标识符

一旦应用程序从资源工厂获得响应,它就可以处理资源操作,例如我们示例中的 Get(图像属性和/或图像正文)。正如上面的示例所示,应用程序层以透明的松散耦合方式从源封装,使用统一的层交互。当然,此交互堆栈可以根据部署模式扩展以用于安全性、策略等堆栈元素。

此外,本例表明,通过在资源工厂中使用简单的物理映射逻辑,资源状态可以被视为目标资源的资源标识符。这种设计模式可以应用于大多数任何资源类型、交互等。根据资源类型,我们可以构建特定的资源工厂,例如 ImageService、StorageService 等。当然,某些服务可以支持未知的(“裸”)资源类型,例如 WS-Transfer/Create 规范定义的类型。

如果资源有另一个包装器,其中包含其资源描述符中的更多元数据,则没有问题。分层分布式传输范例中的主要规则是“不要打开包裹”。例如,联邦快递;你带着你的包裹(资源)去他们(工厂),你填写他们的表格(资源描述符),然后所有这些都被打包(工厂)到联邦快递信封中。你将收到一个联邦快递工厂响应,由一个包裹 ID(资源标识符)表示,该 ID 可用于资源(包裹)操作。你能看到与上述 ImageService 的类比吗?

让我们看看创建请求/响应消息。以下代码片段显示了创建消息的有效负载。此有效负载是资源状态的表示,其本地名称为 CreateResource。我在我的命名空间中创建它,以演示“裸”资源的包装,其描述符由 ResourceDescriptor 表示。

ResourceDescriptor 是资源标识符的复杂类型。有许多有用的属性,例如 key、topic、ID、expires、notification (subscriptions)、source metadata 等,描述了资源状态及其生命周期。例如,当资源状态更新时,可以根据订阅者的订阅向其发送通知。另一个例子:由于资源状态的生命周期可能有限,因此可以在资源状态过期之前通知订阅者。

回到下面的代码片段。加粗部分显示了创建请求中实际的 WS-Transfer 资源状态,以及资源工厂如何接收它

<?xml version="1.0" encoding="utf-16"?>
<soap:Envelope>
  <soap:Header>
   <wsa:Action >
       http://schemas.xmlsoap.org/ws/2004/09/transfer/Create
   </wsa:Action>
   <wsa:To>net.tcp://myServer:12345/PublicImageService</wsa:To>
  </soap:Header>
  <soap:Body>
   <CreateResource 
      xmlns="http://www.rkiss.com/schemas/sb/2005/06/servicebus/xfer">
      <ResourceDescriptor>
        <Name>Example of the Image Resource</Name>
        <Topic>Image</Topic>
        <Key>image:123456789</Key>
        <Expires>2006-06-26T21:07:00</Expires>
        <Notify />
      </ResourceDescriptor>
      <Resource />
   </CreateResource>
  </soap:Body>

以下代码片段显示了资源工厂的创建响应。我已加粗显示资源标识符的部分。请注意,WS-Transfer 不指定资源标识符的类型。它可以是 URI 类型或复杂类型,例如我的 ResourceDescriptor。原因是此资源标识符是资源操作的信息(元数据),而不是客户端的信息。客户端不应关心资源标识符的内容。它就像上面的文件句柄,FedEx 的跟踪号

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope>
  <soap:Body>
    <wxf:ResourceCreated xmlns:wxf=
        "http://schemas.xmlsoap.org/ws/2004/09/transfer">
      <wsa:Address>net.tcp://myServer2:12345/PublicSourceService
      </wsa:Address>
        <wsa:ReferenceProperties>
          <ResourceDescriptor xmlns=
             "http://www.rkiss.com/schemas/sb/2005/06/servicebus/xfer">
            <Topic>Image</Topic>
            <Identifier>uuid:11111111-1111-1111-1111-111111111111
            </Identifier>
            <Expires>2006-06-27T11:07:00Z</Expires>
            <Created>2005-07-05T04:16:37Z</Created>         
            <Source>
              <wsa:Address>http://ImageServer/ws/imageWebService.asmx
              </wsa:Address>
                <wsa:ReferenceProperties>
                  <Proxy type="ImageAdapters.Adapter1, MyAdapter">
                    <Method name="GetImageBody" />
                    <Parameter name="Username" value="myName" />
                    <Parameter name="Password" value="myPassword" />
                    <Parameter name="Location" value="California" />
                    <Parameter name="DocumentIdentifier" 
                               value="ABCD:123456789" />
                  </Proxy>
                </wsa:ReferenceProperties>
            </Source>         
          </ResourceDescriptor>
        </wsa:ReferenceProperties>
    </wxf:ResourceCreated>
  </soap:Body>
</soap:Envelope>

请注意,wsf:ResourceCreated 是子元素(在类术语中是 EndpointAddress 的子类)。顺便说一句,这个类是一个 sealed 类。如果发布版本中没有此限制会更好,就像 WSE2 中的 EndpointReferencewsa:Address),其中 wsa:RefererenceProperties 携带我们的资源操作的资源标识符。它通过“我转发我收到的”方式简化了 WS-Transfer 交互的使用。

我使用 WSE 2 技术验证了上述 WS-Transfer 工厂和操作,包括 ResourceDescriptor 表示。让我们看看如何使用 Indigo 技术实现 WS-Transfer。

以下章节重点介绍 Indigo 通信模型对 WS-Transfer 规范的概念、设计和实现。我假设读者理解该模型及其著名的 ABC(地址、绑定、契约)模式。

我将描述

  • 创建请求和响应消息——其消费者和提供者方法。
  • 自定义绑定——如何在绑定过程中添加您的配置属性。
  • 服务适配器中自定义消息处理的封装。

此外,您还将获得一个简单的控制台记录器作为 MessageInspector 实现的示例。这个简单的记录器用于演示和测试控制台主机程序之间的 WS-Transfer 交互。当然,您也可以使用 Indigo 内置的诊断功能。

特点

由 WS-Transfer 消息驱动的服务具有以下特点

  • 创建资源状态。
  • 获取资源状态。
  • 放置(更新)资源状态。
  • 删除资源状态。
  • 服务适配器中 WS-Transfer 接口的封装。
  • 资源标识符的数据契约。
  • 使用端点绑定进行配置。
  • 插入可配置的服务适配器。
  • 一个服务可以处理多个适配器。

概念与设计实现

实现的概念是基于使用 Indigo 范式,将由 CLR 对象驱动的应用程序层从资源(物理)层封装起来。下图显示了这三个层

连接器代表客户端——业务层,消费者以 RPC 方式连接。连接器的职责是根据 WS-Transfer 规范将调用转换为 SOAP XML 格式的消息,并通过 Indigo 基础设施将其传递给资源特定适配器。在这种情况下,Indigo 代表资源及其消费者之间连接的逻辑模型。它是一种将业务层从物理层分离的机制。我使用与 BizTalk 相同的命名(连接器 - 适配器)。

WS-Transfer 实现的组件分为以下几个部分

第 0 级(核心)

  • 服务合同
  • 请求/响应消息

第 1 级(服务层)

  • 配置
  • Service
  • 适配器

第 2 级(消费者)

  • 客户端

第 0 级 - 核心

这是 WS-Transfer 实现的核心。可以基于 ServiceContract 和请求/响应消息实现最高级别的 WS-Transfer 功能。这种能力将简化其使用。

服务合同

ServiceContract 是 WS-Transfer 接口的声明。下面的代码片段显示了它的抽象声明。如您所见,接口分为两组,例如工厂接口和操作接口,分别支持工厂服务和操作服务。在通用 WS-Transfer 服务的情况下,可以使用 IWSTransfer

 [ServiceContract(Namespace = WSTransfer.NamespaceUri)]
 public interface IWSTransfer : IWSTransferFactory, 
                                   IWSTransferOperation
 {
 }
 
 [ServiceContract(Namespace = WSTransfer.NamespaceUri)]
 [XmlSerializerFormat]
 public interface IWSTransferOperation
 {
    [OperationContract(Action = WSTransfer.GetAction, 
            ReplyAction = WSTransfer.GetResponseAction)]   
    GetResponse Get(GetRequest request);

    [OperationContract(Action = WSTransfer.PutAction, 
              ReplyAction=WSTransfer.PutResponseAction)]
    PutResponse Put(PutRequest request);

    [OperationContract(Action = WSTransfer.DeleteAction, 
         ReplyAction = WSTransfer.DeleteResponseAction)]
    DeleteResponse Delete(DeleteRequest request);
 }

 [ServiceContract(Namespace = WSTransfer.NamespaceUri)]
 [XmlSerializerFormat]
 public interface IWSTransferFactory
 {
    [OperationContract(Action = WSTransfer.CreateAction, 
         ReplyAction = WSTransfer.CreateResponseAction)]
    CreateResponse Create(CreateRequest request);
 }

消息契约 - 请求/响应消息

上述接口使用 Request/Response 对象——消息模式,允许以透明方式插入 Indigo 消息范式,而无需任何“SOAP/XML 世界”的知识。这些对象是将 RPC 调用转换为/来自 WS-Transfer 表示的转换器。在某种程度上,WSE2 消息传递使用 IXmlElement 管道接口将对象传输到/来自 XML 格式文档;Indigo 使用 MessageContractAttribute 在运行时控制自定义头和消息正文的插入。

让我们看看以下代码片段中 WS-Transfer 动作实现的几个接口操作,“它们展示了这种设计模式”。它们的实现以通过 OperationContract 内的 MessageContract 将对象属性传输到/从 XML 格式文档的方式进行模式化。

请注意,服务契约被修饰为使用 XmlSerializer(而不是 XmlFormatter)进行消息流的序列化/反序列化。这种模式允许根据 WS-Transfer 规范要求以编程方式处理 XML 元素。

创建请求

WS-Transfer 规范

<s:Envelope ...>
  <s:Header ...>
    <wsa:Action>http://schemas.xmlsoap.org/ws/2004/09/transfer/Create
    </wsa:Action>
    <wsa:MessageID>xs:anyURI</wsa:MessageID>
    <wsa:To>xs:anyURI</wsa:To>
    ...
  </s:Header>
  <s:Body ...>
    xs:any
  </s:Body>
</s:Envelope>

创建请求实现

[MessageContract]
public class CreateRequest 
{
    #region DataMembers
    private XmlElement _resource;
    #endregion
    
    #region Properties
    [MessageBodyMember(Order = 0)]
    [XmlAnyElement]
    public object Resource
    {
        get { return _resource; }
        set { _resource = Convert.ObjectToXml(value); }
    }
    #endregion
    
    #region Constructors
    public CreateRequest() : this((object)null)
    {
    }
    public CreateRequest(object resource)
    {
        this.Resource = resource;
    }
    #endregion 
}

以及一个例子

<soap:Envelope>
 <soap:Header>
   <wsa:Action >
     http://schemas.xmlsoap.org/ws/2004/09/transfer/Create
   </wsa:Action>
   <wsa:To>net.tcp://myServer:12345/PublicStorageService
   </wsa:To>
   <wsa:MessageID>urn:uuid:00000000-0000-0000-C000-000000000048
   </wsa:MessageID>
 </soap:Header>
 <soap:Body>
   <xxx:Customer>
      <xxx:First>Roman</xxx:First>
      <xxx:Last>Kiss</xxx:Last>
    </xxx:Customer>
 </soap:Body>
</soap:Envelope>

或另一个示例,其中消息体是 DataContract 对象,例如 CreateResource

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" 
   xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
  <s:Header>
    <wsa:Action s:mustUnderstand="1">
     http://schemas.xmlsoap.org/ws/2004/09/transfer/Create
    </wsa:Action>
    <wsa:MessageID>urn:uuid:d4020733-ced1-44ae-9728-e26c8ce457ca;id=0
    </wsa:MessageID>
  </s:Header>
  <s:Body>
    <CreateResource 
       xmlns="http://www.rkiss.com/schemas/sb/2005/06/servicebus/xfer">
       <ResourceDescriptor>
        <Identifier>12345678</Identifier>
       </ResourceDescriptor>
       <Resource />
    </CreateResource>
  </s:Body>
</s:Envelope>

创建响应

WS-Transfer 规范

<s:Envelope ...>
  <s:Header ...>
    <wsa:Action>
     http://schemas.xmlsoap.org/ws/2004/09/transfer/CreateResponse
    </wsa:Action>
    <wsa:RelatesTo>xs:anyURI</wsa:RelatesTo>
    <wsa:To>xs:anyURI</wsa:To>
  ...
  </s:Header>
  <s:Body ...>
    <wxf:ResourceCreated>endpoint-reference
    </wxf:ResourceCreated>
    xs:any
  </s:Body>
</s:Envelope>

创建响应实现

[MessageContract]
public class CreateResponse
{
    #region DataMembers
    private EndpointAddress _epa;
    #endregion

    #region Properties
    [MessageBodyMember(
      Name=WSTransfer.ElementNames.ResourceCreated, 
      Namespace=WSTransfer.NamespaceUri, Order=0)]
    public EndpointAddress10 EndpointAddress10
    {
        get { return EndpointAddress10.FromEndpointAddress(_epa); }
        set { _epa = value.ToEndpointAddress(); }         
    }
    public EndpointAddress EndpointAddress
    {
        get { return _epa; }
        set { _epa = value; }
    }
    #endregion
        
    #region Constructors
    public CreateResponse()
    {
        this.EndpointAddress = null;
    }
    public CreateResponse(EndpointAddress epa)
    {
        this.EndpointAddress = epa;
    }
    #endregion
}

以及一个例子

<soap:Envelope>
  <soap:Header>
    <wsa:Action >
     http://schemas.xmlsoap.org/ws/2004/09/transfer/CreateResponse
    </wsa:Action>
    <wsa:To s:mustUnderstand="1">
     http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
    </wsa:To>
  </soap:Header>
  <soap:Body>
   <wxf:ResourceCreated>
     <wsa:Address>http://www.example.org/someport</wsa:Address>
     <wsa:ReferenceProperties>
      <xxx:CustomerID>123456789</xxx:CustomerID>
     </wsa:ReferenceProperties>
   </wxf:ResourceCreated>
  </s:Body>
</s:Envelope>

另一个示例显示 ReferenceProperties 具有 DataContract(资源标识符)对象,例如:ResourceDescriptor。请再次注意,资源标识符必须由其工厂服务寻址的 WS-Transfer 操作服务识别。这就是 WS-Transfer 架构的魔术风格(“黑盒令牌”)

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" 
    xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
  <s:Header>
    <wsa:Action s:mustUnderstand="1">
     http://schemas.xmlsoap.org/ws/2004/09/transfer/CreateResponse
    </wsa:Action>
    <wsa:RelatesTo>urn:uuid:d4020733-ced1-44ae-9728-e26c8ce457ca;id=0
    </wsa:RelatesTo>
    <wsa:To s:mustUnderstand="1">
     http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
    </wsa:To>
  </s:Header>
  <s:Body>
    <wxf:ResourceCreated 
     xmlns:wxf="http://schemas.xmlsoap.org/ws/2004/09/transfer">
      <wsa:Address>http://myServer2/WebHost/PublicSourceService.ashx
      </wsa:Address>
       <wsa:ReferenceProperties>
         <ResourceDescriptor 
           xmlns="http://www.rkiss.com/schemas/sb/2005/06/servicebus/xfer">
           <Topic>Image</Topic>
           <Expires>2006-06-27T11:07:00Z</Expires>
           <Created>2005-07-05T04:16:37Z</Created> 
           <Source>
             <wsa:Address>http://ImageServer/ws/imageWebService.asmx
             </wsa:Address>
               <wsa:ReferenceProperties>
                 <Proxy type="ImageAdapters.Adapter1, MyAdapter">
                   <Method name="GetImageBody" />
                   <Parameter name="DocumentIdentifier" 
                                 value="ABCD:123456789" />
                 </Proxy>
               </wsa:ReferenceProperties>
           </Source> 
         </ResourceDescriptor>
       </wsa:ReferenceProperties>
    </wxf:ResourceCreated>
  </s:Body>
</s:Envelope>

这就是 WS-Transfer 工厂接口的请求/响应消息的全部内容。我们得到的是:请求消息将任何资源有效负载发送到工厂,工厂使用 CreateResponse 主体进行响应,该主体是资源操作的“票证”。让我们描述如何在 Indigo 中传输 GetRequest/Response 消息

获取请求

对于操作契约,我们有一个稍微不同的场景。EndpointAddress 头需要在 ReferenceProperties 中传递一个资源标识符。请注意,此地址由资源工厂返回。以下代码片段演示了如何使用 MessageHeaderAttribute 创建 GetRequest 消息。

WS-Transfer 规范

<s:Envelope ...>
  <s:Header ...>
    <wsa:Action>http://schemas.xmlsoap.org/ws/2004/09/transfer/Get
    </wsa:Action>
    <wsa:MessageID>xs:anyURI</wsa:MessageID>
    <wsa:To>xs:anyURI</wsa:To>
    ...
  </s:Header>
  <s:Body .../>
</s:Envelope>

获取请求实现

[MessageContract]
public class GetRequest
{
    #region DataMembers
    private XmlElement _identifier = null;
    #endregion 

    #region Properties
    [MessageHeader]
    [XmlAnyElement]
    public object Identifier
    {
        get { return _identifier; }
        set { _identifier = Convert.ObjectToXmlAnyElement(value); }
    }
    #endregion

    #region Constructors
    public GetRequest()
    {
    }
    public GetRequest(object identifier)
    {
      this.Identifier = identifier;
    }
    #endregion
}

一个带 ResourceDescriptor 的例子

<soap:Envelope>
  <soap:Header>
    <wsa:Action >
      http://schemas.xmlsoap.org/ws/2004/09/transfer/Get
    </wsa:Action>
    <wsa:To>net.tcp://myServer:12345/PublicStorageService
    </wsa:To>
    <wsa:MessageID>urn:uuid:94705fae-90e1-462a-969f-23d609c06073;id=0
    </wsa:MessageID>
    <ResourceDescriptor x:Id="1" 
        xmlns="http://www.rkiss.net/schemas/sb/2005/06/servicebus" 
        xmlns:x="http://schemas.microsoft.com/2003/10/Serialization/" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <Key xsi:nil="true"></Key>
      <Name xsi:nil="true"></Name>
      <ResourceId x:Id="2">guid:142dae63-d886-4201-8c25-97f2f6be41b4
      </ResourceId>
      <Topic xsi:nil="true"></Topic>
    </ResourceDescriptor>
  </s:Header>
  <soap:Body/>
</soap:Envelope>

获取响应

WS-Transfer 规范

<s:Envelope ...>
  <s:Header ...>
    <wsa:Action>
     http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse
    </wsa:Action>
    <wsa:RelatesTo>xs:anyURI</wsa:RelatesTo>
    <wsa:To>xs:anyURI</wsa:To>
    ...
  </s:Header>
  <s:Body ...>
    xs:any
  </s:Body>
</s:Envelope>

根据 WS-Transfer 规范,GetResponse 必须在消息正文中返回资源状态的表示。消息是根据资源对象类型生成的。对于 XmlElement 类型,资源主体使用辅助类直接写入消息正文,请参见以下代码片段。我喜欢这种直接的模式

[MessageContract]
public class GetResponse 
{
    #region DataMembers
    private XmlElement _resource;
    #endregion
    
    #region Properties
    [MessageBodyMember]
    [XmlAnyElement]
    public object Resource
    {
        get { return _resource; }
        set { _resource = Convert.ObjectToXml(value); }
    }
    #endregion
    
    #region Constructors
    public GetResponse()         
    {
        this.Resource = null;
    }
    public GetResponse(object resource)
    {
        this.Resource = resource;
    }
    #endregion
}

以及一个例子

<soap:Envelope>
  <soap:Header>
    <wsa:Action>
      http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse
    </wsa:Action>
    <wsa:To s:mustUnderstand="1">
      http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
    </wsa:To>
    <wsa:RelatesTo>uuid:94705fae-90e1-462a-969f-23d609c06073
    </wsa:RelatesTo>
  </soap:Header>
  <soap:Body>... stream ...</soap:Body>
</soap:Envelope>

如上述代码片段所示,消息头或消息体可以是任何对象,因此我们用 XmlAnyElement 属性装饰它们。对于这种格式化方式,任何产生的值都必须转换为 XmlElement。以下代码片段显示了此辅助 static 方法

public static XmlElement ObjectToXmlAnyElement(object resource)
{
  if (resource != null)
  {
    if (resource is XmlElement)
    {
       return resource as XmlElement;
    }
    else if (resource is XmlNode)
    {
       return resource as XmlElement;
    }
    else
    {
      XmlDocument doc = new XmlDocument();
      using (MemoryStream ms = new MemoryStream())
      {
        XmlWriter xtw = XmlTextWriter.Create(ms, null);
        xtw.WriteStartElement("root");
        XmlSerializer xs = new XmlSerializer(resource.GetType());
        xs.Serialize(xtw, resource);
        xtw.WriteEndElement();
        xtw.Flush();
        ms.Position = 0;
        doc.Load(ms);                                           
      }
      return doc.DocumentElement;
    }
  }
  return null;
}

操作消息(例如 PutDelete 请求/响应类)的其余实现与 Get 消息非常相似。我将在本文中跳过该部分,以便我们现在可以关注代表所描述消息用法的 Level 1。

第 1 级 - 服务层

这是一个工厂和服务操作层,用于将 WS-Transfer 消息传递与资源逻辑封装起来。服务层可以用多种设计模式实现。基本上,这个层应该有一个由 WS-Transfer 消息驱动的通用服务层和一个松散耦合的适配器,其中可以实现资源特定的逻辑。下图显示了这种模式

如上图所示,WSTransferService 层对资源工厂和操作具有虚拟访问权限。资源逻辑已解耦到面向资源类型的适配器中,例如 MemoryStorageAdapterImageAdapterSqlStorageAdapter 等。适配器和 WSTransferService 通过 IWSTransferAdapter 接口以松散耦合的方式连接起来,基于配置行为扩展。这种模式允许在一个 WS-Transfer 服务契约下拥有多个适配器(当然,具有不同的端点)。

例如:以下类展示了以紧密耦合的方式将 MemoryStorageAdapter 连接到 WSTransferService

public class WSTransferService : 
       WSTransferService<MemoryStorageAdapter> { }

这是基于 CONFIG 文件的“已配置适配器”的松散耦合连接

public class WSTransferService : 
       WSTransferService<ConfiguredAdapter> { }

其中 WSTransferService<ServiceAdapter> 类表示一个通用的 WS-Transfer 服务和所有管道抽象

public class WSTransferService<ServiceAdapter> 
     : ServiceAdapterBase<ServiceAdapter, IWSTransferAdapter>, 
       IWSTransfer where ServiceAdapter: class { }

上述 WS-Transfer 服务将需要以下组件

  • IWSTransferAdapter - 适配器接口。
  • ServiceAdapterBase<ServiceAdapter, IWSTransferAdapter> - 用于将虚拟适配器连接到服务的基本类。
  • WSTransferService<ServiceAdapter> - 这是 WS-Transfer 消息的服务层。

当然,还有相同的通用(可重用)机制来扩展服务/端点行为

  • ServiceAdapterBehaviorSection - 通过 CONFIG 文件为特定适配器创建服务/端点自定义行为。
  • AdapterAttribute - 通过类/方法属性为特定适配器创建服务/端点自定义行为。
  • AdapterInvoker - 在操作(方法)级别启动适配器实例。

配置 - 自定义行为扩展

我将从底部——配置扩展——开始描述上述组件。以下代码片段显示了服务器 CONFIG 文件的一部分。这是一个带有行为扩展的简单标准配置——参见加粗文本。behaviorExtensions 允许将适配器属性与服务(或端点)绑定。请注意,type 属性是强制属性,其他属性作为无法识别的属性传递给适配器实例。

服务器主机 CONFIG

<system.serviceModel>
  <services>
    <service name="RKiss.WSTransfer.WSTransferService"
        behaviorConfiguration="WxfServiceExtension" >
      <endpoint
        address="net.tcp://:11111/PublicStorage"
        binding="customBinding"
        bindingConfiguration="Binding1"
        contract="RKiss.WSTransfer.IWSTransfer"/>
    </service>
  </services>

  <bindings>
    <customBinding>
      <binding name="Binding1">
        <tcpTransport/>
      </binding>
    </customBinding>
  </bindings>

  <behaviors>
    <serviceBehaviors>
      <behavior name="WxfServiceExtension" >
        <logger enable="true" logAfterReceiveRequest="true"
            logBeforeSendReply="true"/>     
        <wsTransferAdapter name="PublicStorage" topic="weather"
            type="RKiss.WSTransfer.Adapters.MemoryStorageAdapter"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>

  <extensions>
    <behaviorExtensions>
      <add name="logger" type="RKiss.Logger.LoggerBehaviorSection,Logger,
         Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      <add name="wsTransferAdapter"
         type="RKiss.WSTransfer.ServiceAdapterBehaviorElement,WSTransfer,
         Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
    </behaviorExtensions>
  </extensions>
</system.serviceModel>

服务/端点扩展设计模式是直观的,设计良好,并且需要最少的编码。基本上,我们需要三个类来处理此扩展,例如配置部分类、归因类和操作扩展类 - 拦截器。用于保存可配置属性的扩展状态是 AdapterAttribute 类。以下代码片段显示了其实现的主要部分。感谢 WCF(也称为 Indigo)编程将此编码最小化

配置部分的类显示在以下代码片段中

public class ServiceAdapterBehaviorSection : BehaviorExtensionSection
{
  protected override object CreateBehavior()
  {
     string type = (string)this["type"];
     Type adapterType = (type != null && type.Trim().Length > 0) 
        ? Type.GetType(type) : null;
     AdapterAttribute adapter = new AdapterAttribute(adapterType, 
        _unrecognizedAttributes);
     return adapter;
  }
  
  // ...
}

以下类是派生自接口的 AdapterAttribute,其中完成了自定义扩展。请注意,操作扩展无法通过配置部分完成

public class AdapterAttribute : Attribute, 
    IOperationBehavior, IEndpointBehavior, IServiceBehavior
{
  
  public void ApplyDispatchBehavior(OperationDescription description, 
      DispatchOperation dispatch)
  {
     if (dispatch.Invoker is AdapterInvoker)
        return;
     dispatch.Invoker = new AdapterInvoker(Name, AdapterType, 
        Parameters, dispatch.Invoker);
  }
  public void ApplyDispatchBehavior(ServiceDescription description, 
        ServiceHostBase serviceHostBase)
  {
    foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
    {
       foreach (EndpointDispatcher epdispatch in cd.Endpoints)
       {
          ApplyDispatchBehavior(null, epdispatch);  
       }
    }
  }
  public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, 
     EndpointDispatcher endpointDispatcher)
  {
    foreach (DispatchOperation dispatch 
       in endpointDispatcher.DispatchRuntime.Operations)
    {
       ApplyDispatchBehavior(null, dispatch);
    }
  }
  
  // ...
}

此自定义行为扩展的最后一个类是派生自 IOperationInvoker 接口的 AdapterInvokerInvoke 方法表示传入消息的拦截器,我们可以在其中启动包含在其属性中的自定义行为

public class AdapterInvoker : IOperationInvoker
{
   object IOperationInvoker.Invoke(object instance, object[] inputs, 
        out object[] outputs)
   {
      string name = (string)_parameters["name"];
      PropertyInfo pi = instance.GetType().GetProperty("Adapter");
      object adapter = pi.GetValue(instance, null);
      if (adapter == null && AdapterType != null)
      {
          pi.SetValue(instance, Activator.CreateInstance(this.AdapterType, 
                new object[] { this.Parameters }), null);
      }
      return this.innerOperationInvoker.Invoke(instance, inputs, out outputs);
   }
   
   // ...
}

现在,是时候谈谈服务了。哦,在我们继续之前,这里有一些需要展示——适配器接口。以下代码片段显示了它的抽象声明

 public interface IWSTransferAdapter
 {
    EndpointAddress Create(object resource);
    object Get(MessageHeaders resourceIdentifier);
    object Put(MessageHeaders resourceIdentifier,
    object resource);
    void Delete(MessageHeaders resourceIdentifier);
 }

如您所见,上述接口非常轻量级,实际上它以 CRUD 方式映射 WS-Transfer 接口。Create 方法将返回服务操作的 EndpointAddress,包括资源标识符元素。操作服务(适配器)知道资源标识符的 DataContract,因此在每次操作服务期间,很容易在 MessageHeaders 集合中找到它。

Service

WSTransferService 类派生自 ServiceAdapterBaseIWSTransfer 接口。它代表用于 WS-Transfer 消息传递的 WCF (Indigo) 服务层。此服务层可以通过其适配器用作资源的通用层

public class WSTransferService<ServiceAdapter>
  : ServiceAdapterBase<ServiceAdapter, IWSTransferAdapter>,
    IWSTransfer where ServiceAdapter : class
 {
    public WSTransferService()
    {
    }
   
    #region IWSTransfer Members
    public CreateResponse Create(CreateRequest request)
    {
       if (request == null)
          throw new ArgumentNullException("request");

       // Adapter action
       EndpointAddress resourceEndpointAddress =
                 this.Adapter.Create(request.Resource);

       // done
       return new CreateResponse(resourceEndpointAddress);
    }

    public GetResponse Get(GetRequest request)
    {
       if (request == null)
         throw new ArgumentNullException("request");

       MessageHeaders resourceIdentifier =
          OperationContext.Current.IncomingMessageHeaders;

       // Adapter action
       object resource =
              this.Adapter.Get(resourceIdentifier);

       // done
       return new GetResponse(resource);
    }

    public PutResponse Put(PutRequest request)
    {

     if (request == null)
         throw new ArgumentNullException("request");

       MessageHeaders resourceIdentifier =
          OperationContext.Current.IncomingMessageHeaders;

       // Adapter action
       this.Adapter.Put(resourceIdentifier,
                                request.Resource);

       // done
       return new PutResponse();
    }

    public DeleteResponse Delete(DeleteRequest request)
    {
       if (request == null)
         throw new ArgumentNullException("request");

       MessageHeaders resourceIdentifier =
          OperationContext.Current.IncomingMessageHeaders;
         
       // Adapter action
       this.Adapter.Delete(resourceIdentifier);

       // done
       return new DeleteResponse();
    }
    #endregion
 }

上述 ServiceAdapterBase 类的目的是封装和抽象物理适配器与 WS-Transfer 服务层。它是管道机制的一部分。请注意,所有管道实现都位于文件 ServiceAdapterBehaviorExtension.cs 中,并且可完全重用于服务/端点自定义扩展

public class ServiceAdapterBase<ServiceAdapter, AdapterInterface>
   where ServiceAdapter : class
{
    #region Properties
    private HybridDictionary _properties;
    private AdapterInterface _adapter;
 
    public AdapterInterface Adapter
    {
        get { return (AdapterInterface)_adapter; }
        set { _adapter = value; }
    }
    public HybridDictionary Properties
    {
        get { return _properties; }
    }
    #endregion

    public ServiceAdapterBase()
    {
        ServiceHost host = OperationContext.Current.Host as ServiceHost;
        if (host.Description.Behaviors.Contains(typeof(AdapterAttribute)))
        {
            AdapterAttribute aa = 
                host.Description.Behaviors.Find<AdapterAttribute>();
            _properties = aa.Parameters;
            if (typeof(ServiceAdapter) != typeof(ConfiguredAddapter))
            {
                // tightly coupled adapter (compiler)
                Adapter = (AdapterInterface)Activator.CreateInstance(
                      typeof(ServiceAdapter), new object[] { Properties });
            }
            else if (aa.AdapterType != null)
            {
                // loosely coupled adapter (config file)
                Adapter = (AdapterInterface)Activator.CreateInstance(
                              aa.AdapterType, new object[] { Properties });
            }
            else
                throw new FaultException(
                  new ConfigurationErrorsException("Can't create Adapter"));
        }
    }
}

一旦我们有了正确的适配器实例,WS-Transfer 消息就可以通过 IWSTransferAdapter 接口以透明的方式在请求/响应中传递到其处理。

适配器样板

最后,我们来到了消息处理的末端——Adapter。此类别必须针对每种特定资源类型和/或资源标识符实现。一些常见逻辑可以解耦到基类中,并重用于其他适配器,请参见 WSTransferServiceBase.cs 文件中的 WSTransferServiceBase 基类。实现方式直接且易于理解。

所以,进入自定义适配器,让我们看一些实际的例子 - MemoryStorageAdapter。这个 Adapter 包含将资源存储在内存中的逻辑。在实现它之前,资源工厂需要有一个资源标识符的 DataContract。以下代码片段显示了一个非常简单的例子

[XmlSerializerFormat]
[Serializable]
[XmlRoot(ElementName = "ResourceDescriptor",
    Namespace = "http://www.rkiss.net/schemas/sb/2005/06/servicebus")]
[DataContract(Name = "ResourceDescriptor",
    Namespace = "http://www.rkiss.net/schemas/sb/2005/06/servicebus")]
public class ResourceDescriptor
{
    #region DataMembers
    string _id;
    string _name;
    string _key;
    string _topic;
    #endregion
  
    #region Properties
    [DataMember(Order=0)]
    public string ResourceId
    {
        get { return _id; }
        set { _id = value; }
    }
    [DataMember(Order=1)]
    public string ResourceName
    {
        get { return _name; }
        set { _name = value; }
    }
    [DataMember(Order=2)]
    public string Key
    {
        get { return _key; }
        set { _key = value; }
    }
    [DataMember(Order=3)]
    public string Topic
    {
        get { return _topic; }
        set { _topic = value; }
    }
    #endregion

    #region Constructors
    public ResourceDescriptor() { }
    public ResourceDescriptor(string id) 
                 { ResourceId = "guid:" + id; }
    #endregion
}

我们有众所周知的 ResourceDescriptor 数据契约作为资源标识符的表示。然后,使用 WSTransferAdapterBase 基类,Adapter 类可能如下所示

 class MemoryStorageAdapter
    : WSTransferAdapterBase<ResourceDescriptor>,
      IWSTransferAdapter
 {
     Hashtable _storage = null;
    string strStorageName = string.Empty;
    object SyncRoot = new object();
    public Hashtable Storage
    {
       get { return _storage; }
    }
    public MemoryStorageAdapter(HybridDictionary config)
    {
       base.Properties = config;
       strStorageName = base.Properties["name"] as string;
       if(strStorageName == null)
        throw new Exception("Missing name of the Storage");

       lock (SyncRoot)
       {
          object storage =
             AppDomain.CurrentDomain.GetData(strStorageName);
          if (storage == null)
          {
             storage =
               Hashtable.Synchronized(new Hashtable());
             AppDomain.CurrentDomain.SetData(strStorageName,
                                                    storage);
          }
          _storage = storage as Hashtable;
       }
    }

    public EndpointAddress Create(object resource)
    {
       ResourceDescriptor rd =
           new ResourceDescriptor(Guid.NewGuid().ToString());

       // action
       lock (SyncRoot)
       {
          Storage.Add(rd.ResourceId, resource);
       }

       return this.ResourceEndpointAddress(null, rd);
    }

    public object Get(MessageHeaders resourceIdentifier)
    {
       ResourceDescriptor rd =
           GetResourceIdentifier(resourceIdentifier);

       // action
       lock (SyncRoot)
       {
          if (Storage.Contains(rd.ResourceId) == true)
          {
             return Storage[rd.ResourceId];
          }
       }
       throw new Exception(string.Format("The Resource " +
                 "[rid={0}] doesn't exist in the storage",
                 rd.ResourceId));
    }

    public object Put(MessageHeaders resourceIdentifier,
                                         object resource)
    {
       ResourceDescriptor rd =
               GetResourceIdentifier(resourceIdentifier);

       if (ResourceType != null && 
                   resource.GetType() != ResourceType)
          throw new NullReferenceException("The " +
              "resource type is different from factory");

       // action
       lock (SyncRoot)
       {
          if (Storage.Contains(rd.ResourceId) == true)
          {
             Storage[rd.ResourceId] = resource;
             return;
          }
       }
       throw new Exception(string.Format("The " +
           "Resource [rid={0}] doesn't exist in the storage",
           rd.ResourceId));
    }

    public void Delete(MessageHeaders resourceIdentifier)
    {
       ResourceDescriptor rd =
           GetResourceIdentifier(resourceIdentifier);

       // action
       lock (SyncRoot)
       {
          if (Storage.Contains(rd.ResourceId) == true)
          {
             Storage.Remove(rd.ResourceId);
             return;
          }
       }
       throw new Exception(string.Format("The " +
          "Resource [rid={0}] doesn't exist in the storage",
          rd.ResourceId));
    }
 }

如上所示,构造函数在 AppDomain 数据空间中以众所周知的名称创建一个新的存储;如果不存在。CRUD 方法也非常简单 - 对 Hashtable 存储执行操作。

当然,适配器实现可以根据资源类型、工厂和操作具有更复杂的逻辑。任何复杂的资源类型都不会对 WSTransfer 服务层产生影响,只会影响 Adapter 对象。这是这种设计模式的优势。

服务样板

正如我之前提到的,Level 1 还有更多的设计模式。这里还有一个例子——创建一个由资源类型驱动的服务。在这个例子中,我们没有适配器;服务需要包含特定资源类型的所有逻辑。但是,我们仍然可以重用我们的 WSTransferAdapterBase 基类来通用处理 WS-Transfer 消息传递。以下代码片段显示了这种 WS-Transfer 服务的样板。ResourceDescriptor 用作资源标识符的示例

 public class xxxService
  : WSTransferAdapterBase<ResourceDescriptor>, IWSTransfer
{

    #region IWSTransferFactory Members
    public CreateResponse Create(CreateRequest request)
    {
       // create descriptor
       ResourceDescriptor rd =
         new ResourceDescriptor(Guid.NewGuid().ToString());

       // todo: store the resource

       // add descriptor to the address
       EndpointAddress resourceEndpointAddress =
               base.ResourceEndpointAddress(null, rd);

       // done
       return new CreateResponse(resourceEndpointAddress);
    }
    #endregion

    #region IWSTransferOperation Members
    public GetResponse Get(GetRequest request)
    {
       // resource body
       object resource = null;

       // get resource identifier
       MessageHeaders resourceIdentifier =
          OperationContext.Current.IncomingMessageHeaders;

       // get descriptor
       ResourceDescriptor rd =
          GetResourceIdentifier(resourceIdentifier);

       // todo: get resource state

       // done
       return new GetResponse(resource);
    }

    public PutResponse Put(PutRequest request)
    {
       // resource body
       object resource = request.Resource;

       // get resource identifier
       MessageHeaders resourceIdentifier =
          OperationContext.Current.IncomingMessageHeaders;

       // get descriptor
       ResourceDescriptor rd =
          GetResourceIdentifier(resourceIdentifier);

       // todo: update resource state

       // done
       return new PutResponse();
    }

    public DeleteResponse Delete(DeleteRequest request)
    {
       // get resource identifier
       MessageHeaders resourceIdentifier =
          OperationContext.Current.IncomingMessageHeaders;

       // get descriptor
       ResourceDescriptor rd =
          GetResourceIdentifier(resourceIdentifier);
                 
       // todo: delete resource

       // done
       return new DeleteResponse();
    }
    #endregion
}

级别 2 - 消费者

消费者端由基于 ServiceContract 的工厂和操作代理表示。如果消费者端由 Indigo 技术驱动,则可以使用 request/response 核心对象以编程方式轻松实现以下代理。第一个类 WSTransferFactoryProxy 负责发送 WS-Transfer 创建消息;操作代理是根据其响应创建的

/// <summary>Factory Proxy to the WSTransfer Service
/// </summary>
 public class WSTransferFactoryProxy 
     : ProxyBase<IWSTransferFactory>, IWSTransferFactory
 {
    #region Constructors
    public WSTransferFactoryProxy(){}

    public WSTransferFactoryProxy(string configurationName)
       : base(configurationName){}

    public WSTransferFactoryProxy(Binding binding)
       : base(binding){}

    public WSTransferFactoryProxy(Binding binding,
                                    EndpointAddress address)
       : base(address, binding) { }
    #endregion

    #region IWSTransferFactory
    public CreateResponse Create(CreateRequest request)
    {
       return base.Channel.Create(request);
    }
    #endregion

    #region API (Business Methods)
    public WSTransferOperationProxy Create(object resource)
    {
       CreateResponse response =
         base.Channel.Create(new CreateRequest(resource));
       return new WSTransferOperationProxy(base.Endpoint.Binding,
                                          response.EndpointAddress);
    }
    #endregion
 }

 /// <summary>Operation Proxy to the WSTransfer Service
 /// </summary>
 public class WSTransferOperationProxy :
               ProxyBase<IWSTransferOperation>,
    IWSTransferOperation
 {
    #region Constructors
    public WSTransferOperationProxy(){}

    public WSTransferOperationProxy(string configurationName)
       : base(configurationName){}

    public WSTransferOperationProxy(Binding binding,
                                      EndpointAddress address)
       : base(address, binding) { }
    #endregion

    #region IWSTransferOperation Members
    public GetResponse Get(GetRequest request)
    {
       return base.Channel.Get(request);
    }
    public PutResponse Put(PutRequest request)
    {
       return base.Channel.Put(request);
    }
    public DeleteResponse Delete(DeleteRequest request)
    {
       return base.Channel.Delete(request);
    }
    #endregion

    #region API (Business) Methods
    public object Get()
    {
       GetResponse response = Get(new GetRequest());
       return response.Resource;
    }
    public void Put(object resource)
    {
       PutResponse response =
               Put(new PutRequest(resource));
    }
    public void Delete()
    {
       DeleteResponse response =
                 Delete(new DeleteRequest());
    }
    #endregion
 }

上面显示的第二个代理类 WSTransferOperationProxy 非常轻量级,它是 request/response 对象的包装器。

我们可以在以下代码片段中看到上述代理的用法。我已将与 WS-Transfer 消息传递相关的代码行加粗。第一步是创建 WSTransferFactoryProxy 的实例。在此示例中,已使用配置文件 - 请参见下一个代码片段。

步骤非常简单

  • 创建新工厂,
  • 创建资源,
  • 获取资源,
  • 更新资源,
  • 获取更新后的资源,
  • 删除资源,
  • 关闭操作,
  • 关闭工厂。

请注意,第二个测试示例显示了另一种资源(CreateResouce 对象)传递给工厂。它是基于特定工厂与其消费者之间众所周知的 DataContract

using (WSTransferFactoryProxy factory =
 new WSTransferFactoryProxy("LocalStorageFactory"))
{
    Console.WriteLine("Test 1: Create a simply resource ");
    object myResource = "urn:This_is_a_Test_1";
  
    WSTransferOperationProxy operation =
                      factory.Create(myResource);
   
    Console.WriteLine("\n Press any key to 'Get Resource'");
    Console.ReadLine();
 
    object resource = operation.Get();
    Util.ShowMe(resource);
   
    Console.WriteLine("\n Press any key to 'Put Resource'");
    Console.ReadLine();
    object myNewResource = Guid.NewGuid();
   
    operation.Put(myNewResource);
   
    Console.WriteLine("\n Press any key to 'Get Resource'");
    Console.ReadLine();
  
    resource = operation.Get();
    Util.ShowMe(resource);
   
    Console.WriteLine("\n Press any key to 'Delete Resource'");
    Console.ReadLine();
 
    operation.Delete();
   
    Console.WriteLine("Test 2: Create/Get/Delete complex type");
    CreateResource createResource = new CreateResource();
    createResource.ResourceDescriptor =
       new ResourceDescriptor(Guid.NewGuid().ToString());
    createResource.Resource = "urn:This_is_a_Test_2";
  
    operation = factory.Create(createResource);
    resource = operation.Get();
    operation.Delete();
    Util.ShowMe(resource);
   
    operation.Close();
}

这是它的配置文件。请注意,客户端有两个端点,一个用于 WS-Transfer 工厂,另一个用于 WS-Transfer 操作

<system.serviceModel>
  <client>
    <endpoint name="PublicStorageFactory"
      behaviorConfiguration="logging"
      address="net.tcp://:11111/PublicStorage"
      binding="customBinding"
      bindingConfiguration="Binding1"
      contract="RKiss.WSTransfer.IWSTransferFactory"/>
      
    <endpoint name="PublicStorage"
      behaviorConfiguration="logging"
      address="net.tcp://:11111/PublicStorage"
      binding="customBinding"
      bindingConfiguration="Binding1"
      contract="RKiss.WSTransfer.IWSTransfer" />
  </client>

  <bindings>
    <customBinding>
      <binding name="Binding1">
        <tcpTransport/>
      </binding>
    </customBinding>
  </bindings>
</system.serviceModel>

测试

好的,是时候演示一个测试了。我已包含一个小的 WS-Transfer 客户端和控制台主机进程。以下屏幕显示了这两个交互。请注意,消息由基于 MessageInterceptors 的可配置微型控制台记录器发布。我已将这个“小”记录器项目包含在下载中。请注意,控制台记录器程序集必须安装在 GAC 中。

1. 启动 ServiceHost 程序

2. 启动客户端应用程序,并检查创建请求/响应消息。然后,按任意键查看下一对 WS-Transfer 消息

结论

在本文中,我描述了 WS-Transfer 消息传递及其使用 Indigo 通信模型的实现。连接系统中的 WS-Transfer 架构风格能够将业务层从“物理”资源层(任何信息片段)中封装起来。工厂和操作等一对服务,以透明的方式基于资源标识符协同工作。此外,应用程序模型中的 WS-Transfer 风格能够创建具有即插即用技术的逻辑服务总线。我强烈建议您阅读参考部分中提供的链接中有关 WS-Transfer (REST) 风格的更多信息。

参考文献

© . All rights reserved.