Monday, March 1, 2010

Adding WSDL Documentation to your WCF Services

Back in the good ol’ days of ASMX web service development, adding documentation to the WSDL of your service was as simple as providing the desired info in the Description property of the WebMethod attribute:

[WebMethod(Description="This method always returns 'Hello World!'")]
public string HelloWorld()
{
    return "Hello World!";
}

This injected something like the following into your WSDL which allows web service clients and tools to give the consumer a little more information about a service or method.

<wsdl:operation name="Hello World">
    <wsdl:documentation>This method always returns 'Hello World'!</wsdl:documentation>
    <wsdl:input ... />
    <wsdl:output ... />
</wsdl:operation>

Now fast forward to the new services world of WCF…and its mantra of “I’m uber-powerful, but everything that used to be easy is no longer easy”. There is no out-of-the-box way to inject descriptions into your WSDL in WCF.  Can’t believe it? Neither could I…but there is hope. 

WCF was built to allow extensibility to almost everything, including WSDL generation. For this particular problem, we need to look at contract behaviors (IContractBehavior), operation behaviors (IOperationBehavior), and the IWsdlExportExtension interface.  Armed with these we can develop a behavior which allows you to specify WSDL descriptions for a service or operation in much the same way as the old ASMX days.  Below is the code for a WsdlDocumentation attribute which does just this:

/// <summary>
/// Attribute which injects wsdl:documentation elements into the WSDL.
/// </summary>
/// <remarks>
/// This attribute can be applied to the service contract and it's methods.  
/// </remarks>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method)]
public class WsdlDocumentationAttribute : Attribute, IContractBehavior, IOperationBehavior, IWsdlExportExtension
{
    private ContractDescription _contractDescription;
    private OperationDescription _operationDescription;

    /// <summary>
    /// Initializes a new instance of WsdlDocumentationAttribute.
    /// </summary>
    /// <param name="text">Text to inject into the WSDL.</param>
    public WsdlDocumentationAttribute(string text)
    {
        this.Text = text;
    }

    /// <summary>
    /// Text to inject into the WSDL for the target element.
    /// </summary>
    public string Text { get; set; }

    void IWsdlExportExtension.ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
    {
        // This is either for a service contract or operation, so set documentation accordingly.
        if (_contractDescription != null)
        {   
            // Attribute was applied to a contract.
            context.WsdlPortType.Documentation = this.Text;
        }
        else
        {
            // Attribute was applied to an operation.
            Operation operation = context.GetOperation(_operationDescription);
            if (operation != null)
            {
                operation.Documentation = this.Text;
            }
        }
    }
    
    void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        _contractDescription = contractDescription;
    }
        
    void IOperationBehavior.ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        _operationDescription = operationDescription;
    }    

   // **** All other interface members are not used and have been removed for brevity
}

To use this new attribute, we just apply it to a service contract and/or method…

[ServiceContract(Name="MyService")]
[WsdlDocumentation("Description of MyService")]
public interface IMyService
{
    [OperationContract]
    [WsdlDocumentation("This method always returns 'Hello World!'")]
    public string HelloWorld()
    {
        return "Hello World!";
    }
}

Voila, the descriptions show up in the WSDL!

3 comments:

Moritz said...

That works very well! Thanks a lot!

Is it really correct to add the omitted members empty like this?

public void AddBindingParameters ( ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters ) { }
public void AddBindingParameters ( OperationDescription operationDescription, BindingParameterCollection bindingParameters ) { }
public void ApplyClientBehavior ( ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime ) { }
public void ApplyClientBehavior ( OperationDescription operationDescription, ClientOperation clientOperation ) { }
public void Validate ( ContractDescription contractDescription, ServiceEndpoint endpoint ) { }
public void Validate ( OperationDescription operationDescription ) { }
public void ExportEndpoint ( WsdlExporter exporter, WsdlEndpointConversionContext context ) { }

Unknown said...

Thenk you very useful

Unknown said...

do not work for me, I use framework 3.5, everything is fine, only when I put

Post a Comment