Introduction
XML webservices can be developed very easily within the .NET Framework, and moreover it provides us with a nice help generator so you can automatically show your clients how they have to talk with your service.
Unfortunately, the generated help doesn't reflect inherited data structures, so it becomes quite useless as soon as such structures are being used to build the interface. Inheritance is of course a very useful technique when authoring XML webservices. So, for instance, you may require login data for each request or you may want to reply some status data with each response. Thus every request structure may inherit a generic one and every response may inherit a generic response structure.
Background
Data exchange between an XML webservice and its consumers is defined by a WSDL contract. The .NET Framework creates it automatically from the structures defined by the webservice author. Such a contract is represented externally by an XML Schema document and internally by an instance of the System.Web.Services.Description.ServiceDescription
class. In either case, the WSDL reflects exactly any inheritance structure using the XML Schema extension element pointing to the base structure. However, the default help rendering algorithm doesn't follow these extension references, therefore base structures remain unseen.
Solution
You can write your own help generator in order to generate any kind of documentation pages you like. The location of the WsdlHelpGenerator
can be defined in the web.config.
But I really don't want to rewrite or change the default rendering algorithm, so my solution is to modify the WSDL data slightly for it, using a simple XSL transformation that replaces any extension reference by the appropriate base structure. This can be done by handling the PreLoad
event of the WsdlHelpGenerator
, so no code needs to be changed at all, apart from adding two methods to the WsdlHelpGenerator
class.
Three Steps to Solve the Problem
Get the installed default description generator DefaultWsdlHelpGenerator.aspx (on my computer, it's in C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG) and save it as WsdlHelpGenerator.aspx in the web directory of your webservice. Open your web.config and put...
<webServices>
<wsdlHelpGenerator href="WsdlHelpGenerator.aspx" />
</webServices>
... inside the <system.web>
section.
Open WsdlHelpGenerator
.aspx and add these two methods directly below the Page_Load
method:
protected override void OnPreLoad(EventArgs e) {
base.OnPreLoad(e);
string key = Context.Items["wsdlsWithPost"] != null ?
"wsdlsWithPost" : "wsdls";
serviceDescriptions = (ServiceDescriptionCollection)Context.Items[key];
TransformServiceDescriptions(ref serviceDescriptions);
Context.Items[key] = serviceDescriptions;
}
void TransformServiceDescriptions(ref ServiceDescriptionCollection descriptions) {
ServiceDescriptionCollection transformed = new ServiceDescriptionCollection();
System.Xml.Xsl.XslCompiledTransform xslt =
new System.Xml.Xsl.XslCompiledTransform();
xslt.Load(Server.MapPath("WsdlHelp.xsl"));
foreach (ServiceDescription desc in descriptions)
{
MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream();
desc.Write(ms1);
ms1.Position = 0;
xslt.Transform(new System.Xml.XPath.XPathDocument(ms1), null, ms2);
ms2.Position = 0;
transformed.Add(ServiceDescription.Read(ms2));
ms1.Dispose();
ms2.Dispose();
}
descriptions = transformed;
}
Finally, to get this code working, put the transformation file WsdlHelp.xsl into the web directory of your webservice. It may look as follows:
="1.0"="utf-8"
<xsl:stylesheet version="1.0"
xmlns:xsl=http://www.w3.org/1999/XSL/Transform
xmlns:s="http://www.w3.org/2001/XMLSchema">
<xsl:output
method="xml"
indent="no"
encoding="utf-8"
omit-xml-declaration="no"
/>
<xsl:template match="/" xml:space="default">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="*" priority="0.5" xml:space="default">
<xsl:copy>
<xsl:copy-of select="attribute::*" />
<xsl:choose>
<xsl:when test="child::*" />
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates select="child::*" />
</xsl:copy>
</xsl:template>
<xsl:template match="s:complexType" priority="1.0">
<xsl:element name="s:complexType" namespace="http://www.w3.org/2001/XMLSchema">
<xsl:copy-of select="attribute::*" />
<xsl:element name="s:sequence">
<xsl:copy-of select=".//s:sequence/*" />
<xsl:if test="./s:complexContent/s:extension">
<xsl:comment> schema extension expanded: <xsl:value-of
select="./s:complexContent/s:extension/@base"/> </xsl:comment>
<xsl:call-template name="fetch-sequence">
<xsl:with-param name="typename"
select="substring-after(./s:complexContent/s:extension/@base,':')" />
</xsl:call-template>
</xsl:if>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template name="fetch-sequence">
<xsl:param name="typename" />
<xsl:copy-of select="//s:complexType[@name = $typename]//s:sequence/*" />
<xsl:if test="//s:complexType[@name = $typename]/s:complexContent/s:extension">
<xsl:call-template name="fetch-sequence">
<xsl:with-param name="typename"
select="substring-after(//s:complexType[@name = $typename]
/s:complexContent/s:extension/@base,':')" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Points of Interest
Once you have your own WsdlHelpGenerator
, you can also easily modify the appearance, add your own stylesheets, your company logo, etc.
If you don't want to have your webservice method list sorted alphabetically, replace the SortedList methodsTable
within the Page_Load
method of the WsdlHelpGenerator
by an unsorted list.
You may also have a look at the excellent article Externalizing Web Service Documentation and apply XSL transformation there. Just put the C# code above into the ServiceDescriptionGenerator.aspx.cs file.
History
- 17th August, 2008: Initial post