ImmDoc .NET - a Lightweight Tool for Generating HTML Documentation

December 12, 2011


The project is now hosted on GitHub:


Do you remember Visual Studio 2003 and its nice feature of generating HTML documentation for your projects? I bet many of you do and just as I miss it now because unfortunately in Visual Studio 2005 it's no longer present.

Recently I needed such functionality to document a .NET library I've been developing and just couldn't find the right tool to do the job. I'm aware of the existence of NDoc but as far as I know it's quite dormant these days and it lacks support for .NET Framework 2.0. So I decided to spare some time to develop such tool by myself hoping that I would learn something new and that it'll be useful - not only for me but also for other programmers out there.

Below I put the list of topics which I had to broaden my knowledge on to complete the project so you know what you can learn by analyzing the provided source code:

  • reflection mechanisms,
  • subtleties of C# and MSIL,
  • regular expressions,
  • syntax of the XML comment files generated by the compiler,
  • how to read XML files and programmatically validate them against a schema definition,
  • embedded resources.


Before I give you more technical details about ImmDoc .NET you may want to visit this page and see the example output generated by this tool - documentation of three arbitrarily chosen assemblies from .NET Framework: System.Runtime.Remoting, System.Security and System.Transactions.


Currently there's only command-line interface available for ImmDoc .NET. One of the reasons for this is the fact that creating GUI requires some time :) However thanks to this approach one can easily use the program from batch scripts or tools supporting the build process such as NAnt.

The usage of ImmDoc .NET is pretty easy and straightforward. You can put all your assemblies and XML comment files in one folder along with the ImmDoc .NET executable, then run the program and after some processing you'll get a doc folder containing generated documentation. You can also explicitly give the names of the files you want ImmDoc .NET to process and you can use a few options which I list below:

  • -pn, -ProjectName:STRING - use this switch if you want to give your documentation project a meaningful name,
  • -ex, -Exclude:FILE - if you don't explicitly give the file names you can use this switch to exclude particular files from processing,
  • -od, -OutputDirectory:DIR - use this switch to specify the name of the directory where you want to have your documentation generated,
  • -fd, -ForceDelete - ImmDoc .NET will not delete the output directory if it already exists unless you use this switch,
  • -vl, -VerboseLevel:LEVEL - you can set various levels of verbosity (the greater the level is the more output from the program you'll get; LEVEL can be from 0 (no output) to 3 (full output).

There is also a feature which needs explicit mentioning. Because it's not possible to put XML comments on namespaces and because you can document multiple assemblies together, ImmDoc .NET provides a way to add additional comments. To do this you have to create XML file with .docs extension. Syntax of this file is very simple but note that it'll be validated against a schema (namely AdditionalDocumentation.xsd - you can find it in the zip archive with the source code). After you've created such file all you have to do is put it in one folder with your assemblies and XML comment files you want to process or just explicitly give its name as an argument to the program. Below is the example:

<?xml version="1.0" encoding="utf-8"?>

    <assembly name="SomeAssembly">
        Description of the assembly goes here.
      <namespace name="Some.Namespace">
          Description of the namespace goes here.


Project architecture and design

ImmDoc .NET comprises two assemblies (which are at the end merged by ILMerge tool, so that's why there's only one .exe file), namely ImmDocNetLib which actually does the whole job of analyzing assemblies and XML comment files and ImmDocNet - a console application which uses ImmDocNetLib and provides the interface for the user.

As I've already said ImmDocNetLib has two main responsibilities: analysis of given assemblies and generating the documentation. Let's have a closer look at the first task wich in principle involves using reflection to obtain detailed information about assemblies and modules they contain.

You'll find the most important classes in the Imm.ImmDocNetLib.MyReflection.MetaClasses namespace. These classes are lightweight and more or less exact equivalents of classes from System.Reflection namespace. Essentially almost all of them inherit from MetaClass class which among other things contains properties like Name, Summary and Remarks. Other classes add specific details concerning entities they represent, eg. MyMethodInfo holds information about return type and parameters. Such information is often represented in ImmDoc .NET by other classes which inherit from MetaClass (like for example MyParameterInfo, MyFieldInfo etc).

So in the first step of the processing information obtained using ordinary reflection mechanisms is combined with comments extracted from XML comment files to form an internal representation of given assemblies. Below is the UML diagram which will hopefully give you some overview of this representation (to avoid clutter not all classes are included):

After collecting all needed information it's finally time to actually start generating some documentation. Relevant classes are contained in the Imm.ImmDocNetLib.Documenters namespace. We have there some simple abstract class named Documenter from which we can derive other classes responsible for generating documentation in whatever format we like. I've implemented HTMLDocumenter class which creates set of HTML, CSS and JavaScript files which together constitute a nice MSDN-like documentation of given assemblies.

HTMLDocumenter is rather a big class so in order to reuse some functionality one would probably want to do some high-level refactoring before implementing one's own documenter.

To give you general overview of responsibilities of HTMLDocumenter I've created simple UML diagram which lists example methods implemented by this class.

Here are short descriptions of above methods:

bool GenerateDocumentation(
  string outputDirectory,
  DocumentationGenerationOptions options)

Initiates the process of generating documentation. Invokes such methods as PrepareOutputDirectory(), GenerateMainIndex(), ExtractStyleSheets(), ProcessAssemblies(), GenerateTableOfContents(), etc.

void CreateInvokableMembersOverloadsIndex(
    MyInvokableMembersOverloadsInfo myInvokableMembersOverloadsInfo,
    MyClassInfo declaringType,
    string dirName)

This method creates an HTML page which will contain index of all overloads of a particular method or constructor.

string CreateNamespaceMemberSyntaxString(MyClassInfo namespaceMember)

This method is responsible for creating a type declaration string. Such string comprises for example visibility modifier, base class, implemented interfaces, constraints on generic parameters, etc.

void ExtractBinaryResourceToFile(string resourceName, string fileName)

There are couple of graphics used in generated pages and this method is used to extract specified embedded resource to a physical file.

string ProcessComment(string contents)

This method processes every comment found in XML file in order to replace such tags as <code>, <c>, <see> and so on with appropriate HTML tags.

void WriteIndexHeader(
  StreamWriter sw,
  string pageTitle, 
  string[] sectionsNamesAndIndices)

This method writes a common HTML header used by almost all generated pages.

string ResolveLink(MetaClass metaClass)

For the generated documentation to be usable it is necessary to provide some navigation between pages and this method aids with the task of creating hyperlinks (for example to the particular member of a class).


I hope that someone will find ImmDoc .NET useful (I know I do ;). I'm waiting for some feedback and if there is some interest I'll continue to develop this project (it still lacks some minor features and after all a user-friendly front-end would be a nice thing to have). So if you have some suggestions, questions or maybe you've found a bug I'd be more than glad to "hear" from you - my e-mail is:

log4net Contextual Properties and ASP.NET

Probably most of you know and use the log4net library, which is a logging framework for the .NET platform. log4net is a great and practical tool, easy to use and at the same time flexible enough, so that you can tailor it to your more advanced logging scenarios. Most users of this library will sooner or later get acquainted with the possibility to store contextual data, which is then used when your messages are logged. Such data might be for example: the name of currently logged in user or the URL of the request.

As it turns out more often than not in our profession, not everything is as simple as it seems. Collection of contextual properties, which are bound to currently executing thread, that is ThreadContext.Properties because that's what we're talking about, is practically of no use in a web environment, if we want to utilize it in a standard way. By the standard way I mean the ordinary act of adding new items to the collection:

log4net.ThreadContext.Properties["UserName"] = Thread.CurrentPrincipal.Identity.Name;

If we make such assignment inside, for example, the Application_BeginRequest event, we expect, quite reasonably, that later in the process, every use of a logger will store our information and, what's more, this information will be bound to this specific request. Unfortunately, semantics of this collection will not guarantee this in a web environment due to so called thread agility employed by the ASP.NET runtime. What does it mean exactly? I guess it might be a surprise for you, as it was for me when I first found out about this, that a single HTTP request to a ASP.NET application may be handled by multiple threads during its life cycle (!).

In the context of log4net library, switching threads while processing a single request means, that we can't rely on the values stored in the ThreadContext.Properties collection. The way this collection is implemented along with the ASP.NET runtime's thread agility won't guarantee us that proper values will get passed to the new thread. As a result, if we happen to have a bug in our production system and we would want to analyze the path that a particular user took, we can assume with high probability, that this information will be distorted.

There is, however, a workaround for this problem. Namely, in ASP.NET there is one collection, for which we have guarantees, that its contents (bound to a particular request) will stay intact even after switching to a new thread. It's the HttpContext.Items collection. I'd like to show you the implementation of the solution based on this collection, that you should be able to effortlessly use in your own applications.

The main idea of this solution is to create a wrapper for values that we want to store in the ThreadContext.Properties. That way, at the moment log4net retrieves those values to log a message, our object can decide, whether this value should be taken from HttpContext.Items collection (if we're in a web environment) or whether the original value should be returned.

I called the class AdaptivePropertyProvider<T>. Its code is presented below:

public class AdaptivePropertyProvider<T>
  private readonly string _propertyName;
  private readonly T _propertyValue;

  #region Constructor(s)

  protected internal AdaptivePropertyProvider(string propertyName, T propertyValue)
    if (string.IsNullOrEmpty(propertyName))
      throw new ArgumentNullException("propertyName");

    _propertyName = propertyName;
    _propertyValue = propertyValue;

    if (HttpContext.Current != null)
      HttpContext.Current.Items[GetPropertyName()] = propertyValue;


  #region Overrides of object

  public override string ToString()
    if (HttpContext.Current != null)
      var item = HttpContext.Current.Items[GetPropertyName()];

      return item != null ? item.ToString() : null;

    if (!ReferenceEquals(_propertyValue, null))
      return _propertyValue.ToString();

    return null;


  #region Private helper methods

  private string GetPropertyName()
    return string.Format("{0}{1}", AdaptivePropertyProvider.PropertyNamePrefix, _propertyName);


It's worth noting that the logic of retrieveing a value is placed in the ToString() method. Here we exploit the fact, that log4net itself uses this method for formatting objects stored in the Properties collection.

For convenience let's define a second class, which will expose a factory method for constructing objects of class AdaptivePropertyProvider<T>.

public class AdaptivePropertyProvider
  public const string PropertyNamePrefix = "log4net_app_";

  #region Factory methods

  public static AdaptivePropertyProvider<T> Create<T>(string propertyName, T propertyValue)
    return new AdaptivePropertyProvider<T>(propertyName, propertyValue);


Usage of such factory method allows the compiler to infer the appropriate type of the property value (that's not the case with ordinary constructors — we can't omit generic type parameters).

log4net.ThreadContext.Properties["UserName"] =
  AdaptivePropertyProvider.Create("UserName", Thread.CurrentPrincipal.Identity.Name);

By remembering the lesson that we should keep contextual properties in a way described above, we should be able to avoid unpleasant surprises during analysis of production logs in a time of crisis :]