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 Service.MeterProcessState.Areas.HelpPage.ModelDescriptions; namespace Service.MeterProcessState.Areas.HelpPage { /// /// A custom that reads the API documentation from an XML documentation file. /// 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}']"; /// /// Initializes a new instance of the class. /// /// The physical path to XML document. 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; } } }