Friday, July 25, 2008

Adding FlatWSDL to WCF WebService

WCF WebServices in a UDDI Environment

Download the SourceCode

If it comes to the point where you want to integrate your WCF WebServices into a UDDI environment you will stumble accross a feature introduced in WCF.

By default you will find in the WSDL types section:

<wsdl:types>
<xsd:schema targetNamespace="http://tempuri.org/Imports">
<xsd:import schemaLocation="http://localhost:3826/Service1.svc?xsd=xsd0" namespace="http://tempuri.org/" />
<xsd:import schemaLocation="http://localhost:3826/Service1.svc?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/" />
<xsd:import schemaLocation="http://localhost:3826/Service1.svc?xsd=xsd2" namespace="http://schemas.datacontract.org/2004/07/MyFirstWCFService" />
</xsd:schema>
</wsdl:types>


Looks great, but here it is causing a problem. Using a UDDI means you bind your Service at runtime. To do so you need a WSDL that contains only the abstract informations. But "http://localhost:3826/Service1.svc?xsd=xsd0" ist not very abstract, right?

You have 3 choices now.
a.) You have always your service at the given adress up and running.
This would just negate the whole UDDI concept.
b.) Load every data schema by hand, and replace the include statement with real schema. By hand. This is pain in the ass. Especially when you deal a number of datatypes from different namespaces.
c.) You find a way to somehow flatten your WSDL.

As I did not like a.) or b.) I looked for c.) and luckily stumbled across the articles of Tomas Restrepo and Cristian Weyers. Christian even provides the source for download and their FlatWSDL does a nice job. Thanks guys!

But their example was related to self-hosted services. As I mainly try to run IIS based WCF Services I came on day to the point where I had to integrate FlatWSDL with an IIS based WCF Service.

The problem. You do not directly control the ServiceHost object and thus

foreach(ServiceEndpoint endpoint in selfHost.Description.Endpoints)
endpoint.Behaviors.Add(new FlatWsdl());

won't work here.

But Ok then. Everything related to ServiceHost Configuration you can do either in Code or via the ConfigFiles. The WCF Config Editor is really a great help here.

So I just added FlatWSDL.cs to my project, recompiled it and fired up the WCF ConfigEditor.

The first step is to register FlatWSDL as a "behaviour element extension" what can be done under the "Advanced - Extensions" tab.

There is a button "New". Enter a Name, pick your Service.DLL and select.....

nothing.... which is about the whole choice you have.

When I googled for "wcf add custom endpoint behaviour config" I again stumbled accross an article from James Bender where he also implements an IEndPointBehaviour but here he derives his behaviour class from the abstract class BehaviorExtensionElement as well.

Related to BehaviorExtensionElement the help says:
"Represents a configuration element that contains sub-elements that specify behavior extensions, which enable the user to customize service or endpoint behaviors."
"Sounds great" I thought and derived the FlatWSDL class from BehaviorExtensionElement. So your class definition now looks like
using System.Collections;
using System.Collections.Generic;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Xml.Schema;
using ServiceDescription = System.Web.Services.Description.ServiceDescription;

namespace SCDServices.ServiceImplementation
{
/// <summary>
///
/// </summary>
public class FlatWsdl : BehaviorExtensionElement, IWsdlExportExtension, IEndpointBehavior
{
    public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
    {
    }

    public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
    {
        XmlSchemaSet schemaSet = exporter.GeneratedXmlSchemas;

        foreach (ServiceDescription wsdl in exporter.GeneratedWsdlDocuments)
        {
            List<XmlSchema> importsList = new List<XmlSchema>();

            foreach (XmlSchema schema in wsdl.Types.Schemas)
            {
                AddImportedSchemas(schema, schemaSet, importsList);
            }

            wsdl.Types.Schemas.Clear();

            foreach (XmlSchema schema in importsList)
            {
                RemoveXsdImports(schema);
                wsdl.Types.Schemas.Add(schema);
            }
        }
    }

    private void AddImportedSchemas(XmlSchema schema, XmlSchemaSet schemaSet, List<XmlSchema> importsList)
    {
        foreach (XmlSchemaImport import in schema.Includes)
        {
            ICollection realSchemas =
            schemaSet.Schemas(import.Namespace);

            foreach (XmlSchema ixsd in realSchemas)
            {
                if (!importsList.Contains(ixsd))
                {
                    importsList.Add(ixsd);
                    AddImportedSchemas(ixsd, schemaSet, importsList);
                }
            }
        }
    }

    private static void RemoveXsdImports(XmlSchema schema)
    {
        for (int i = 0; i < schema.Includes.Count; i++)
        {
            if (schema.Includes[i] is XmlSchemaImport)
                schema.Includes.RemoveAt(i--);
        }
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }

    public override System.Type BehaviorType
    {
    get { return typeof(FlatWsdl); }
    }

    protected override object CreateBehavior()
    {
    return new FlatWsdl();
    }

    #region Ctor

    /// <summary>
    /// Default constructor.
    /// </summary>
    public FlatWsdl()
    {
    }
    #endregion
    }
} 


1.) We compile and fire up the config editor again.
We go to "Advanced - Extension - behaviour element Extension" and press new.
Now we browse to our Service.DLL in the bin directory and Bingo!



We select Thinktecture.ServiceModel.Extensions.Description.FlatWsdl from the List.

2.) We now browse to "Advanced - Endpoint Behaviours" und press "New Endpoint Configuration"
We enter a name (here MexFlatWSDLBehaviour), press "Add" and select FlatWSDL from the List.



In case there is no FlatWSDL in the List, save, close and reopen the ConfigEditor. It now complains that it can't find the DLL file. Press "Yes" browse to our Service.DLL in bin directory and click "Yes" when the editor warns you it is going to execute some code.
Now at latest there should be FlatWSDL in the list.

3.) Now add the newly created Endpointbehaviour to our ServiceEndPoint (not the mex endpoint).
You can recognize your ServiceEndpoint as the "Contract" attribute points to your ServiceInterface.
For "BehaviourConfiguration" select our "MexFlatWSDLBehaviour" from the list.


Done

When you now check the WSDL emitted from our WCF WebService you will find nice inline type schemas.

<wsdl:types>
<xsd:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/">
<xsd:element name="HelloWorld">
<xsd:complexType>
<xsd:sequence />
</xsd:complexType>
</xsd:element>
<xsd:element name="HelloWorldResponse">
<xsd:complexType>
 <xsd:sequence>
    <xsd:element minOccurs="0" name="HelloWorldResult" nillable="true" type="xsd:string" />
 </xsd:sequence>
</xsd:complexType>
</xsd:element>



Christian had found another approach through a custom ServiceHost and custom ServiceHostFactory. Look at the bottom of the article.

I recommend taking a look at WCFExtra. They just released Version 2. Among others it brings single WSDL support out of the box.

Download the SourceCode

7 comments:

Anonymous said...

This is a great article elLoco!

Could you please provide the screen-shots for the sake of clarity? Looks like the link is broken or something...

Thanks a lot!!

MP.

Anonymous said...

Could you post a screenshot of the service webconfig file to show how the result will be looking after applying the new configuration?

Anonymous said...

Doesn't work.

unrecognised element FlatWSDL in web.config.

Anonymous said...

Source link doesn't work....

elLoco said...

? works fine for me. Try "Save as".

Anonymous said...

Great article. How can we implement the same if we are consuming the thirdparty service. How can I write the code to read the service url dynamically and flatten their wsdl.

Unknown said...

Hi,

Now, I am facing big issue using WCF generated WSDL(using Add Service Reference in New Project) to generate stubs using Jax-WS.

Here is error message
[ERROR] Two declarations cause a collision in the ObjectFactory class.
line 1 of http://localhost:1234/wcfservice.svc?xsd=xsd1
[ERROR] This is the other declaration.
line 1 of http://localhost:1234/wcfservice.svc?xsd=xsd2

Here is my code snippets below
//AddDataRequest.cs
namespace Sample
{
//[DataContract]
public class AddDataRequest
{
string id;

[DataMemberAttribute]
public string ID
{
get { return id; }
set { id = value; }
}
}
}

//AddDataResponse.cs
namespace Sample
{
[DataContract]
public class AddDataResponse
{
[DataMember]
public string retCode = "1";
}
}

//IData.cs
namespace Sample
{
[XmlComments]
[ServiceContract]
public interface IData
{
[OperationContract]
AddDataResponse AddData(AddDataRequest request);
}
}

//Data.svc.cs
namespace Sample
{
public class Sample: IData
{
public AddDataResponse AddData(AddDataRequest param1)
{
AddDataResponse addData= new AddDataResponse ();
return addData;
}
}

// web.config














































































































Even, I tried in VS 2012 but still the same issue. Could it be possible coz of WSDL flattening or something else?

I hope some one will advise me to close the case since I am running out of time.



Thanks and best regards