2

Good morning,

I have a solution consisting of two projects. One is a class library, containing common classes that will be used in other projects. The other is a WebAPI 2.1 project.

I am generating the help files for the API by using the automatic help page generator, but I've noticed that when it references classes in the Common project, it doesn't use the summaries.

Is there any way of making it do this? I've searched online but I can't find any solution to this. I've also tried installing the help page generator in the Common project, but to no avail.

user176504
  • 183
  • 1
  • 1
  • 10
  • Following is an example of how you can do this: http://stackoverflow.com/questions/22165724/web-api-help-page-xml-comments-from-more-than-1-files/22169357#22169357 – Kiran Jul 03 '14 at 17:35
  • 2
    Duplication of: http://stackoverflow.com/questions/21895257/how-can-xml-documentation-for-web-api-include-documentation-from-beyond-the-main – Adam Ocsvari May 08 '15 at 16:21

1 Answers1

3

I had the same problem and this is just because the documentation provider takes only one xml document which is the one generated from current project (if you followed the instructions you may remember adding:

config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/[YOUR XML DOCUMENT]"));

The rest of your classes and their metadata is added to a different xml document. What I did is I modified the xml documentation provider to accept multiple xml document path and search through each document for metadata related to the class been enquired about. You would need to add the xml document from the various dlls you are referencing but this definitely solved my issue. See below for the variation of the XmlDocumentationProvider:

public class XmlMultiDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider
{
    private List<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}']";

    /// <summary>
    /// Initializes a new instance of the <see cref="XmlDocumentationProvider"/> class.
    /// </summary>
    /// <param name="documentPath">The physical path to XML document.</param>
    public XmlMultiDocumentationProvider(params string[] documentPath)
    {
        if (documentPath == null)
        {
            throw new ArgumentNullException("documentPath");
        }
        _documentNavigator = new List<XPathNavigator>();
        foreach (string s in documentPath)
        {
            XPathDocument xpath = new XPathDocument(s);
            _documentNavigator.Add(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 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 = null;
        foreach(XPathNavigator navigator in _documentNavigator )
        {
          XPathNavigator temp  = navigator.SelectSingleNode(selectExpression);
          if (temp != null)
          {
              propertyNode = temp;
              break;
          }
        }
        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));
            foreach (XPathNavigator navigator in _documentNavigator)
            {
                XPathNavigator temp = navigator.SelectSingleNode(selectExpression);
                if (temp != null)
                {
                    return temp;
                }
            }

        }

        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 => 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);
        foreach (XPathNavigator navigator in _documentNavigator)
        {
            XPathNavigator temp = navigator.SelectSingleNode(selectExpression);
            if (temp != null)
            {
                return temp;
            }
        }
        return null;
    }

    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 => 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;
    }
}

You can get the idea or simply use the whole class at your discretion. Just remember to replace in your HelpPageConfig -> SetDocumentationProvider call the class name and add the path to the various xml documents.

  • Would you mind to share with us, how you would reference from the Startup project to an other project in the same solution, to access generated XML documentation? ( How is your input array looks for the "config.SetDocumentationProvider(new XmlMultiDocumentationProvider(urls));" call? – Adam Ocsvari May 08 '15 at 16:10
  • @AdamOcsvari you can do it like this: config.SetDocumentationProvider(new XmlMultiDocumentationProvider( HttpContext.Current.Server.MapPath("~/App_Data/XmlDocumentation.xml"), HttpContext.Current.Server.MapPath("~/bin/example.namespace.xml"))); – Adam Brown Oct 23 '18 at 02:21