By Klaus Salchner
Introduction
Every developer is relying on a development platform, the two major ones
being .NET and J2EE. The development platform provides the basic plumbing
like file IO, threading, XML processing and much more so we developer don’t
have to worry about the low level plumbing itself. You use the classes and
methods provided by the development platform. Both platforms have a rather
large class library and you rely on a comprehensive documentation to
understand it and to be able to use it. The same holds true for your own
projects and products. Every product needs to constantly innovate and be
expanded to survive in the market place. This means your own class library
is growing and you get quickly to the point where your team also relies on
good documentation to be able to understand, use and improve it. This holds
very true when you bring in new team members.
We all understand the need to comprehensively document our class libraries
and to keep it up to date with the code base. And still, documentation is an
ongoing pain for all of us. We want better documentation about the
development platform
we use and your team requires better documentation about each others code.
Many people experienced the pain trying to keep external documentation up to
date with an ever changing code base and also finding the right
documentation when looking at some piece of code. The Visual Studio .NET IDE
takes a first big step in easing that pain. It allows to document your code
in a way that the compiler can create an XML document describing your
classes, methods, properties, etc. This article explains how to use this
feature and then create a basic help file out of it.
How to create XML documentation tags in VS.NET
How this works is that you document your code using XML tags and when you
compile your project the compiler looks for those tags and uses them to
create a XML file describing your classes, methods, properties, etc. This is
available in C# since Visual Studio .NET 2003 and in VB.NET since Visual
Studio .NET 2005. In an empty line above your class, method or property
declaration type three slashes in C# or three apostrophes in VB.NET. The IDE
now creates a XML structure in front of your class, method or property which
reflects exactly its signature (see example below). You then type in the
documentation of the class, method or property.
/// <summary>
/// the form which allows to choose the XML and XSLT document and
/// then perform the transformation to create the help page
/// </summary>
partial class CodeDocumenter : Form
{
/// <summary>
/// user can choose the XML source file to use
/// </summary>
/// <param name="sender">event sender</param>
/// <param name="e">event arguments</param>
private void SourceFilePicker_Click(object sender, EventArgs e)
{
}
}
Methods have also a XML property tag for each argument. This allows you to
describe each argument in detail. It also creates a XML tag for the return
value itself so you can describe the expected return value. Any change to
the method signature requires also keeping the XML tags in sync. It is also
important to keep the order of the XML tags describing each argument in sync
with the order of your arguments in the method signature. So if you add a
new argument at the beginning of your method signature then you also need to
add the XML tag describing this argument before all the other ones. First
add a new line at the appropriate position, which already adds the three
slashes or apostrophes for you. Then type the left angle bracket (<) which
opens up an intellisense menu and shows you already the "param name =
'argument name'". Select it and add the right angle bracket (>) and the IDE
creates the complete XML tag for you and you add the description.
The intellisense menu shows also additional XML tags available. You can for
example add a permission tag describing the permissions callers need to have
to be able to call and execute the method successfully. You can use the
example tag to show an example how to use the method, etc. This makes it
very easy to document your classes, methods and properties comprehensively.
How to create the XML documentation file
You need to tell the compiler to create a XML documentation file using all
the XML documentation tags you added in your code. You can do this through
your project properties. The steps are different for VB.NET and C#. In C#
select the “Build” tab and under the “Output” group select the option “XML
Documentation File”. Beside the option is a textbox with the path and name
of the XML file. This gets set by default to the same path where your binary
gets created plus the project name as the file name, for example
“bin\Debug\CodeDocumenter.XML”. In VB.NET select the “Compile tab” and at
bottom check the option “Generate XML documentation file”. This creates at
the same location where your binary is created, which is by default the BIN
folder under your project folder, an XML documentation file (same name as
your project file). Next compile your code and look for the XML
documentation file. Here is a sample XML documentation file for the code
snippet shown above.
<?xml version="1.0" ?>
<doc>
<assembly>
<name>CodeDocumenter</name>
</assembly>
<members>
<member name="T:CodeDocumenter.CodeDocumenter">
<summary>
the form which allows to choose the XML and XSLT document and
then perform the transformation to create the help page
</summary>
</member>
<member name="M:CodeDocumenter.CodeDocumenter.SourceFilePicker_Click
(System.Object,System.EventArgs)">
<summary>user can choose the XML source file to use</summary>
<param name="sender">event sender</param>
<param name="e">event arguments</param>
</member>
</members>
</doc>
Any missing XML tag or mismatch between the XML tags and class, method or
property signature will be show as a warning. It is important to resolve
this to assure that documentation and code stay in sync. As soon as your
team gets negligent on that you will see the documentation gets less and
less valuable till you reach the point that it is out-of-sync and no longer
used. This is a discipline you need to instill in your development team.
How the Visual Studio .NET IDE uses the XML documentation file
The Visual Studio .NET IDE is also taking advantage of the XML documentation
file. The intellisense feature of the IDE will look for the XML
documentation file at the same location as the referenced binary file. If
present it will use the XML documentation file when showing the intellisense
and show right in your IDE your descriptions of the classes, methods, method
arguments, return values, properties, etc. This can be extremely useful for
other teams and team members. You get helpful information shown right when
you have a need to use the class, method or property. So when distributing
your binaries to other teams or team members make sure to pass along the XML
documentation file. They will appreciate that little bit of convenience.
How to create a help file for your code
You can use the same XML documentation file to create help files describing
your code. This is useful if you are selling components within the developer
community. But it is also useful to create help files describing your code
each time you create a release. It can be used by your support teams,
integration partners or by your sustained maintenance team (the team which
does the bug fixing after a release). The biggest advantage of this approach
is that code and documentation are kept in sync. Even if you have separate
technical writers creating your code documentation, they can still use the
help files generated out of the XML documentation files as a starting point.
This cuts down the time they need to gather information.
The simplest way to create a help file is to apply a XSL transformation to
the XML documentation file which creates a HTML file. The enclosed tool
allows you to select a XML documentation file, a XSL stylesheet, perform the
transformation and save it as a HTML help file. Here is how the XSL
stylesheet provided by the tool works. First the template for the matching
root node gets called. This one selects all assembly nodes and calls its
associated template.
<!-- root node: calls the assembly match which then calls the
members match -->
<xsl:template match="/">
<xsl:apply-templates select="//assembly"/>
</xsl:template>
The assembly template includes the CSS stylesheet provided by the tool. The
tool creates a copy of that CSS stylesheet at the same location it saves the
generated HTML help file. All HTML code generated references styles defined
in the CSS stylesheet, so it becomes easy to change the formatting of the
generated help file later on. Next it creates a HTML table to describe the
assembly. It then displays the assembly name in the first row of this table.
Next it selects all member nodes which have a name attribute containing the
“T:” characters. The XML documentation file adds all types as member nodes
with a name attribute always starting with “T:”. So effectively we select
all types and call its appropriate template.
<!-- assembly: build the help table with the title of the assembly
-->
<xsl:template match="assembly">
<LINK REL="STYLESHEET" TYPE="TEXT/CSS" HREF="HelpStyleSheet.css"/>
<TABLE CLASS="AsemblyTable" CELLPADDING="2" CELLSPACING="0">
<TR CLASS="AssemblyHeader">
<TD COLSPAN="3">
<xsl:text>Assembly </xsl:text>
<FONT CLASS="Title"><xsl:value-of
select="name"/></FONT>
</TD>
</TR>
<xsl:apply-templates select="//member[contains(@name,'T:')]"/>
</TABLE>
</xsl:template>
The member template first creates three variables. The first variable stores
the name of the member. This is done by cutting off the “T:” prefix and also
removing the assembly name. Looking at the XML documentation file you see
that each type is added using the syntax “T:assembly-name:type-name”. The
second member variable stores the type prefix and the assembly name. The
last variable stores a filter which we use when selecting all members of
this type, which is effectively “assembly-name.member-name.”. Then we add
for each processed type a row to the assembly table created in the assembly
template. We add an empty column at the beginning and at the end for
positioning purpose. The middle column contains the information about the
type itself. It creates another HTML table to display all the type
information. In the first row it shows the type name and if present its
description. Then it calls the ProcessTypeMembers template and passes along
a collection of nodes, which are all members for the processed type as well
as the name of the processed type.
<!-- process all types listed in the XML file -->
<xsl:template match="//member[contains(@name,'T:')]">
<!-- value of processed type; without assembly name & member identifier
-->
<xsl:variable name="MemberValue" select="substring(@name,
$AssemblyNameLength + 4,
string-length(@name) - $AssemblyNameLength - 3)"/>
<!-- get member identifier and assembly name which is before the member
name -->
<xsl:variable name="IdentifierAndAssemblyName"
select="substring-before(@name,
$MemberValue)"/>
<!-- get member filter; which queries all members of the processed type
-->
<xsl:variable name="MemberFilter" select="concat(concat(substring(
$IdentifierAndAssemblyName, 3, string-length(
$IdentifierAndAssemblyName) - 2), $MemberValue), '.')"/>
<TR CLASS="AssemblyBody">
<TD>
<xsl:text> </xsl:text>
</TD>
<TD>
<TABLE CLASS="TypeTable" CELLPADDING="2" CELLSPACING="0">
<TR CLASS="TypeHeader">
<TD COLSPAN="2">
<xsl:text>Type </xsl:text>
<FONT CLASS="Title"><xsl:value-of
select="$MemberValue"/></FONT>
<!-- output the type description if present -->
<xsl:if test="string-length(summary) > 0">
<xsl:text> - </xsl:text>
<xsl:value-of select="summary"/>
</xsl:if>
</TD>
</TR>
<!-- call template to process type members & pass along all
members -->
<xsl:call-template name="ProcessTypeMembers">
<xsl:with-param name="Members"
select="//member[contains(@name,
$MemberFilter)]"/>
<xsl:with-param name="TypeName" select="concat($MemberValue,
'.')"/>
</xsl:call-template>
</TABLE>
</TD>
<TD>
<xsl:text> </xsl:text>
</TD>
</TR>
</xsl:template>
The template ProcessTypeMembers does the work of adding now to the type
table all members (please download the code attached to this article). It
loops through the passed along member nodes. For each member node it first
gets the member name by cutting off the member identifier (can be “M:” for
method, “F:” for field and “P:” for property, etc.) and the assembly and
type name. The format used in the XML documentation file is
“member-identifier:assembly-name.type-name.member-name”. It then performs
the following logic:
- Constructors – If it finds the string “#ctor” in the name
attribute of the member node it knows it is a constructor. It adds a new row
to the type table, the first column showing that this is a constructor and
in the second column showing the description in the summary tag.
- Property – If it finds the member identifier “P:” in the name
attribute it knows that the member is a property. It adds a new row to the
type table, the first column showing that this is a property and in the
second column showing the name of the property with its description in the
summary tag.
- Field – If it finds the member identifier “F:” in the name
attribute it knows that the member is a field. It adds a new row to the type
table, the first column showing that this is a field and in the second
column showing the name of the field with its description in the summary
tag.
- Type – If it finds the identifier “T:” it knows that the node
represents the type itself and ignores it. The type node itself gets also
selected and passed along to the ProcessTypeMembers template but no
additional processing is needed.
- Method – If it finds the member identifier “M:” in the name
attribute it knows that the member is a method. It adds a new row to the
type table, the first column showing that this is a method and in the second
column showing the name of the method with its description in the summary
tag. It then adds another table for all method arguments. It selects all
param nodes for the processed member node and outputs the argument name and
its description. It then also checks if there is a return node, in case the
method has a return value. In that case it adds another row to the argument
table with the name return value and the description of the return
value.
- Unknown – All other nodes are considered as unknown. It adds a
new row to the type table, the first column showing that this is an unknown
member and in the second column showing the name of the member with its
description in the summary tag.
Again, please refer to the attached code for the complete XSL
stylesheet. If you want your help file to be structured use the attached XSL
stylesheet as template and modify as needed. You can also create multiple
XSL stylesheets and select the one to use when performing the
transformation. The attachment also includes the CSS stylesheet referenced
by the generated help file (called HelpStyleSheet.css). Modify it to change
the font-family, font-size, font-color and more.
Summary
The value of the documentation capabilities build into the Visual Studio
.NET IDE are often overlooked. They are easy to use and can improve the
productivity of your development team quite noticeably. As this article
shows there are ways how to further maximize the value of these
documentation capabilities. It can reduce quite a bit the costs associated
with documenting your code. This holds very much true when you need to
provide such documentation to members out-side of the team, whether these
are internal teams involved in supporting the product post release or
external customers. There are professional tools available on the market and
there are freeware tools available which are build using the XML
documentation file.
The following
article shows how you can utilize the VS.NET 2003 Power Toys to generate
a help file out of the XML documentation file and incorporate that help file
into the VS.NET IDE so it is available right within your IDE. The attached
example shows how easy it is to build such infrastructure. If you have
comments on this article or this topic, please contact me @ klaus_salchner@hotmail.com. I
want to hear if you learned something new. Contact me if you have questions
about this topic or article.
CodeDocumenter2005.zip
About the author
Klaus Salchner has worked for 14 years in the industry, nine years in Europe
and another five years in North America. As a Senior Enterprise Architect
with solid experience in enterprise software development, Klaus spends
considerable time on performance, scalability, availability,
maintainability, globalization/localization and security. The projects he
has been involved in are used by more than a million users in 50 countries
on three continents.
Klaus calls Vancouver, British Columbia his home at the moment. His next big
goal is doing the New York marathon in 2005. Klaus is interested in guest
speaking opportunities or as an author for .NET magazines or Web sites. He
can be contacted at
klaus_salchner@hotmail.com or http://www.enterprise-minds.com.
Enterprise application architecture and design consulting services are
available. If you want to hear more about it contact me! Involve me in your
projects and I will make a difference for you. Contact me if you have an
idea for an article or research project. Also contact me if you want to
co-author an article or join future research projects!