Wednesday, December 21, 2011

How to flatten your Wsdl?

When creating web services that are to be interoperable (easily accessbile across various platforms) the wsdl cannot have any xsd:import elements. In order to achieve this we need to flatten the wsdl. We do this by creating our own ServiceHostFactory.

First we create a class that defines the new ServiceHostFactory called FlatWsdlServiceHostFactory :


using System;
 
namespace 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);
        }
    }
}

Now we create a Custom ServiceHost that performs the action of flattening the wsdl:


using System;
using System.ServiceModel.Description;
 
namespace FlatWsdl
{
    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)
        {
            
        }
 
        /// <summary>
        /// Just Inject Flat WSDL
        /// </summary>
        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());
            }
        }
    }
}

Now we have the actual class that inspects your wsdl and injects the xsd's into the wsdl by replacing the xsd:import elements in your wsdl.


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 FlatWsdl
{
    public class FlatWsdl: 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);
                
                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) { }
    }
}

Now your FlatWsdlServiceHostFactory is ready which would flatten your wsdl. Create a new WCF Service called FaltWsdlService.svc and open the markup of this file which the following code


<%@ ServiceHost Language="C#" Debug="true" Service="Sample.FlatWsdlService" CodeBehind="FlatWsdlService.svc.cs" %>

Replace the above with the code below which states the Service needs to use the FlatWsdlServiceHostFactory instead of the default ServiceHostFactory from WCF.


<%@ ServiceHost Language="C#" Debug="true" Factory="FlatWsdl.FlatWsdlServiceHostFactory" Service="SampleService.FlatWsdlService" CodeBehind="FlatWsdlService.svc.cs" %>

Now when you browse to your wsdl you not find any xsd:import elements rather you would have one single wsdl.

No comments: