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
Anonymous
September 23, 2008
PingBack from http://www.easycoded.com/flatten-your-wsdl-with-this-custom-servicehost-for-wcf/Anonymous
September 23, 2008
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); } } }Anonymous
November 09, 2008
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, MassimoAnonymous
November 18, 2008
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)Anonymous
March 29, 2009
How to flatten WCF WSDL for interoperability purposes...Anonymous
April 08, 2009
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);Anonymous
March 07, 2011
The comment has been removedAnonymous
September 28, 2011
The comment has been removedAnonymous
April 18, 2012
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.