Blackball Software is a software consultancy and design company based in Auckland, New Zealand.

We love what we do and we're very good at it. We don't muck around, bamboozling or intimidating with nerd speak.

Nup, we just listen to what you need then we build it better than you ever imagined.

Automatic code documentation based on your C# comments

Automatic code documentation based on your C# comments

I’ve written a few APIs over the years and the worst part is writing the documentation:

  • it takes extra time
  • it must be updated every time you make changes to your code
  • it is a duplication of work because I already document my code inline anyway

So, here’s a handy utility I wrote which will use reflection to whip through your code and draw out the comments.

Generating XML Documentation

Before proceeding, you must setup your project to generate an XML file of your code comments.  This is done via the Properties –> Build menu in your project (presumably it’s a web project).  See this screenshot below:

This generates an xml file in the bin directory every time you build.  The file contains all your code comments, ready for parsing by my helper utility.  The format is something like this:

So, with this in place, here is the utility class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using Common;


namespace Web.Code
{
    /// <summary>
    /// Creates documentation for our various API methods
    /// </summary>
    public class ApiDocumentationGenerator
    {

        #region Sub classes

        public class Parameter
        {
            public string Name { get; set; }
            public string Type { get; set; }
            public string Description { get; set; }
        }

        public class Method
        {
            public string Name { get; set; }
            public List<Parameter> Parameters = new List<Parameter>();
            public string Summary { get; set; }
        }

        #endregion

        #region Properties

        public List<Method> Methods = new List<Method>();
        private string PathToXmlDocumentation = "";

        private XDocument _XmlDocumentation = null;
        private XDocument XmlDocumentation
        {
            get
            {
                if (_XmlDocumentation == null)
                {
                    _XmlDocumentation = XDocument.Load(this.PathToXmlDocumentation);
                }
                return _XmlDocumentation;
            }
        }

        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="pathToXmlDocumentationFile"></param>
        public ApiDocumentationGenerator(string pathToXmlDocumentationFile)
        {
            this.PathToXmlDocumentation = pathToXmlDocumentationFile;
        }

        /// <summary>
        /// Generates our classes
        /// </summary>
        public void Generate()
        {
            // BaseController is a class I wrote which all my MVC *Controller methods inherit from.  If you don't have a base class, you can just use
            // whatever parent class you know your own API methods sit within.  And if there is no parent class, then just get every time in the assembly
            var ass = System.Reflection.Assembly.GetAssembly(typeof(BaseController));

            // Get each class
            foreach (var controller in ass.GetTypes())
            {
                if (controller.IsSubclassOf(typeof(BaseController))) ExtractMethods(controller);
            }
        }

        /// <summary>
        /// Finds the methods in this controller
        /// </summary>
        /// <param name="controller"></param>
        private void ExtractMethods(Type controller)
        {
            foreach (var method in controller.GetMethods())
            {
                // My API methods are decorated with a custom attribute, ApiMethodAttribute, so only show those ones
                var attrs = System.Attribute.GetCustomAttributes(method);

                // Check our attributes show we have an API method
                var isAPIMethod = false;
                foreach (System.Attribute attr in attrs)
                {
                    if (attr is ApiMethodAttribute)
                    {
                        isAPIMethod = true;
                        break;
                    }
                }

                // Break if not an API method
                if (!isAPIMethod) continue;

                // Parse out properties
                var meth = new Method();
                meth.Name = controller.Name.Replace("Controller", "") + "/" + method.Name;
                this.Methods.Add(meth);

                // Quick hack to detect the XML segment we want - I know that all my API methods are in *Controller methods, so I can just restrict to this
                var memberName = "Controller." + method.Name;

                // Get the methods from our documentation
                var docInfo = (
                              from m in this.XmlDocumentation.Descendants("members").Descendants("member")
                              where m.Attribute("name").Value.Contains(memberName)
                              select new {
                                  Summary = m.Descendants("summary").First().Value,
                                  Params = m.Descendants("param")
                              }
                ).FirstOrDefault();

                // Now copy the XML back into my method/parameter classes
                if (docInfo != null)
                {
                    meth.Summary = docInfo.Summary;

                    // Add parameters
                    foreach (var param in docInfo.Params)
                    {
                        var p = new Parameter();
                        meth.Parameters.Add(p);
                        p.Name = param.Attribute("name").Value;
                        p.Description = param.Value;
                    }
                }
            }
        }
    }
}

Note that this won’t compile for you because it references a custom attribute, ApiMethodAttribute, and my base class, BaseController.  However, you could delete the logic around these and the documentation should still generate.

Now it’s just a matter of calling the class.  I use mine in an MVC ActionResult:

/// <summary>
/// Uses reflection to document our API methods
/// </summary>
/// <returns></returns>
public ActionResult APIDocumentation()
{
    var pathToDocs = HttpContext.Server.MapPath("~/bin/APIDocumentation.xml");
    var model = new ApiDocumentationGenerator(pathToDocs);
    model.Generate();
    return View("admin/apidocumentation", model);
}

And for clarity, I’ll include my View, so you can see how it’s used to render the results to the user:

@model Web.Code.ApiDocumentationGenerator
@{
    ViewBag.Title = "API Documentation";
}

<h2>API Documentation</h2>

@foreach (var method in Model.Methods.OrderBy(x => x.Name))
{
    <h3>@method.Name</h3>
    <p><i>@method.Summary</i></p>
    if (method.Parameters.Any())
     {
         <ul>
             @foreach (var param in method.Parameters)
             {
                 <li><strong>@param.Name </strong>@param.Description</li>
             }
         </ul>
     }
}

Hope that helps.

Best practice front-end architecture using Microsoft ASP.Net MVC and Rivets.js

Best practice front-end architecture using Microsoft ASP.Net MVC and Rivets.js

Comparing the performance of AppFabric against Sql Server

Comparing the performance of AppFabric against Sql Server