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

从 WCF 导出注解

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (16投票s)

2008年10月7日

CPOL

11分钟阅读

viewsIcon

85943

downloadIcon

1288

本文介绍用于导出 wsdl 文档和 schema 注释的 WCF 扩展的设计和实现。

目录

特点

  • 松耦合设计模式
  • 使用自定义属性声明式注释和控制
  • 使用标准的 C# XML 注释进行隐式注释
  • 使用服务行为扩展进行配置
  • 来自 wcf 服务的 wsdl 文档和操作契约
  • DataContract 注释
  • MessageContract 注释
  • IXmlSerializable 注释
  • POCO(纯粹的 C# 对象)注释(仅适用于 .netFx 3.5 SP1)

引言与概念

wsdl 1.1 和 xml schema 包含一个选项,其中每个元素(组件)都可以被注释。注释不是服务元数据的一部分。它是关于数据的元数据,用于以人类可读的方式告知服务使用者元数据。为此,注释可以采用任何文本表示样式,例如:html 标签。注释不参与验证过程。出于互操作性原因,不建议使用注释组件作为服务与其使用者之间的附加交换模式或上下文。

下图是 WSDL 1.1 规范中的一个屏幕截图:

如上图所示,有诸如 5、8、14、19 等行用于 wsdl 组件文档,规范中写道:

2.1.4 文档

WSDL 使用可选的 wsdl:document 元素作为人类可读文档的容器。该元素的内容是任意文本和元素(XSD 中的“混合”)。文档元素允许出现在任何 WSDL 语言元素中。

.NetFX 3.5 SP1 的当前版本仍然没有内置功能来注释其任何组件的 wsdl 文档。另一方面,WCF 具有强大的可扩展性模式,用于插入自定义注释器来描述服务元数据,正如本文稍后会看到的。

现在,问题来了。为什么要以及需要在 wsdl 文档中注释什么?如何操作等等?

嗯,没有唯一的答案和指导方针。本文展示了一个遵循此指导方针的示例实现,其中服务契约分为两部分,例如分区的 wsdl 文档和对象契约的 xml schema 注释。

1. WSDL 元数据中的服务契约文档

为了记录 WCF 服务中的服务和操作契约,我们可以使用 C# Xml 注释和自定义属性,例如 DocumentationAttribute。下图显示了一个此注释的示例。

由自定义属性和注释行装饰的 ServiceContract 的文档被映射到 <wsdl:service .../> 组件,如下图所示。

由自定义属性和注释行装饰的 OperationContract 的文档被映射到 <wsdl:operation .../> 组件,如下图所示。

正如您所见,只有两个 wsdl 组件用于映射来自 WCF 服务的文档。这是本文支持的功能。

WCF 服务元数据注释的第二部分与对象及其可序列化元素相关。在上面的示例中,我们在 OperationContract 中有一个对象,例如 MyMessageContract。让我们看看如何注释它。

2. Xml Schema 元数据中的对象注释

在这种情况下,与前一种情况类似,我们有 C# Xml 注释行和自定义属性,例如 AnnotationAttribute。这两种情况之间的主要区别在于注释的目标。在前面描述的第一种情况下,目标是位于单独的服务元数据部分的 wsdl 文档。在第二种情况下,它始终与对象相关,目标是 MetadataSection 中的 XML Schema。请注意,元数据容器中的节数没有限制,基本上取决于服务契约的复杂性。

好的,我们继续看下图,显示了 MessageContract 对象。

如上面的代码片段所示,有标准的 C# Xml 注释行和用于类注释的附加自定义属性。请注意,自定义 AnnotationAttribute 是 C# XML 注释行的可选属性。我们可以仅使用此属性进行轻量级注释。

下图显示了位于元数据部分的注释 XML Schema 的片段。

如上片段所示,MessageBodyMember 是一个复杂类型。在此示例中,此对象由 DataContractSerializer 序列化,这是 WCF 契约的默认序列化器。该类及其属性的装饰方式与 MessageContract 不同。从注释的角度来看,源类的编写方式没有区别,请看下图。

好的,但这里有些不同。自定义 AnnotationAttribute 具有其他属性来控制注释导出过程。下图提供了更多详细信息。

如上例所示,xs:annotation/xs:appinfo 元素具有 Surrogate 子元素。这是 XsdDataContractExporter.Option.DataContractSurrogate 属性的一个特性。这是我们可以插入自定义代理以进行注释的地方。在本文中,为此创建了 Annotation 类,稍后将详细介绍。字符串类型和自定义代理类型之间的切换是通过 AnnotationAttribute 中的控制属性完成的。上图显示了在 FirstName 元素中进行此选择的结果,其中代理是 string 类型。

WCF 服务注释的另一个特性是能够从服务控制导出元数据的注释。此任务可以通过自定义服务行为和 config 部分中的配置来完成。下面的代码片段显示了这个扩展。

<behaviors>
   <serviceBehaviors>
      <behavior name='mex'>
         <serviceMetadata/>
         <annotation enable='true' exportAsText='false'/>
      </behavior>
   </serviceBehaviors>
</behaviors>

<annotation .../> 元素表示用于使用硬编码的 C# Xml 注释和/或自定义属性(如 DocumentationAttribute AnnotationAttribute )的 master 开关。请注意,此扩展是可选的,与硬编码设置(如 enable=trueexportAsText=false(使用代理注释对象))结合使用。

我的解决方案还包括一个名为 MetadataViewer 的小工具(请参阅下图),它可以从任何公共终结点或契约程序集获取元数据。这应该能提供导出的 WCF 服务元数据的更好/完整的图景。使用嵌入的 XmlNotepad2007 表单,我们可以看到元数据树。根据我之前的描述,元数据节不止一个。第一个节由 wsdl:definitions 占用,其他节由 XML 模式占用。请注意,MetadataViewer 是一个独立的、小型的工具,可用于从具有标准绑定的 wsdl 和 mex 终结点(Web 服务和 wcf 服务)检索元数据。

好的,回到关于服务元数据中注释的初始想法。

基本上,服务元数据(如 wsdl 文档和 XML 模式)可以通过以下方式进行注释:

  • 在线,作为生成服务元数据导出的一部分,基于操作请求,例如 wsdl 或 mex。
  • 离线,已导出的服务元数据由外部工具注释,例如:Altova

可以看出,这两种情况都需要对现有服务进行公共访问,换句话说,服务必须已部署并运行。另一种情况是在建模时注释元数据,其中元数据以契约优先的方式创建,例如:请参阅我最近的文章 用于可管理服务的契约模型。在这种情况下,注释将作为另一个资源存储在存储库中。当即将到来的微软新模型驱动平台(OSLO)在月底的 PDC 2008 上推出时,这种方法将更有趣。

好的,就到这里了,希望您对服务元数据中的注释有了整体的了解。让我们开始深入研究 WCF 服务扩展的设计和实现,以导出注释。

设计与实现

WCF 服务中元数据注释的设计和实现基于 System.ServiceModel.Description.WsdlExporter 扩展,通过实现其接口 IWsdlExportExtension 的方法,例如 ExportContract ExportEndpoint。通过在终结点或契约行为上配置这些方法,我们可以控制导出元数据。

下图显示了 DocumentationAttribute 对象的类图,用于拦截 WsdlExport 过程。

DocumentAttribute 类实现了几个接口,例如 IServiceBehavior 接口,用于从行为扩展传递配置属性;IContractBehavior IOperationBehavior ,用于注入自定义 DataContractAnnotationSurrogate ;当然还有 IWsdlExportExtension 接口。

这些接口的实现很简单,并且在 MSDN 和 WCF 示例中有详细记录,其中提供了自定义 DataContract Surrogate 和 Wsdl 文档的示例。

我将只关注 MessageContract 注释的实现,它不像 DataContract 序列化器选项那样具有内置功能。

为此,设计了以下类来封装由 MessageContractAttribute 装饰的类的注释过程。

MessageContractAnnotation.Export 方法由 IWsdlExportExtension.ExportEndpoint 调用,其实现如下面的代码片段所示。

public static void Export(WsdlExporter exporter, WsdlEndpointConversionContext context)
{
  // MessageContractExporter
  object messageContractExporterObj = null;
  Type type = Type.GetType(
    "System.ServiceModel.Description.MessageContractExporter+MessageExportContext, 
      System.ServiceModel, Version=3.0.0.0, Culture=neutral, 
      PublicKeyToken=b77a5c561934e089");
       
  if (exporter.State.TryGetValue(type, out messageContractExporterObj))
  {
    // walk through the Elements
    IDictionary o = 
      (IDictionary)messageContractExporterObj.GetType().GetField("ElementTypes",
        BindingFlags.NonPublic | BindingFlags.Instance).GetValue(
        messageContractExporterObj);
       
    foreach (var item in o.Values)
    {
      XmlSchemaElement elem = 
        (XmlSchemaElement)item.GetType().GetProperty("Element", 
          BindingFlags.NonPublic | BindingFlags.Instance).GetValue(item, null);
       
      OperationDescription operation = 
       (OperationDescription)item.GetType().GetProperty("Operation", 
         BindingFlags.NonPublic | BindingFlags.Instance).GetValue(item, null);

      var operAnnotation = 
       operation.SyncMethod.GetCustomAttributes(typeof(DocumentationAttribute), 
         false).FirstOrDefault() as DocumentationAttribute;
       
      if (operAnnotation != null)
      {
        elem.Annotation = 
         CreateXmlSchemaAnnotation(operAnnotation.Documentation, 
          operAnnotation.ExportXmlDoc, operation.SyncMethod);
      }

      // [MessageBody]
      if (ExportAnnotationForMessageBody(exporter, operation, elem)) continue;

      // [MessageHeader]
      if (ExportAnnotationForMessageHeaders(exporter, operation, elem)) continue;

      //  multi-parts
      if (ExportAnnotationForMessageParameters(exporter, operation, elem)) continue;
    }
  }
}

我之前提到,DataContract 序列化器有一个内置选项,可以注入自定义代理来注释元数据组件。在 MessageContract 的情况下,缺少此功能,因此挑战在于找到一种方法来拦截和自定义元数据导出过程。

MessageContract 导出的过程由内部类(如 XmlSerializerMessageContractExporter)驱动。其抽象类有一个受保护的嵌套类 MessageExportContext,请参阅 Reflector 中的以下代码片段:

IWsdlExportExtension.ExportEndpoint 调用我们的拦截器时,上述类中的所有字典都已填充。对于注释过程,我们需要一个 ElementTypes 字典。上面的 Reflector 代码片段显示了这个类。

同样,正如您所见,该类是内部的,因此我在上面的 Export 方法中使用了反射来获取 XmlSchemaElement 和相关的 OperationDescriptionXmlSerializerMessageContractExporter 的实例是从 WsdlExporter.State 中获取的。

一旦我们获得了对 XmlSchemaElement 及其操作的访问权,其余的实现就非常轻量且直接了。让我们看看如何实现 MessageHeader 注释,下面的代码片段显示了这一点。

internal static bool ExportAnnotationForMessageHeaders(
  WsdlExporter exporter, OperationDescription operation, XmlSchemaElement elem)
{
  bool retVal = false;
  
  MessageDescription md = 
    operation.Messages.Cast().FirstOrDefault(
     e=>e.Headers.Count > 0 && e.Headers.FirstOrDefault(h=>h.Name ==elem.Name)!=null);
     
  if (md != null)
  {
    MemberInfo mi = md.Headers.FirstOrDefault(h => h.Name == elem.Name).MemberInfo;
    
    var attribute = mi.GetCustomAttributes(
      typeof(AnnotationAttribute), false).FirstOrDefault() as AnnotationAttribute;
    
    if (attribute != null)
    {
      elem.Annotation = CreateXmlSchemaAnnotation(
        attribute.Annotation, attribute.ExportXmlDoc, mi);
    }
    retVal = true;
  }
  return retVal;
}

正如您所见,第一步是查询 OperationDescription 以获取具有特定头名称的 MessageDescription。然后,我们可以从 memberInfo 获取 AnnotationAttribute ,然后调用辅助方法来创建目标 XmlSchemaAnnotation 元素 - 请参阅以下代码片段。

internal static XmlSchemaAnnotation CreateXmlSchemaAnnotation(
  string text, bool bExportXmlDoc, MemberInfo memberInfo)
{
  XElement element = null;
  XmlDocument doc = new XmlDocument();

  if (memberInfo != null && bExportXmlDoc)
  {
    Type type = 
      memberInfo.DeclaringType == null ? (Type)memberInfo : memberInfo.DeclaringType;
    string memberName = XmlDocumentation.CreateMemberName(memberInfo);
    element = XmlDocumentation.Load(memberName, type);
  }
  if (element == null)
    element = new XElement("member", new XText(text));
  else
    element.AddFirst(new XText(text));

  doc.LoadXml(element.ToString());

  XmlSchemaAnnotation annotation = new XmlSchemaAnnotation();
  XmlSchemaDocumentation documentation = new XmlSchemaDocumentation();
  documentation.Markup = doc.DocumentElement.ChildNodes.Cast<XmlNode>().ToArray();
  annotation.Items.Add(documentation);

  return annotation;
}

上述方法的职责是根据 C# Xml 注释和 AnnotationAttribute 创建一个注释元素 XmlSchemaElement。有一个 XmlDocumentation 类用于封装逻辑,以获取在特定文件中发布的 C# Xml 注释元素。此逻辑分为两个步骤,第一步是创建基于类成员信息的唯一名称(例如 XmlDocumentation.CreateMemberName 方法),第二步是使用此唯一名称查询 xml 注释文件以获取 xml 元素 - 方法 XmlDocumentation.Load

请注意,必须在项目中启用 C# XML 注释,方法是将指向该文件的路径设置为,这些注释将以扁平的 XML 格式存储。

好了,让我们做一些测试。

安装与测试

WCF 服务扩展的元数据注释解决方案分为以下 3 个项目。

第一个项目是一个独立的 Windows 窗体应用程序,用于从任何支持 wsdl 或 mex 的终结点检索元数据。此外,还有一个功能可以获取程序集中服务的契约元数据。第二个项目是一个主机控制台程序,用于演示不同的契约和操作。最后,第三个项目是 WcfAnnotation,它包含了本文所述的完整实现。您必须将此程序集包含到您的解决方案中,才能使用本文的功能。

注意,WcfAnnotation 解决方案已在 Visual Studio 2008 SP1 + .NetFx 3.5 SP1 中编写和测试。

在 WCF 服务元数据中测试注释非常简单,基于以下步骤:

  1. 启动 TestServer 控制台程序。
  2. 启动 MetadataViewer
  3. 点击 Get 按钮从 url 终结点获取元数据(例如:net.pipe://localhost/wsdldoc/mex)。
  4. 在 TreeView 或 Xml 选项卡中检查元数据。

下图显示了 TerminateAt 元素的最后一个 MetadataSection 的一部分。

好了。您已准备好注释您的 WCF 服务。请查看 TestServer 项目中的 Contracts.cs 文件,了解这些自定义属性的用法。

结论

本文介绍了一个 WCF 服务扩展,用于导出元数据注释。此功能不是 WCF 范式内置的,但可以轻松添加。注释是元数据中的一个可选元素,将这些人类可读的信息嵌入到 wsdl 文档和/或 xml 模式中,可以为服务契约的消费者提供更多解释。

微软即将推出的模型平台愿景中,元数据存储在存储库中,它们在逻辑模型和去中心化的运行时组件(服务、应用程序等)之间起着关键作用。拥有更多的元数据注释对于建模和发现具有显着优势。存储库可以轻松存储成千上万个资源,如终结点、绑定、契约、消息、模式、策略等。注释将有助于在存储库中发现和管理元数据。

我们可以在 Microsoft PDC 2008 会议上听到更多关于建模策略的详细信息,届时将介绍 OSLO 项目及其技术。下次见。

参考文献

© . All rights reserved.