Currently, I have an ASP.NET Web Api running side by side with an ASP.NET web application. Using the methodology outlined by Yao Huang in his blog posts relating to the HelpPage NuGet package, specifically XMLDocumentationProvider, I have successfully been able to document my API - almost...
The problem that I am running into is that particular API method calls inherit (and possibly override) the behavior from a base class, we'll call it ApiBase
. As a result, these methods do not have their own documentation, as they are inheriting the method from the base class and usually need no further explanation than that provided by ApiBase
. What I'm trying to do (and have no direction as to how to do it) is, for methods that inherit implementation from a base class, find a way to inherit and display the associated XML comments associated with the base class and display them with the method being invoked.
To clarify this, here is a sample method from ApiBase
:
public abstract class ApiBase
{
/// <summary>
/// Stub used as an example
/// </summary>
/// <param name="stub">Random boolean value</param>
/// <returns>The boolean value</returns>
public virtual bool returnBool(bool stub)
{
return stub;
}
/// <summary>
/// Stub #2 used as an example
/// </summary>
/// <param name="stub">Random int value</param>
/// <returns>A value less than the parameter</returns>
public virtual int returnLess(int stub)
{
return (stub - 10);
}
}
Later on, let's say we have a controller class, ApiChild
, that inherits this functionality:
public class ApiChild : ApiBase
{
public override int returnLess(int stub)
{
return (stub - 20);
}
}
When ApiChild
invokes either returnBool
or returnLess
, I'd like their comments to be grabbed from the base class by XMLDocumentationProvider
. ApiExplorer already grabs the rest of the information, such as return type, parameters, etc., successfully from the base class, but I am unsure as to how to extend this functionality to comment retrieval. I appreciate any help you may be able to provide. My own thoughts lean toward some sort of implementation that uses reflection to analyze a method during run time to determine its attributes and somehow appropriately grab comments from its parent if need be. Any thoughts/guidance is much appreciated.
For reference, here is the current code used in XMLDocumentationProvider
:
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Description;
using System.Xml.XPath;
namespace MyProject.Areas.HelpPage
{
public interface IResponseDocumentationProvider
{
string GetResponseDocumentation(HttpActionDescriptor actionDescriptor);
}
/// <summary>
/// A custom <see cref="IDocumentationProvider"/> that reads the API documentation from an XML documentation file.
/// </summary>
public class XmlDocumentationProvider : IDocumentationProvider, IResponseDocumentationProvider
{
private XPathNavigator _documentNavigator;
private const string MethodExpression = "/doc/members/member[@name='M:{0}']";
private const string ParameterExpression = "param[@name='{0}']";
/// <summary>
/// Initializes a new instance of the <see cref="XmlDocumentationProvider"/> class.
/// </summary>
/// <param name="documentPath">The physical path to XML document.</param>
public XmlDocumentationProvider(string documentPath)
{
if (documentPath == null)
{
throw new ArgumentNullException("documentPath");
}
XPathDocument xpath = new XPathDocument(documentPath);
_documentNavigator = xpath.CreateNavigator();
}
public virtual string GetDocumentation(HttpActionDescriptor actionDescriptor)
{
XPathNavigator methodNode = GetMethodNode(actionDescriptor);
if (methodNode != null)
{
XPathNavigator summaryNode = methodNode.SelectSingleNode("summary");
if (summaryNode != null)
{
return summaryNode.Value.Trim();
}
}
return null;
}
public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
{
ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
if (reflectedParameterDescriptor != null)
{
XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor);
if (methodNode != null)
{
string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName));
if (parameterNode != null)
{
return parameterNode.Value.Trim();
}
}
}
return null;
}
private XPathNavigator GetMethodNode(HttpActionDescriptor actionDescriptor)
{
ReflectedHttpActionDescriptor reflectedActionDescriptor = actionDescriptor as ReflectedHttpActionDescriptor;
if (reflectedActionDescriptor != null)
{
string selectExpression = String.Format(CultureInfo.InvariantCulture, MethodExpression, GetMemberName(reflectedActionDescriptor.MethodInfo));
return _documentNavigator.SelectSingleNode(selectExpression);
}
return null;
}
private static string GetMemberName(MethodInfo method)
{
string name = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", method.DeclaringType.FullName, method.Name);
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length != 0)
{
string[] parameterTypeNames = parameters.Select(param => GetTypeName(param.ParameterType)).ToArray();
name += String.Format(CultureInfo.InvariantCulture, "({0})", String.Join(",", parameterTypeNames));
}
return name;
}
private static string GetTypeName(Type type)
{
if (type.IsGenericType)
{
// Format the generic type name to something like: Generic{System.Int32,System.String}
Type genericType = type.GetGenericTypeDefinition();
Type[] genericArguments = type.GetGenericArguments();
string typeName = genericType.FullName;
// Trim the generic parameter counts from the name
typeName = typeName.Substring(0, typeName.IndexOf('`'));
string[] argumentTypeNames = genericArguments.Select(t => GetTypeName(t)).ToArray();
return String.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", typeName, String.Join(",", argumentTypeNames));
}
return type.FullName;
}
public virtual string GetResponseDocumentation(HttpActionDescriptor actionDescriptor)
{
XPathNavigator methodNode = GetMethodNode(actionDescriptor);
if (methodNode != null)
{
XPathNavigator returnsNode = methodNode.SelectSingleNode("returns");
if (returnsNode != null)
return returnsNode.Value.Trim();
}
return null;
}
}
}