Interoperable WCF Service by Flattening our 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.
NOTE: Going ahead in .NET 4.5 you would have a inbuilt feature to achieve the same.