Flatten your WSDL with this Custom ServiceHost for WCF


Yesterday I mentioned using a custom service host to flatten the WSDL that is generated by a WCF service.  This is something Christian showed us all how to do a long while ago, to improve interoperability between WCF-implemented services and consumers written on other technology stacks.  Flattening WSDL is important for Interop purposes becausse many tools don’t digest modular WSDL very well.  When I say modular WSDL, I mean WSDL that imports other WSDL’s or XSDs. 


I realized that I had never actually published the code for my custom WCF service host that flattens WSDL. 


So here it is. [updated 146pm US/Pacific time based on Natasa’s comment]




using System;


using System.Collections;


using System.Collections.Generic;


using System.ServiceModel.Channels;


using System.ServiceModel.Description;


using System.ServiceModel.Dispatcher;


using System.Xml.Schema;


using ServiceDescription = System.Web.Services.Description.ServiceDescription;


 


namespace Thinktecture.ServiceModel


{


    public class FlatWsdl : IWsdlExportExtension, IEndpointBehavior


    {


        public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context) { }


 


        public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)


        {


            XmlSchemaSet schemaSet = exporter.GeneratedXmlSchemas;


            foreach (WsdlDescription wsdl in exporter.GeneratedWsdlDocuments)


            {


                List<XmlSchema> importsList = new List<XmlSchema>();


                foreach (XmlSchema schema in wsdl.Types.Schemas)


                    AddImportedSchemas(schema, schemaSet, importsList);


 


                if (importsList.Count == 0)


                    return;


 


                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 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 class FlatWsdlServiceHost : System.ServiceModel.ServiceHost


    {


        public FlatWsdlServiceHost() { }


 


        public FlatWsdlServiceHost(Type serviceType, params Uri[] baseAddresses)


            : base(serviceType, baseAddresses) { }


 


        public FlatWsdlServiceHost(object singletonInstance, params Uri[] baseAddresses)


            : base(singletonInstance, baseAddresses) { }


 


        protected override void ApplyConfiguration()


        {


            Console.WriteLine(“ApplyConfiguration (thread {0})”,


                              System.Threading.Thread.CurrentThread.ManagedThreadId);


            base.ApplyConfiguration();


            InjectFlatWsdlExtension();


        }


 


        private void InjectFlatWsdlExtension()


        {


            foreach (ServiceEndpoint endpoint in this.Description.Endpoints)           


                endpoint.Behaviors.Add(new FlatWsdl());


        }


    }


 


 


 


    public sealed class FlatWsdlServiceHostFactory : System.ServiceModel.Activation.ServiceHostFactory


    {


        public override System.ServiceModel.ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)


        {


            return base.CreateServiceHost(constructorString, baseAddresses);


        }


 


        protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)


        {


            return new FlatWsdlServiceHost(serviceType, baseAddresses);


        }


    }


}


 


And to use this, you would specify something like this in your .svc file:


<%@ ServiceHost
Language=”C#”
Factory=”Thinktecture.ServiceModel.FlatWsdlServiceHostFactory”
Service=”Ionic.Samples.Webservices.WcfService1″%>

Comments (9)

  1. Natasa Manousopoulou says:

    In order to make the exporter work correctly if there are more than one endpoints defined for the service, ExportEndpoint() must be slightly changed:

           public void ExportEndpoint(

                 WsdlExporter exporter,

                 WsdlEndpointConversionContext context

              )

           {

               XmlSchemaSet schemaSet = exporter.GeneratedXmlSchemas;

               foreach (WsdlDescription wsdl in exporter.GeneratedWsdlDocuments)

               {

                   List<XmlSchema> importsList = new List<XmlSchema>();

                   foreach (XmlSchema schema in wsdl.Types.Schemas)

                   {

                       AddImportedSchemas(schema, schemaSet, importsList);

                   }

                   if (importsList.Count == 0)

                   {

                       return;

                   }

                   wsdl.Types.Schemas.Clear();

                   foreach (XmlSchema schema in importsList)

                   {

                       RemoveXsdImports(schema);

                       wsdl.Types.Schemas.Add(schema);

                   }

               }

           }

  2. Massimo says:

    I’ve been searching far and wide, with no success, for a snippet showing how to flatten a wsdl+(several xsd) files to a single wsdl file I can feed to the wsdl2php utility from WSO2. I’m no WCF programmer, but your code looks promising … only problem is, can it be adapted to read its input from a set of files?

    From what I understand, at a minimum one needs to replace the exporter.GeneratedXmlSchemas and exporter.GeneratedWsdlDocuments with something that can fill an XmlSchemaSet and a WsdlDescription objects, but I have no idea how.

    Thank you in advance for any advice you can give,

    Massimo

  3. DaveK says:

    This is very useful, thanks. Under what license may this be used?

    Looks like it has a bug. This:

    foreach (WsdlDescription wsdl in exporter.GeneratedWsdlDocuments)

    I think should be:

    foreach (ServiceDescription wsdl in exporter.GeneratedWsdlDocuments)

  4. AppliSec says:

    How to flatten WCF WSDL for interoperability purposes…

  5. Shane says:

    I am having difficulty getting this to work from within a windows service.

    Here is my code

    FlatWsdlServiceHost flatServiceHost = new FlatWsdlServiceHost(typeof(WSOrder), baseAddress);

    flatServiceHost.AddServiceEndpoint(typeof(IWSOrder), binding, registerAddress);

    flatServiceHost.Description.Behaviors.Add(metadataBehavior);

  6. Sam says:

    Where's the tool in the tools menu? This is like hacking into the OS to create a directory!!!! Very lame. I've already spent 3 days on this rubbish. Will somebody please go the extra mile! Don't they pay enough at Microsoft?

  7. Need a stand alone utility says:

    I need a STAND ALONE UTILITY I dont use WCF hate WCF and cant connect to a WCF service because of the crappy WSDL with multiple XSDs

    I cant use this worthless source code and it appears most others cant either

  8. Kramer says:

    There's another bug in this code. Where it says:

                   if (importsList.Count == 0)

                       return;

    That return should actually be a continue statement. As written, if the extension is used on a WsdlExporter to export multiple endpoints, it will only correctly process the first exported endpoint. When the extension is used to process the second (third, fourth, etc.) endpoint, it encounters the WSDL document from the first endpoint, discovers it has no imports left, and aborts instead of continuing on to the WSDL document from the second/third/etc. endpoints.

Skip to main content