If you’ve done any work with WCF and non .NET clients, you’ve probably had the need to make WCF emit a flattened WSDL that doesn’t use xsd includes. You’ve probably run across Tomas Restrepo’s InlineXSDInWSDLBehavior and Christian Weyer’s FlatWSDL extension. They both work quite well. Possibly too well.
We noticed .NET behaved strangely when referring to a service that had a flattened wsdl. Everything was a string and all collections were arrays. Basically, rather than having the luxury of telling the proxy what the underlying data type was and having it handle the appropriate serialization we had to cast everything to and from the underlying on the wire data. It was workable, but in truth it kinda sucked. So, I figured there had to be a way to make WCF emit flat wsdl only when we wanted it.
After spending way too much time chasing down blind alleys I finally decided to just implement a IDispatchMessageInspector as a ServiceBehavior and be done with it. There’s proably a nicer, better way to do this, but I haven’t found it.
The first thing that needed to be done to make this work was figure out if the request that was about to be sent was in response to a wsdl query. I didn’t find a straight forward way to get that information in BeforeSendReply, so I just grabbed the request URI in the AfterReceiveRequest method and spit it back out as the correlation state object.
public object AfterReceiveRequest (ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
//set the uri to the correlation property
return request.Properties.Via;
}
Once I had the uri, then I pulled the query string out in BeforeSendReply and if it was a wsdl request (at the standard address) and it specified flat, we can intercept the response and send the flat wsdl instead. While I was in there intercepting things, I thought it would be nice to add a link to the flattened wsdl in the default service description page. So, in the case that a request comes in at the base address with no arguments, then we look and see if the response is the default page and if so, we add our new link to it.
public void BeforeSendReply (ref System.ServiceModel.Channels.Message reply, object correlationState)
{
Uri URL=(Uri)correlationState;
string query=URL.Query;
string baseaddress=OperationContext.Current.Host.BaseAddresses[0].ToString();
//is there any possiblity that this is a wsdl request?
if (query.StartsWith(“?”, StringComparison.OrdinalIgnoreCase))
{
query=query.Substring(1);
List<string> queries=new List<string>((query.Length>0)?query.Split(new char[]{‘&’}):new string[0]);
//if it’s a flat wsdl request we need to fix it up
if (queries.Contains(“wsdl”) && queries.Contains(“flat”))
{
reply=BuildFlatWsdlReply(reply);
}
}
//if they’re just going to the base address, then we need to spruce up the default page
else if (URL.ToString().ToLower()==baseaddress.ToLower())
{
reply=BuildDescriptionPage(reply, baseaddress);
}
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
BuildFlatWSDLReply creates a new WsdlExporter, uses it to export the endpoints of the current ServiceHostBase, and sends it along to the methods cribbed from Tomas Restrepo’s code for export. Once the wsdl comes back it writes it out to a memory stream and passes it along in a reader to Message.CreateMessage and copies the original method’s header and properties to our new and improved message.
private System.ServiceModel.Channels.Message BuildFlatWsdlReply (System.ServiceModel.Channels.Message reply)
{
//create a wsdl exporter and export the endpoints
WsdlExporter exporter=new WsdlExporter();
ServiceHostBase myHost=OperationContext.Current.Host;
//creates the wsdl doc
exporter.ExportEndpoints(myHost.Description.Endpoints, new System.Xml.XmlQualifiedName(myHost.Description.Name, myHost.Description.Namespace));
System.ServiceModel.Channels.Message replyMessage=null;
//write the wsdl as xml to a memory stream
MemoryStream meStream=new MemoryStream();
XmlWriter meXML=XmlWriter.Create(meStream);
ServiceDescriptionCollection myServices=GetFlatWsdl(exporter);
myServices[0].Write(meXML);
meStream.Position=0;
//create a reader to pass into the message body
XmlReader meRead=XmlReader.Create(meStream);
replyMessage=System.ServiceModel.Channels.Message.CreateMessage(reply.Version, reply.Headers.Action, meRead);
//copy everything else from the original message
replyMessage.Headers.CopyHeadersFrom(reply.Headers);
replyMessage.Properties.CopyProperties(reply.Properties);
reply=replyMessage;
return reply;
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
The code for building the description page does something similar, but in this case it checks to see if the reply includes the wsdl url and if it finds it, appends the instructions for getting the flattened wsdl if needed. This particular piece of code is ugly, and could benefit from a judicial use of regular expressions. I’m including it here for completeness sake. Don’t flame me about it unless you decide to include the code to fix it. 😉
private static System.ServiceModel.Channels.Message BuildDescriptionPage (System.ServiceModel.Channels.Message reply, string baseaddress)
{
string body=reply.ToString()+“</HTML>”;
//reply stream omits the closing tag for some reason;
string stringToFind=“<A HREF=\”"+baseaddress+“?wsdl\”>”+baseaddress+“?wsdl</A></PRE></P>”;
int foundat=body.IndexOf(stringToFind);
//if the wsdl string is found, fix up the reply otherwise, just send it back since we can’t deal with it.
if (foundat>0)
{
System.ServiceModel.Channels.Message replyMessage=null;
//modify the standard page and shove it in a string
StringBuilder insertText=new StringBuilder();
insertText.Append(“<P class=’intro’>If your client requires a flattened version of the wsdl, it can be retrieved at:”);
insertText.Append(“<P><PRE><A HREF=\”"+baseaddress+“?wsdl&flat\”>”+baseaddress+“?wsdl&flat</A></PRE></P></P>”);
string newMessage=body.Insert(foundat+stringToFind.Length, insertText.ToString());
//create a memory stream and shove our new message into it
MemoryStream memStream=new MemoryStream();
XmlWriter meWrite=XmlWriter.Create(memStream);
meWrite.WriteRaw(newMessage);
meWrite.Flush();
memStream.Position=0;
//create a reader for the stream and use it to create the message
XmlReader meRead=XmlReader.Create(memStream);
replyMessage=System.ServiceModel.Channels.Message.CreateMessage(reply.Version, reply.Headers.Action, meRead);
//copy everything else from the original message
replyMessage.Headers.CopyHeadersFrom(reply.Headers);
replyMessage.Properties.CopyProperties(reply.Properties);
return replyMessage;
}
else
{
return reply;
}
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
That’s it in a nutshell. This code is without warranty, either expressed or implied. Hopefully it’ll save someone a little bit of pain.
Also, sorry about the code formatting. WordPress won’t let me do a lot about it without upgrading, so it looks like I’ll be moving to my own domain soon. Any recommendations for a .NET blogging engine are greatly appreciated.