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
{
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
public ApiDocumentationGenerator(string pathToXmlDocumentationFile)
{
this.PathToXmlDocumentation = pathToXmlDocumentationFile;
}
public void Generate()
{
var ass = System.Reflection.Assembly.GetAssembly(typeof(BaseController));
foreach (var controller in ass.GetTypes())
{
if (controller.IsSubclassOf(typeof(BaseController))) ExtractMethods(controller);
}
}
private void ExtractMethods(Type controller)
{
foreach (var method in controller.GetMethods())
{
var attrs = System.Attribute.GetCustomAttributes(method);
var isAPIMethod = false;
foreach (System.Attribute attr in attrs)
{
if (attr is ApiMethodAttribute)
{
isAPIMethod = true;
break;
}
}
if (!isAPIMethod) continue;
var meth = new Method();
meth.Name = controller.Name.Replace("Controller", "") + "/" + method.Name;
this.Methods.Add(meth);
var memberName = "Controller." + method.Name;
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();
if (docInfo != null)
{
meth.Summary = docInfo.Summary;
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
:
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!