title: Extending webapi 2.1's help page to include additional data
body: <p>I found a pretty dated post from Yao at microsoft <a href="http://blogs.msdn.com/b/yaohuang1/archive/2012/12/10/asp-net-web-api-help-page-part-3-advanced-help-page-customizations.aspx" rel="nofollow">advanced help page customizations</a></p>
  
  <p>I have tried to implement the steps however I am unable to get my custom comment to display within the help documentation.</p>
  
  <p>Within my  </p>
  
  <blockquote>
    <p>XmlDocumentationProvider.cs</p>
  </blockquote>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Globalization;
  using System.Linq;
  using System.Reflection;
  using System.Web.Http.Controllers;
  using System.Web.Http.Description;
  using System.Xml.XPath;
  
  
  namespace MyApp.Areas.HelpPage
  {
      /// &lt;summary&gt;
      /// A custom &lt;see cref="IDocumentationProvider"/&gt; that reads the API documentation from an XML documentation file.
      /// &lt;/summary&gt;
      public class XmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider
      {
          private XPathNavigator _documentNavigator;
          private const string TypeExpression = "/doc/members/member[@name='T:{0}']";
          private const string MethodExpression = "/doc/members/member[@name='M:{0}']";
          private const string PropertyExpression = "/doc/members/member[@name='P:{0}']";
          private const string FieldExpression = "/doc/members/member[@name='F:{0}']";
          private const string ParameterExpression = "param[@name='{0}']";
  
          /// &lt;summary&gt;
          /// Initializes a new instance of the &lt;see cref="XmlDocumentationProvider"/&gt; class.
          /// &lt;/summary&gt;
          /// &lt;param name="documentPath"&gt;The physical path to XML document.&lt;/param&gt;
          public XmlDocumentationProvider(string documentPath)
          {
              if (documentPath == null)
              {
                  throw new ArgumentNullException("documentPath");
              }
              XPathDocument xpath = new XPathDocument(documentPath);
              _documentNavigator = xpath.CreateNavigator();
          }
  
          public string GetDocumentation(HttpControllerDescriptor controllerDescriptor)
          {
              XPathNavigator typeNode = GetTypeNode(controllerDescriptor.ControllerType);
              return GetTagValue(typeNode, "summary");
          }
  
          public virtual string GetDocumentation(HttpActionDescriptor actionDescriptor)
          {
              XPathNavigator methodNode = GetMethodNode(actionDescriptor);
              return GetTagValue(methodNode, "summary");
          }
  
  
          public virtual string GetActionTypes(HttpActionDescriptor actionDescriptor)
          {
              XPathNavigator methodNode = GetMethodNode(actionDescriptor);
              return GetTagValue(methodNode, "action");
          }
  
  
          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;
          }
  
          public string GetResponseDocumentation(HttpActionDescriptor actionDescriptor)
          {
              XPathNavigator methodNode = GetMethodNode(actionDescriptor);
              return GetTagValue(methodNode, "returns");
          }
  
  
  
          public string GetDocumentation(MemberInfo member)
          {
              string memberName = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(member.DeclaringType), member.Name);
              string expression = member.MemberType == MemberTypes.Field ? FieldExpression : PropertyExpression;
              string selectExpression = String.Format(CultureInfo.InvariantCulture, expression, memberName);
              XPathNavigator propertyNode = _documentNavigator.SelectSingleNode(selectExpression);
              return GetTagValue(propertyNode, "summary");
          }
  
          public string GetDocumentation(Type type)
          {
              XPathNavigator typeNode = GetTypeNode(type);
              return GetTagValue(typeNode, "summary");
          }
  
          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}", GetTypeName(method.DeclaringType), method.Name);
              ParameterInfo[] parameters = method.GetParameters();
              if (parameters.Length != 0)
              {
                  string[] parameterTypeNames = parameters.Select(param =&gt; GetTypeName(param.ParameterType)).ToArray();
                  name += String.Format(CultureInfo.InvariantCulture, "({0})", String.Join(",", parameterTypeNames));
              }
  
              return name;
          }
  
          private static string GetTagValue(XPathNavigator parentNode, string tagName)
          {
              if (parentNode != null)
              {
                  XPathNavigator node = parentNode.SelectSingleNode(tagName);
                  if (node != null)
                  {
                      return node.Value.Trim();
                  }
              }
  
              return null;
          }
  
          private XPathNavigator GetTypeNode(Type type)
          {
              string controllerTypeName = GetTypeName(type);
              string selectExpression = String.Format(CultureInfo.InvariantCulture, TypeExpression, controllerTypeName);
              return _documentNavigator.SelectSingleNode(selectExpression);
          }
  
          private static string GetTypeName(Type type)
          {
              string name = type.FullName;
              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 genericTypeName = genericType.FullName;
  
                  // Trim the generic parameter counts from the name
                  genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));
                  string[] argumentTypeNames = genericArguments.Select(t =&gt; GetTypeName(t)).ToArray();
                  name = String.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", genericTypeName, String.Join(",", argumentTypeNames));
              }
              if (type.IsNested)
              {
                  // Changing the nested type name from OuterType+InnerType to OuterType.InnerType to match the XML documentation syntax.
                  name = name.Replace("+", ".");
              }
  
              return name;
          }
  
          public interface ICustomCommentProvider
          {
              string GetCustomComment(HttpActionDescriptor actionDescriptor);
          }
  
          public virtual string GetCustomComment(HttpActionDescriptor actionDescriptor)
          {
              XPathNavigator methodNode = GetMethodNode(actionDescriptor);
              if (methodNode != null)
              {
                  XPathNavigator returnsNode = methodNode.SelectSingleNode("customcomment");
                  if (returnsNode != null)
                  {
                      return returnsNode.Value.Trim();
                  }
              }
  
              return null;
          }
      }
  }
  </code></pre>
  
  <p>I have modifie</p>
  
  <pre><code> public interface ICustomCommentProvider
      {
          string GetCustomComment(HttpActionDescriptor actionDescriptor);
      }
  
      public virtual string GetCustomComment(HttpActionDescriptor actionDescriptor)
      {
          XPathNavigator methodNode = GetMethodNode(actionDescriptor);
          if (methodNode != null)
          {
              XPathNavigator returnsNode = methodNode.SelectSingleNode("customcomment");
              if (returnsNode != null)
              {
                  return returnsNode.Value.Trim();
              }
          }
  
          return null;
      }
  </code></pre>
  
  <p>I have updated the </p>
  
  <blockquote>
    <p>HelpPageModel.cs </p>
  </blockquote>
  
  <p>to include</p>
  
  <blockquote>
  <pre><code>public string CustomMessage { get; set; }
  </code></pre>
  </blockquote>
  
  <p>Last thing I have updated is the GenerateApiModel</p>
  
  <pre><code> private static HelpPageApiModel GenerateApiModel(ApiDescription apiDescription, HttpConfiguration config)
      {
          HelpPageApiModel apiModel = new HelpPageApiModel()
          {
              ApiDescription = apiDescription,
          };
  
  
  
          var responseDocProvider =
              config.Services.GetDocumentationProvider() as XmlDocumentationProvider.ICustomCommentProvider;
          if (responseDocProvider != null)
          {
              apiModel.CustomMessage = responseDocProvider.GetCustomComment(apiDescription.ActionDescriptor);
          }
  </code></pre>
  
  <p>Everytime I run the application responseDocProvider is null. Is there anything sticking out that looks like I have done it wrong?</p>
  
  <p>Help on the problem would be very much appreciated </p>