Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HelpPage xml documentation when controllers or models in different assembly

Using the new webapi 2.1 bits. For some reason when I open the help page no description details appear for any properties e.g.

/// <summary>
/// Some summary that shows correct
/// </summary>
[DataContract]
public class MyClass
{

    /// <summary>
    /// Display something.....
    /// </summary>
    [DataMember(Name = "Great")]
    public string MyGreatProperty { get; set; }

When I open the help page I can see the summary "Some summary that shows correct" however for the properties of this model none of the summaries are showing up.

Does webapi 2 not support descriptions on a property?

like image 595
Diver Dan Avatar asked Jan 27 '14 23:01

Diver Dan


2 Answers

You may use the following implementation of IDocumentationProvider that is a copy of XmlDocumentationProvider with minor modifications.

Usage example in HelpPageConfig.cs:

config.SetDocumentationProvider(new MultipleXmlDocumentationProvider(
            HttpContext.Current.Server.MapPath("~/bin/App.DataContracts.XML"),
            HttpContext.Current.Server.MapPath("~/bin/App.Controllers.XML")));

MultipleXmlDocumentationProvider class:

public class MultipleXmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider
{
    private IList<XPathNavigator> _documentNavigators;
    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="documentPaths">List of physical paths to XML documents.</param>
    public MultipleXmlDocumentationProvider(params string[] documentPaths)
    {
        if (!documentPaths.Any())
        {
            throw new ArgumentNullException("documentPaths");
        }

        var documents = documentPaths.Where(p => File.Exists(p)).ToList();

        _documentNavigators = documents.Select(p => new XPathDocument(p).CreateNavigator()).ToList();
    }

    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);
        var propertyNode = _documentNavigators.Select(n => n.SelectSingleNode(selectExpression)).FirstOrDefault(n => n != null);
        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 _documentNavigators.Select(n => n.SelectSingleNode(selectExpression)).FirstOrDefault(n => n != null);
        }

        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);
        return _documentNavigators.Select(n => n.SelectSingleNode(selectExpression)).FirstOrDefault(n => n != 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;
    }
}
like image 171
2 revs Avatar answered Oct 21 '22 20:10

2 revs


Thanks for the details. I am not sure if I would call this a bug in Web API as it does not inherently depend on an xml file for documentation. For example, If you have installed HelpPage, you would notice a file called XmlDocumentationProvider.cs under Areas\HelpPage. This implementation of provider looks at single xml file.

For your scenario, you could create a custom implementation of IDocumentationProvider & IModelDocumentationProvider in which based on the C# type, you can look at different xml documentation files. You could reuse most of the code in the default XmlDocumentationProvider.

like image 42
Kiran Avatar answered Oct 21 '22 20:10

Kiran