23 September 2008

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"%>
Filed under: , ,
 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Flatten your WSDL with this Custom ServiceHost for WCF : EasyCoded said:

PingBack from http://www.easycoded.com/flatten-your-wsdl-with-this-custom-servicehost-for-wcf/

23 September 08 at 11:38 AM
# Natasa Manousopoulou said:

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

               }

           }

       }

23 September 08 at 2:48 PM
# Massimo said:

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

10 November 08 at 5:55 AM
# DaveK said:

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)

18 November 08 at 4:30 PM
# AppliSec said:

How to flatten WCF WSDL for interoperability purposes...

30 March 09 at 1:53 AM
# Shane said:

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

09 April 09 at 5:44 AM

Leave a Comment

Comment Policy: No HTML allowed. URIs and line breaks are converted automatically. Your e–mail address will not show up on any public page.

(required) 
(optional)
(required) 
Page view tracker