A Sample IWsdlExportExtension for WCF

I've been playing for a few hours with implementing an IWsdlExportExtension for Windows Communication Foundation in RC1, and thought some of you might be interested in this little sample. Basically you implement IWsdlExportExtension as a way to customize the WSDL generation process for a given service contract or service endpoint. Here I'll show a simple extension that will add a default notice (say a copyright notice) to the WSDL and XSDs that WCF generates for a given service.

Where to implement it

IWsdlExportExtension needs to be implemented in either a Contract or an Endpoint Behavior itself. Either way is possible, but while trying this I found out some differences between both approaches that are not immediately apparent from the documentation.

The first is that the IWsdlExportExtension interface is composed of two different methods: ExportContract() and ExportEndpoint(). You need to be aware that in normal cases, the WCF runtime will call only one of this methods on your extension: If the extension is a contract behavior, then you'll want to implement ExportContract(); if it is an endpoint behavior you'll want to implement ExportEndpoint() instead.

My first try was to implement my custom extension as a contract behavior. That worked, but it only got me so far. What I discovered was that if the extension is a contract behavior [1], then when ExportContract() is called only the base WSDL description for the service contract has been created, but none of the supporting XML Schemas will be available yet. However, if you implement the extension as an endpoint behavior, then it seems this is done late enough in the game for you to be able to access (and modify) the generated schemas, which is a good thing.

For our sample extension we'll be implementing an endpoint behavior, which means our class will also need to implement IEndpointBehavior. The good news is that this implementation can be empty, since we don't actually need to implement any of the methods in the interface, and it's just a way to get WCF to use our IWsdlExportExtension.

Our Implementation

With that out of the way, implementing our extension is failry simple process. Here's what we want it to do:

  • We'll define a custom setting for our app.config file that holds the message we want to add to our exported WSDL and XSDs; we'll call it WsdlNotice.
  • For any exported WSDL, we want to set the top-level <documentation/> element to whatever is configured in WsdlNotice.
  • For any exported schema files, we'll want to add a new top-level XSD annotation, on which we'll set the <xsd:documentation/> element to whatever is configured in WsdlNotice

So, here's our implementation with all the heavy lifting done inside the ExportEndpoint() method:

//
// WsdlNoticeBehavior.cs
//
// Author:
//    Tomas Restrepo (tomasr@mvps.org)
//
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Web.Services;
using System.Web.Services.Description;
using WsdlDescription = System.Web.Services.Description.ServiceDescription;
 
namespace Winterdom.ServiceModel.Extensions
{
 
   public class WsdlNoticeBehavior
      : IWsdlExportExtension, IEndpointBehavior
   {
 
      #region IWsdlExportExtension Implementation
      //
      // IWsdlExportExtension Implementation
      //
      public void ExportContract(
            WsdlExporter exporter, 
            WsdlContractConversionContext context
         )
      {
         // never called
      }
 
      public void ExportEndpoint(
            WsdlExporter exporter, 
            WsdlEndpointConversionContext context
         )
      {
         foreach ( WsdlDescription wsdl in exporter.GeneratedW
sdlDocuments )
         {
            // add the notice to the WSDL <documention/> element
            wsdl.Documentation = Properties.Settings.Default.WsdlNotice;
         }
         // add a new XSD annotation with the documentation
         // at the root of each generated schema
         ICollection schemas = exporter.GeneratedXmlSchemas.Schemas();
         foreach ( XmlSchema schema in schemas )
         {
            XmlDocument doc = new XmlDocument();
            XmlNode node =
               doc.CreateTextNode(Properties.Settings.Default.WsdlNotice);
            XmlSchemaDocumentation comment = new XmlSchemaDocumentation();
            comment.Markup = new XmlNode[] { node };
            XmlSchemaAnnotation annotation = new XmlSchemaAnnotation();
            annotation.Items.Add(comment);
            schema.Items.Insert(0, annotation);
         }
      }
 
      #endregion // IWsdlExportExtension Implementation
 
 
      #region IEndpointBehavior Implementation
      //
      // IContractBehavior Implementation
      //
      public void AddBindingParameters(
         ServiceEndpoint endpoint, 
         BindingParameterCollection bindingParameters
         )
      {
         // not needed
      }
 
      public void ApplyClientBehavior(
         ServiceEndpoint endpoint, 
         ClientRuntime clientRuntime
         )
      {
         // not needed
      }
 
      public void ApplyDispatchBehavior(
         ServiceEndpoint endpoint, 
         EndpointDispatcher dispatcher
         )
      {
         // not needed
      }
 
      public void Validate(ServiceEndpoint endpoint)
      {
         // not needed
      }
 
      #endregion // IContractBehavior Implementation
 
   } // class WsdlNoticeBehavior
 
} // namespace Winterdom.ServiceModel.Extensions

Now we just need to add our behavior to the correct endpoint:

Uri uri = new Uri("http://localhost:8188/VoucherService");
ServiceHost host = 
   new ServiceHost(typeof(VoucherService), uri);
 
WSHttpBinding binding = new WSHttpBinding();
ServiceEndpoint endpoint = 
   host.AddServiceEndpoint(typeof(IVoucherService), binding, uri);
endpoint.Behaviors.Add(new WsdlNoticeBehavior());
host.Open();

Here's the configuration for our notice on our app.config file:

<userSettings>
   <Winterdom.ServiceModel.Extensions.Properties.Settings>
      <setting name="WsdlNotice" serializeAs="String">
         <value>
            This service description Copyright (C) 2006, Tomas Restrepo.
            All rights reserved.
         </value>
      </setting>
   </Winterdom.ServiceModel.Extensions.Properties.Settings>
</userSettings>

[1] A little off topic, but it might be worth mentioning that I'm not entirely comfortable with the way contract behaviors are implemented in WCF. Normally, one will want to define an attribute which implements IContractBehaviorAttribute to be able to attach your behavior to the specified contract in a nice way (from the API point of view). However, it feels totally unnatural  for the attribute class to have to implement the actual behavior (including IWsdlExportExtension if that's your goal) in the same attribute class. I would've really expected to be able to define an attribute that instead determines the type that implemented the behavior instead, that being more in line with the pattern followed throughout the .NET Framework.

3 Comments

  1. Hassan Gulzar

    Hi! I think your research here can help me. I’m dealing with a very huge WCF service and the generated wsdl is nilling everything by default.

    Thing is, so strings are actually nillable but some are not and my client is complaining. If you can post the actual project, it’ll be really helpful. I need to control the contracts that can be nill or not. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>