Click here to Skip to main content
Click here to Skip to main content

Exporting Annotations from WCF

, 7 Oct 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
This article describes the design and implementation of the WCF extension for exporting the wsdl documentation and schema annotation.

Contents

Features

  • Loosely coupled design pattern
  • Declaratively annotation and control using a custom attribute
  • Implicitly annotation using standard C# XML Comments
  • Configuration using the service behavior extension
  • Wsdl documentation from wcf service and operation contracts
  • DataContract annotation
  • MessageContract annotation
  • IXmlSerializable annotation
  • POCO (Plain old C# objects) annotation (only for .netFx 3.5 SP1)

Introduction and Concept

The wsdl 1.1 and xml schema include an option, where each element (component) can be annotated. The annotation is not a part of the service metadata. It is data about the data, to inform the service consumer in the human readable style about the metadata. For this purpose, the annotation can have any text representation style, for instance: html tags. Annotations do not participate in the validation process. For interoperability reason, the annotation component is not recommended to use as an additional exchange pattern or context between the service and its consumer.

The following picture is a screen snippet from the WSDL 1.1 spec: 

As you can see in the above picture, there are lines such as 5, 8, 14, 19, etc. for wsdl component documentation and the spec said:

2.1.4 Documentation

WSDL uses the optional wsdl:document element as a container for human readable documentation. The content of the element is arbitrary text and elements ("mixed" in XSD). The documentation element is allowed inside any WSDL language element.

The current release of the .NetFX 3.5 SP1 still doesn't have a built-in feature for annotating a wsdl document for any of its component. On the other hand, the WCF has a strong extensibility parading for plugging-in a custom annotator to describe your service metadata, as you will see more details later in this article. 

Now, the questions are coming. Why and what do I need to annotate in the wsdl document? How much do I do it, etc.?

Well, there is no unique answer and guidelines for that. This article shows implementation of one example of this guideline, where the service contract is divided into two parts, such as partitioned wsdl documentations and xml schema annotation for object contract.

1. Service Contract Documentation in the WSDL Metadata

For documenting Service and Operation Contract in the WCF Service we can use C# Xml Comments and custom attribute such as DocumentationAttribute. The following picture shows an example of this annotation:

The documentation of the ServiceContract decorated by custom attribute and comment lines is mapped into the <wsdl:service .../> components like it is shown in the following picture:

The documentation of the OperationContract decorated by custom attribute and comment lines is mapped into the <wsdl:operation .../> components like it is shown in the following picture:

As you can see, there are only two wsdl components for mapping documentation from the WCF Service. That's the feature which is supported by this article.

The second part of the WCF Service metadata annotation is related to the object and its serializable elements. In the above example, we have one object in the OperationContract such as MyMessageContract. Let's look at how it can be annotated.

2. Object annotation in the Xml Schema Metadata

In this case, similarly to previous one, we have C# Xml Comment lines and custom attribute such as AnnotationAttribute. The major difference between these two cases is a target of the annotation. In the first case, described earlier, the target is the wsdl documentation section located in the separate service metadata section. In the second one, which is always related to the objects, the target is the XML Schema in the MetadataSection. Note, the number of sections in the metadata container is not limited and basically is depended on the service contract complexity.

Ok, let's continue with the following picture, where the MessageContract object is shown.

As the above code snippet shows, there are standard C# Xml Comment lines and additional custom attributes for annotation in the class. Note, the custom AnnotationAttribute is an optional attribute to the C# XML Comment lines. We can use this attribute for lightweight annotation only.

The following picture shows a snippet from the annotated XML Schema located in the metadata section:

As you can see in the above snippet, the MessageBodyMember is a complex type. In this example, this object is serialized by DataContractSerializer which is a default serializer for WCF contract. The class and its properties are decorated differently than the MessageContract. From the annotation point of the view, these is no difference how the source class is written, see the following picture:

OK, but here is something different. The custom AnnotationAttribute has additional properties to control the annotation exporting process. The following picture gives you more details:

As the above example shows, the xs:annotation/xs:appinfo element has Surrogate child element. This is a feature of the XsdDataContractExporter.Option.DataContractSurrogate property. This is the place where we can plug-in our custom surrogate for annotation. In this article, the Annotation class has been created for this purpose, see later for more details. Switching between the string type and custom surrogate type is done by control property in the AnnotationAttribute. The above picture shows a result of this selection in the FirstName element, where surrogate is a string type. 

The other feature of the WCF Service annotation is having the capability for controlling the annotation in the export metadata from the service. This task can be accomplished by custom service behavior and configuration in the config section. The following code snippet shows this extension:

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

The <annotation .../> element represents a master switcher for using hard coded C# Xml Comments and/or custom attributes such as DocumentationAttribute and AnnotationAttribute. Note, that this extension is an optional to the hardcoded setup such as enable=true and exportAsText=false (using a surrogate annotation object).

My solution also includes a small tool called MetadataViewer (see the following picture) that obtains the metadata from any public endpoint or contract assembly. This should provide better/full picture about the exported WCF Service Metadata. Using an embedded XmlNotepad2007 form, we can see the Metadata Tree. According to my earlier description, there are more than one MetadataSection. The first one is occupied by wsdl:definitions and the other ones by XML schemas. Note, the MetadataViewer is a standalone small tool that can be used for retrieving metadata from wsdl and mex endpoints (web services and wcf services) with a standard binding. 

Ok, back to the initial thinking about the annotations in the Service Metadata.

Basically the service metadata such as wsdl document and XML schemas can be annotated in the following ways:

  • on-line, as part of the generating service metadata export based on the operation request such as wsdl or mex
  • off-line, where an exported service metadata is annotated by external tool, for instance: Altova

As you can see, both cases require public access to the existing service, in other words, the service has to be deployed and ran. The other case is an annotation of the metadata during the modeling time, where metadata is created in the Contract First fashion, for example: please see my recent article Contract Model for Manageable Services. In this case, the annotations are stored in the Repository like another resource. This approach will be more interesting, when upcoming Microsoft new model driven platform (OSLO) will be introduced at PDC 2008 at the end of this month.  

Ok, that's all for now, I hope you got the overall picture about the annotation in the service metadata. Let's start digging in the design and implementation of the WCF Service Extension for exporting annotations.

Design and Implementation

The design and implementation of the metadata annotation in the WCF Service model is based on the System.ServiceModel.Description.WsdlExporter extension by implementing its interface IWsdlExportExtension methods such as ExportContract and ExportEndpoint. Plumbing these methods on the endpoint or contract behaviors we can get the control for customizing exported metadata.

The following picture shows a class diagram of the DocumentationAttribute object for intercepting WsdlExport process:

The DocumentAttribute class implements few interfaces such as IServiceBehavior interface for passing the configuration properties from the behavior extension, IContractBehavior and IOperationBehavior for injecting a custom DataContractAnnotationSurrogate and of course the IWsdlExportExtension interface.

Implementation of these interfaces are straightforward and well documented in the MSDN and WCF Samples, where examples for custom DataContract Surrogate and Wsdl Documentation are available.

I am going to focus only on the implementation of the MessageContract annotation, which is not supported in the same way as the built-in capability for DataContract serializer option.

For this purpose, the following class has been designed to encapsulate the annotation process of the class decorated by MessageContractAttribute:

The MessageContractAnnotation.Export method is invoked by IWsdlExportExtension.ExportEndpoint and its implementation is shown in the following code snippet:

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;
    }
  }
}

I mentioned earlier that, the DataContract serializer has a built-in option to inject a custom surrogate for annotating metadata components. In the case of the MessageContract, this feature is missing, therefore the challenge was to find the way to intercept and customize the metadata exporting process.

The process of the MessageContract exporting is driven by internal class such as XmlSerializerMessageContractExporter. Its abstract class has a protected nested class MessageExportContext, see the following code snippet from the Reflector: 

When the IWsdlExportExtension.ExportEndpoint will call our interceptor, all dictionaries in the above class are already populated. For the annotation process, we need to have an ElementTypes dictionary. The following code snippet from the Reflector shows this class:

Again, as you can see, the class is internal, therefore I used, in the above Export method, reflection to obtain XmlSchemaElement and related OperationDescription. The instance of the XmlSerializerMessageContractExporter is obtained from the WsdlExporter.State.  

Once we have access to the XmlSchemaElement and its operation, the rest of the implementation is very light and straightforward. Let's look at how the MessageHeader annotation is implemented, the following code snippet shows that:

internal static bool ExportAnnotationForMessageHeaders(
  WsdlExporter exporter, OperationDescription operation, XmlSchemaElement elem)
{
  bool retVal = false;
  
  MessageDescription md = 
    operation.Messages.Cast<messagedescription />().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;
}

As you can see, the first step is to query the OperationDescription to get the MessageDescription with a specific header name. Then, we can obtain an AnnotationAttribute from the memberInfo and then invoke the helper method to create a target XmlSchemaAnnotation element - see the following code snippet:

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;
}

The responsibility of the above method is to create an annotation element  XmlSchemaElement  based on the C# Xml Comments and AnnotationAttribute. There is a XmlDocumentation class for encapsulating the logic in order to obtain the C# Xml Comments element published in the specific file. This logic is divided into two steps, where the first step is to create a unique name based on the class member info such as XmlDocumentation.CreateMemberName method and the second step is to use this unique name for querying the xml documentation file to get the xml elements -  method XmlDocumentation.Load.

Note that using the C# XML Comments must be enabled in the project by setting a path to the file, where these comments are going to be stored in the flat xml fashion.

That's all, let's do some testing.

Installation and Testing

The solution for WCF Service Extension for metadata annotation is divided into the following 3 projects:

The first project is a standalone Windows Form application for retrieving metadata from any wsdl or mex enabled endpoints. In addition, there is a feature for getting the metadata of the service contract located in the assembly. The second project is a host console program for demonstrating different contracts and operations. Finally, the third project is WcfAnnotation that includes full implementation described in this article. This assembly must be included to your solution in order to use the features of this article.

Note, the WcfAnnotation solution has been written and tested in the Visual Studio 2008 SP1 + .NetFx 3.5 SP1.

Testing annotations in the WCF Service Metadata is very straightforward based on the following steps:

  1. Launch the TestServer console program
  2. Launch the MetadataViewer
  3. Click on Get button to get  metadata from the url endpoint (for instance: net.pipe://localhost/wsdldoc/mex)
  4. Check metadata in the TreeView or Xml tab

The following picture shows a portion of the last MetadataSection for TerminateAt element:

That's all. You are ready to annotate your WCF Service. Please, have a look at the Contracts.cs file in the TestServer project for usage of these custom attributes.

Conclusion

This article described an extension to WCF Service for exporting Metadata annotations. This feature is not built-in the WCF paradigm, but it can be easily added. The annotation is an optional element in the Metadata and having this human readable information embedded in the wsdl documentation and/or xml schemas is giving more explanation about the service contract for its consumers.

Upcoming Microsoft vision for modeling platform, the metadata are stored in the repository where they play a key position between the logical model and decentralized runtime components (services, applications, etc.). Having more annotations of the metadata is a significant advantage for modeling and discovery. The Repository can easily hold thousands of resources such as endpoints, bindings, contracts, messages, schemas,  policies, etc. The annotation will be a good helper for discovery and managing metadata in the Repository.     

We can hear more details about the modeling strategy at the Microsoft PDC 2008 conference, where project OSLO and its technologies will be introduced. See you there.       

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Roman Kiss
Software Developer (Senior)
United States United States
No Biography provided

Comments and Discussions

 
GeneralGreat article / code, bug in asp/wcf development server PinmemberDerek Bartram8-Sep-09 7:20 
GeneralRe: Great article / code, bug in asp/wcf development server PinmemberDerek Bartram8-Sep-09 7:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 7 Oct 2008
Article Copyright 2008 by Roman Kiss
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid