WCF: Concept of FLAT WSDL
Ask:
Java Client (Non.Net client) unable to consume the WCF service WSDL document.
Reason:
After collecting fiddler traces from client side, I see that there is a problem while trying to get the XSD downloaded.
WCF expose XSD via external location and sometime Java client may not be able to access them.
Fiddler Traces extract:
First request from svcutil.exe
GET https://XYZ.com.com/MyService/MyServiceFile.svc?wsdl
HTTP/1.1
Response, we do get the WSDL doc:
========
HTTP/1.1 200 OK
Content-Type: text/xml; charset=UTF-8
<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions
name="MyServiceFile" targetNamespace="tempuri.org/"
xmlns:wsdl="schemas.xmlsoap.org/wsdl/"
xmlns:wsx="schemas.xmlsoap.org/ws/2004/09/mex"
xmlns:wsa10="www.w3.org/2005/08/addressing"
xmlns:tns="tempuri.org/" xmlns:soap12="schemas.xmlsoap.org/wsdl/soap12/"
xmlns:wsu="docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wsap="schemas.xmlsoap.org/ws/2004/08/addressing/policy"
xmlns:msc="schemas.microsoft.com/ws/2005/12/wsdl/contract"
xmlns:wsa="schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:wsam="www.w3.org/2007/05/addressing/metadata"
xmlns:wsaw="www.w3.org/2006/05/addressing/wsdl"
xmlns:soap="schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="www.w3.org/2001/XMLSchema"
xmlns:soapenc="schemas.xmlsoap.org/soap/encoding/">
<wsp:Policy
wsu:Id="MyRoutingEndpoint_policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding
xmlns:sp="schemas.xmlsoap.org/ws/2005/07/securitypolicy"><wsp:Policy><sp:TransportToken><wsp:Policy><sp:HttpsToken
RequireClientCertificate="false"/></wsp:Policy></sp:TransportToken><sp:AlgorithmSuite><wsp:Policy><sp:Basic256/></wsp:Policy></sp:AlgorithmSuite><sp:Layout><wsp:Policy><sp:Strict/></wsp:Policy></sp:Layout></wsp:Policy></sp:TransportBinding></wsp:All></wsp:ExactlyOne>
</wsp:Policy>
<wsdl:types>
<xsd:schema
targetNamespace="tempuri.org/Imports">
<xsd:import
schemaLocation="XYZ.com.com/MyService/MyServiceFile.svc?xsd=xsd0"
namespace="tempuri.org/"/>
<xsd:import
schemaLocation="XYZ.com.com/MyService/MyServiceFile.svc?xsd=xsd1"
namespace="schemas.microsoft.com/2003/10/Serialization/"/>
<xsd:import
schemaLocation="XYZ.com.com/MyService/MyServiceFile.svc?xsd=xsd2"
namespace="schemas.microsoft.com/2003/10/Serialization/Arrays"/>
<xsd:import
schemaLocation="XYZ.com.com/MyService/MyServiceFile.svc?xsd=xsd3"
namespace="schemas.datacontract.org/2004/07/McAfeePortal.MyService"/>
<xsd:import
schemaLocation="XYZ.com.com/MyService/MyServiceFile.svc?xsd=xsd4"
namespace="schemas.datacontract.org/2004/07/McAfeePortal.PurchaseMailService"/>
</xsd:schema></wsdl:types><wsdl:message
Second: Because of wrong Schema location, we end up in sending the request to wrong address:
GET XYZ.com.com/MyService/MyServiceFile.svc?xsd=xsd0
HTTP/1.1
Because of this issue, my reference.cs file is blank.
Options:
Considering the requirement, the feature is added in latest and greatest WCF 4.5.
But what about client running on 4.0 ?
Solution:
Using WCF extensibility we can achieve this by implementing the - IWsdlExportExtension
public class MyFlatWsdl : IWsdlExportExtension,IEndpointBehavior
{
public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
{
//throw new NotImplementedException();
}
public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
{
XmlSchemaSet schemaSet =exporter.GeneratedXmlSchemas;
foreach (System.Web.Services.Description.ServiceDescription wsdl in exporter.GeneratedWsdlDocuments)
{
List<XmlSchema> importsList = new List<XmlSchema>();
foreach(XmlSchema schema inwsdl.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 voidAddImportedSchemas(XmlSchema schema, XmlSchemaSet schemaSet, List<XmlSchema> importsList)
{
foreach (XmlSchemaImportimport in schema.Includes)
{
ICollectionrealSchemas = schemaSet.Schemas(import.Namespace);
foreach(XmlSchema ixsd in realSchemas)
{
if(!importsList.Contains(ixsd))
{
importsList.Add(ixsd);
AddImportedSchemas(ixsd, schemaSet, importsList);
}
}
}
}
private voidRemoveXsdImports(XmlSchema schema)
{
for (int i = 0; i < schema.Includes.Count; i++)
{
if(schema.Includes[i] is XmlSchemaImport)
schema.Includes.RemoveAt(i--);
}
}
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint,System.ServiceModel.Channels.BindingParameterCollection
bindingParameters)
{
//throw new NotImplementedException();
}
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint,System.ServiceModel.Dispatcher.ClientRuntime
clientRuntime)
{
//throw new NotImplementedException();
}
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint,System.ServiceModel.Dispatcher.EndpointDispatcher
endpointDispatcher)
{
//throw new NotImplementedException();
}
void IEndpointBehavior.Validate(ServiceEndpoint endpoint)
{
//throw new NotImplementedException();
}
}
public class MyBehaviorExtension : BehaviorExtensionElement
{
public overrideType BehaviorType
{
get { return typeof(MyFlatWsdl); }
}
protected override object CreateBehavior()
{
MyFlatWsdl myBehavior = new MyFlatWsdl();
return myBehavior;
}
}
Config file changes:
<extensions>
<behaviorExtensions>
<add name="MyEndPointBehavior" type="WcfService2.MyBehaviorExtension, WcfService2"/>
</behaviorExtensions>
</extensions>
<service name="WcfService2.Service1">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="" behaviorConfiguration="MyBeh"
name="end" contract="WcfService2.IService1" />
</service>
<endpointBehaviors>
<behavior name="MyBeh">
<MyEndPointBehavior />
</behavior>
</endpointBehaviors>
With above code/config changes we can easily expose WCF service with Flat WSDL in 4.0.
FYI, in WCF 4.5 it is available as "SingleWSDL" option.
Hope this help !