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"%>