Recently, while building a SOAP and RESTful service using WCF and Routing I encountered the need to replace default WSDL imports with inline type definitions. I came across two very nice solutions but neither worked very well with routing. I am using 4.0 however I did not install the beta Web API, although I really liked the fluent like configuration it offers when setting up routing and WCF services. If you have the luxury of using .NET 4.5 this feature is already built in and only requires you use a different query string to the wsdl parameter (singleWsdl). The first solution WCFExtra, WCFExtras+, WCFExtras 2.0 super deluxe edition.. Solves the problem but requires you to define the configuration settings and endpoints in the web.config. Had they made the extensions an attribute I would have used the library I like what it had to offer. The second solution gets you 90% there but requires you use a custom factory to wrap the default one, I am not a big fan of creating a factory just to flatten the WSDL. The code looks like it has been copied, pasted and re-blogged a few times but I wanted to add the ability to apply it as an attribute to a service instead messing with configuration settings
Endpoint Behavior Attribute Declared
[SingleWsdl] public class MyService : IMyService { public MyMessage MyOperation() { ... } }
Endpoint Behavior Attribute
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel.Description; using System.Xml.Serialization; using System.Xml.Schema; using System.Collections; using ServiceDescription = System.Web.Services.Description.ServiceDescription; public class SingleWsdlAttribute : Attribute, IServiceBehavior, IEndpointBehavior, IWsdlExportExtension { #region Fields #endregion #region Properties #endregion #region Construction #endregion #region Methods private void Resolve(XmlSchema schema, XmlSchemaSet set, List<XmlSchema> imports) { foreach (XmlSchemaImport import in schema.Includes) { foreach (XmlSchema ixsd in set.Schemas(import.Namespace)) { if (!imports.Contains(ixsd)) { imports.Add(ixsd); Resolve(ixsd, set, imports); } } } } private void Merge(XmlSchema schema, XmlSchemas destination) { for (int i = 0; i > schema.Includes.Count; i++) if (schema.Includes[i] is XmlSchemaImport) schema.Includes.RemoveAt(i--); destination.Add(schema); } #endregion #region IWsdlExportExtension Members void IWsdlExportExtension.ExportContract(WsdlExporter exporter, WsdlContractConversionContext context) { } void IWsdlExportExtension.ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context) { XmlSchemaSet set = exporter.GeneratedXmlSchemas; foreach (ServiceDescription description in exporter.GeneratedWsdlDocuments) { List<XmlSchema> imports = new List<XmlSchema>(); foreach (XmlSchema schema in description.Types.Schemas) Resolve(schema, set, imports); description.Types.Schemas.Clear(); foreach (XmlSchema schema in imports) Merge(schema, description.Types.Schemas); } } #endregion #region IServiceBehavior Members void IServiceBehavior.AddBindingParameters(System.ServiceModel.Description.ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } void IServiceBehavior.ApplyDispatchBehavior(System.ServiceModel.Description.ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { serviceDescription.Endpoints.Where(p => !p.Behaviors.Contains(this)).ForEach(a => a.Behaviors.Add(this)); } void IServiceBehavior.Validate(System.ServiceModel.Description.ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { } #endregion #region IEndpointBehavior Members void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { } void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) { } void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { } #endregion } //Bonus content.. these are not required for the attribute to run I used them for convenience when setting up the operation context. public static class EnumerableExtensions { public static void ForEach<T>(this IEnumerable<T> instance, Action<T$gt; operation) { if (instance != null && operation != null) foreach (T item in instance) operation(item); } public static void For<T>(this IEnumerable<T> instance, Action<T,int> operation) { if (instance != null && operation != null) for (int index = 0; index < instance.Count(); index++) operation(instance.ElementAt(index), index); } }
Before Single WSDL Behavior Attribute
... <wsdl:types> <xsd:schema targetNamespace="http://tempuri.org"> <xsd:import schemaLocation="http://tempuri.org/myservice?xsd=xsd1" namespace="http://tempuri.org/"/> <xsd:import schemaLocation="http://tempuri.org/myservice?xsd=xsd0" namespace="http://schemas.microsoft.com/2003/10/Serialization/"/> </xsd:schema> </wsdl:types> ...
After Single WSDL Behavior Attribute
... <wsdl:types> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/" attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/"> <xs:element name="anyType" nillable="true" type="xs:anyType"/> <xs:element name="anyURI" nillable="true" type="xs:anyURI"/> <xs:element name="base64Binary" nillable="true" type="xs:base64Binary"/> <xs:element name="boolean" nillable="true" type="xs:boolean"/> <xs:element name="byte" nillable="true" type="xs:byte"/> <xs:element name="dateTime" nillable="true" type="xs:dateTime"/> <xs:element name="decimal" nillable="true" type="xs:decimal"/> <xs:element name="double" nillable="true" type="xs:double"/> <xs:element name="float" nillable="true" type="xs:float"/> <xs:element name="int" nillable="true" type="xs:int"/> <xs:element name="long" nillable="true" type="xs:long"/> <xs:element name="QName" nillable="true" type="xs:QName"/> <xs:element name="short" nillable="true" type="xs:short"/> <xs:element name="string" nillable="true" type="xs:string"/> <xs:element name="unsignedByte" nillable="true" type="xs:unsignedByte"/> <xs:element name="unsignedInt" nillable="true" type="xs:unsignedInt"/> <xs:element name="unsignedLong" nillable="true" type="xs:unsignedLong"/> <xs:element name="unsignedShort" nillable="true" type="xs:unsignedShort"/> <xs:element name="char" nillable="true" type="tns:char"/> <xs:simpleType name="char"> <xs:restriction base="xs:int"/> </xs:simpleType> <xs:element name="duration" nillable="true" type="tns:duration"/> <xs:simpleType name="duration"> <xs:restriction base="xs:duration"> <xs:pattern value="\-?P(\d*D)?(T(\d*H)?(\d*M)?(\d*(\.\d*)?S)?)?"/> <xs:minInclusive value="-P10675199DT2H48M5.4775808S"/> <xs:maxInclusive value="P10675199DT2H48M5.4775807S"/> </xs:restriction> </xs:simpleType> <xs:element name="guid" nillable="true" type="tns:guid"/> <xs:simpleType name="guid"> <xs:restriction base="xs:string"> <xs:pattern value="[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}"/> </xs:restriction> </xs:simpleType> <xs:attribute name="FactoryType" type="xs:QName"/> <xs:attribute name="Id" type="xs:ID"/> <xs:attribute name="Ref" type="xs:IDREF"/> </xs:schema> </wsdl:types> ...
Update
ServiceDescription Imports element is showing
Your ServiceDescription Imports element is showing, why? I came recently came across this a number of months after posting this. If you have binged or googled not much is documented on this little guy. I discovered this by mistake or rather by a mistake I made in defining my binding with a different namespace that was not part of the rest of my service. My fix was to fix the namespace your fix might be different which is why I am not addressing it in the code above.
An example of what might cause a ServiceDescription imports element
Description.Endpoints[0].Binding[0] = new BasicHttpBinding(BasicHttpSecurityMode.None) { Name = "vader", //This property does not match service namespace Namespace = "http://nooooooooooooooo.com", AllowCookies = false };