You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
6.9 KiB
162 lines
6.9 KiB
8 months ago
|
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;
|
||
|
using WebAPI.Areas.HelpPage.ModelDescriptions;
|
||
|
|
||
|
namespace WebAPI.Areas.HelpPage
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// A custom <see cref="IDocumentationProvider"/> that reads the API documentation from an XML documentation file.
|
||
|
/// </summary>
|
||
|
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}']";
|
||
|
|
||
|
/// <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 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 = _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 => 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 => 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;
|
||
|
}
|
||
|
}
|
||
|
}
|