PageRenderTime 46ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Server/DeviceHive.API/Controllers/MetadataController.cs

https://github.com/oryol/devicehive-.net
C# | 371 lines | 298 code | 51 blank | 22 comment | 87 complexity | fc49a41ab644d9af27e7f9c8ae09cf9c MD5 | raw file
Possible License(s): MIT
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Web.Http;
  7. using System.Web.Http.Controllers;
  8. using System.Web.Http.Description;
  9. using System.Xml.Linq;
  10. using System.Xml.XPath;
  11. using DeviceHive.API.Business;
  12. using DeviceHive.API.Filters;
  13. using DeviceHive.API.Mapping;
  14. using DeviceHive.API.Models;
  15. using DeviceHive.Data.Validation;
  16. using Ninject;
  17. namespace DeviceHive.API.Controllers
  18. {
  19. [ApiExplorerSettings(IgnoreApi = true)]
  20. public class MetadataController : BaseController
  21. {
  22. private XmlCommentReader DataXmlCommentReader { get; set; }
  23. private XmlCommentReader ApiXmlCommentReader { get; set; }
  24. public MetadataController([Named("Data")] XmlCommentReader dataXmlCommentReader, [Named("API")] XmlCommentReader apiXmlCommentReader)
  25. {
  26. DataXmlCommentReader = dataXmlCommentReader;
  27. ApiXmlCommentReader = apiXmlCommentReader;
  28. }
  29. public Metadata Get()
  30. {
  31. var apiExplorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();
  32. var metadata = new Metadata
  33. {
  34. Resources = apiExplorer.ApiDescriptions
  35. .OrderBy(d => d.ActionDescriptor.ControllerDescriptor.ControllerName)
  36. .Select(d => new { Resource = GetResourceType(d), Method = d })
  37. .Where(d => d.Resource != null)
  38. .GroupBy(d => d.Resource, d => d.Method)
  39. .Select(g => new { Resource = g.Key, Methods = g.GroupBy(m =>
  40. GetMethodName((ReflectedHttpActionDescriptor)m.ActionDescriptor)).Select(m => m.First()).ToList(), })
  41. .Select(cd => new MetadataResource
  42. {
  43. Name = cd.Resource == null ? null : cd.Resource.Name,
  44. Documentation = GetTypeDocumentation(cd.Resource),
  45. Properties = cd.Resource == null ? null : GetTypeParameters(cd.Resource),
  46. Methods = cd.Methods
  47. .Where(m => GetMethodName((ReflectedHttpActionDescriptor)m.ActionDescriptor) != null)
  48. .Select(m => new MetadataMethod
  49. {
  50. Name = GetMethodName((ReflectedHttpActionDescriptor)m.ActionDescriptor),
  51. Documentation = GetMethodDocumentation((ReflectedHttpActionDescriptor)m.ActionDescriptor),
  52. Verb = m.HttpMethod.Method,
  53. Uri = "/" + m.RelativePath,
  54. UriParameters = GetUrlParameters(m),
  55. Authorization = GetAuthorization(m.ActionDescriptor),
  56. RequestDocumentation = GetRequestDocumentation((ReflectedHttpActionDescriptor)m.ActionDescriptor),
  57. RequestParameters = GetRequestParameters((ReflectedHttpActionDescriptor)m.ActionDescriptor),
  58. ResponseDocumentation = GetResponseDocumentation((ReflectedHttpActionDescriptor)m.ActionDescriptor),
  59. ResponseParameters = GetResponseParameters((ReflectedHttpActionDescriptor)m.ActionDescriptor),
  60. }).ToArray(),
  61. }).ToArray(),
  62. };
  63. return metadata;
  64. }
  65. private Type GetResourceType(ApiDescription description)
  66. {
  67. var descriptor = description.ActionDescriptor as ReflectedHttpActionDescriptor;
  68. // read associated resource type (resource XML element on controller type)
  69. var typeElement = ApiXmlCommentReader.GetTypeElement(descriptor.MethodInfo.DeclaringType);
  70. if (typeElement == null)
  71. return null;
  72. var resourceElement = typeElement.Element("resource");
  73. if (resourceElement == null)
  74. return null;
  75. return GetCrefType(resourceElement);
  76. }
  77. private string GetTypeDocumentation(Type type)
  78. {
  79. if (type == null)
  80. return null;
  81. // get XML documentation for specified resource type
  82. var resourceTypeElement = DataXmlCommentReader.GetTypeElement(type);
  83. return resourceTypeElement.ElementContents("summary");
  84. }
  85. private string GetMethodName(ReflectedHttpActionDescriptor descriptor)
  86. {
  87. // get XML name parameter for corresponding method
  88. var methodElement = ApiXmlCommentReader.GetMethodElement(descriptor.MethodInfo);
  89. return methodElement.ElementContents("name");
  90. }
  91. private string GetMethodDocumentation(ReflectedHttpActionDescriptor descriptor)
  92. {
  93. // get XML documentation for corresponding method
  94. var methodElement = ApiXmlCommentReader.GetMethodElement(descriptor.MethodInfo);
  95. return methodElement.ElementContents("summary");
  96. }
  97. private MetadataParameter[] GetUrlParameters(ApiDescription method)
  98. {
  99. // get list of URL parameters for specified method
  100. var descriptor = method.ActionDescriptor as ReflectedHttpActionDescriptor;
  101. return method.ParameterDescriptions
  102. .Where(p => p.Source == ApiParameterSource.FromUri)
  103. .Select(p =>
  104. {
  105. var parameterElement = ApiXmlCommentReader.GetMethodParameterElement(descriptor.MethodInfo, p.Name);
  106. return new MetadataParameter
  107. {
  108. Name = p.Name,
  109. Type = ToJsonType(p.ParameterDescriptor.ParameterType),
  110. Documentation = parameterElement.Contents(),
  111. IsRequred = p.ParameterDescriptor.DefaultValue == null &&
  112. !(p.ParameterDescriptor.ParameterType.IsGenericType &&
  113. p.ParameterDescriptor.ParameterType.GetGenericTypeDefinition() == typeof(Nullable<>)),
  114. };
  115. }).ToArray();
  116. }
  117. private string GetRequestDocumentation(ReflectedHttpActionDescriptor descriptor)
  118. {
  119. // get XML documentation for 'json' parameter element
  120. return ApiXmlCommentReader.GetMethodParameterElement(descriptor.MethodInfo, "json").Contents()
  121. ?? "Do not supply a request body with this method.";
  122. }
  123. private MetadataParameter[] GetRequestParameters(ReflectedHttpActionDescriptor descriptor)
  124. {
  125. var methodElement = ApiXmlCommentReader.GetMethodElement(descriptor.MethodInfo);
  126. if (methodElement == null)
  127. return null;
  128. var parameters = new List<MetadataParameter>();
  129. // read cref type on the 'json' parameter element
  130. var methodParamElement = methodElement.XPathSelectElement("param[@name='json']");
  131. if (methodParamElement != null)
  132. {
  133. var resourceType = GetCrefType(methodParamElement);
  134. if (resourceType != null)
  135. {
  136. parameters.AddRange(GetTypeParameters(resourceType, JsonMapperEntryMode.OneWay));
  137. }
  138. }
  139. // read XML request element
  140. var requestElement = methodElement.Element("request");
  141. if (requestElement != null)
  142. {
  143. foreach (var parameterElement in requestElement.Elements("parameter")
  144. .Where(p => p.Attribute("name") != null))
  145. {
  146. var name = (string)parameterElement.Attribute("name");
  147. var type = (string)parameterElement.Attribute("type");
  148. var mode = (string)parameterElement.Attribute("mode");
  149. var required = (bool?)parameterElement.Attribute("required");
  150. // remove an existing parameter
  151. if (mode == "remove")
  152. {
  153. parameters.RemoveAll(p => p.Name.StartsWith(name));
  154. continue;
  155. }
  156. // add or update an existing parameter
  157. var param = parameters.FirstOrDefault(p => p.Name == name);
  158. if (param == null)
  159. {
  160. param = new MetadataParameter { Name = name, Type = type };
  161. parameters.Add(param);
  162. }
  163. if (!string.IsNullOrEmpty(parameterElement.Contents()))
  164. {
  165. param.Documentation = parameterElement.Contents();
  166. }
  167. if (required != null)
  168. {
  169. param.IsRequred = required.Value;
  170. }
  171. // if element includes cref - parse the specified type and add parameters from it
  172. var cref = GetCrefType(parameterElement);
  173. if (cref != null)
  174. {
  175. parameters.AddRange(GetTypeParameters(cref, JsonMapperEntryMode.OneWay, param.Name + (type == "array" ? "[]" : null)));
  176. }
  177. }
  178. }
  179. return parameters.ToArray();
  180. }
  181. private string GetResponseDocumentation(ReflectedHttpActionDescriptor descriptor)
  182. {
  183. // get XML documentation for returns element
  184. return ApiXmlCommentReader.GetMethodReturnsElement(descriptor.MethodInfo).Contents()
  185. ?? "If successful, this method returns an empty response body.";
  186. }
  187. private MetadataParameter[] GetResponseParameters(ReflectedHttpActionDescriptor descriptor)
  188. {
  189. var methodElement = ApiXmlCommentReader.GetMethodElement(descriptor.MethodInfo);
  190. if (methodElement == null)
  191. return null;
  192. var parameters = new List<MetadataParameter>();
  193. // read cref type on the XML returns element
  194. var methodReturnsElement = methodElement.Element("returns");
  195. if (methodReturnsElement != null)
  196. {
  197. var resourceType = GetCrefType(methodReturnsElement);
  198. if (resourceType != null)
  199. {
  200. parameters.AddRange(GetTypeParameters(resourceType, JsonMapperEntryMode.OneWayToSource));
  201. }
  202. }
  203. // read XML response element
  204. var responseElement = methodElement.Element("response");
  205. if (responseElement != null)
  206. {
  207. foreach (var parameterElement in responseElement.Elements("parameter")
  208. .Where(p => p.Attribute("name") != null))
  209. {
  210. var name = (string)parameterElement.Attribute("name");
  211. var type = (string)parameterElement.Attribute("type");
  212. var mode = (string)parameterElement.Attribute("mode");
  213. // remove an existing parameter
  214. if (mode == "remove")
  215. {
  216. parameters.RemoveAll(p => p.Name.StartsWith(name));
  217. continue;
  218. }
  219. // add or update an existing parameter
  220. var param = parameters.FirstOrDefault(p => p.Name == name);
  221. if (param == null)
  222. {
  223. param = new MetadataParameter { Name = name, Type = type };
  224. parameters.Add(param);
  225. }
  226. if (!string.IsNullOrEmpty(parameterElement.Contents()))
  227. {
  228. param.Documentation = parameterElement.Contents();
  229. }
  230. // if element includes cref - parse the specified type and add parameters from it
  231. var cref = GetCrefType(parameterElement);
  232. if (cref != null)
  233. {
  234. parameters.AddRange(GetTypeParameters(cref, JsonMapperEntryMode.OneWayToSource, param.Name + (type == "array" ? "[]" : null)));
  235. }
  236. }
  237. }
  238. return parameters.ToArray();
  239. }
  240. private Type GetCrefType(XElement element)
  241. {
  242. // parses cref attribute and returns corresponding type
  243. var typeName = (string)element.Attribute("cref");
  244. if (typeName == null || !typeName.StartsWith("T:"))
  245. return null;
  246. var type = Type.GetType(typeName.Substring(2), false);
  247. if (type == null)
  248. {
  249. type = AppDomain.CurrentDomain.GetAssemblies()
  250. .Select(a => a.GetType(typeName.Substring(2)))
  251. .Where(t => t != null).FirstOrDefault();
  252. }
  253. return type;
  254. }
  255. private MetadataParameter[] GetTypeParameters(Type type, JsonMapperEntryMode? exclude = null, string prefix = null)
  256. {
  257. // get JSON mapping manager
  258. var mapper = JsonMapperManager.GetMapper(type);
  259. if (mapper == null)
  260. return new MetadataParameter[] { };
  261. // create parameters from mapping entries
  262. var parameters = new List<MetadataParameter>();
  263. foreach (var parameter in mapper.Entries.Where(e => exclude == null || e.Mode != exclude.Value))
  264. {
  265. // add parameter that corresponds to the mapped property
  266. var isJsonObject = parameter.EntityProperty.IsDefined(typeof(JsonFieldAttribute), false);
  267. var param = new MetadataParameter
  268. {
  269. Name = (prefix == null ? null : prefix + ".") + parameter.JsonProperty,
  270. Type = isJsonObject ? "object" : ToJsonType(parameter.EntityProperty.PropertyType),
  271. IsRequred = IsRequired(parameter.EntityProperty),
  272. Documentation = DataXmlCommentReader.GetPropertyElement(parameter.EntityProperty).ElementContents("summary"),
  273. };
  274. parameters.Add(param);
  275. // add child object parameters
  276. if (param.Type == "object" && !isJsonObject)
  277. {
  278. parameters.AddRange(GetTypeParameters(parameter.EntityProperty.PropertyType, exclude, param.Name));
  279. }
  280. }
  281. return parameters.ToArray();
  282. }
  283. private string GetAuthorization(HttpActionDescriptor description)
  284. {
  285. var filters = description.GetFilters().Union(description.ControllerDescriptor.GetFilters()).ToList();
  286. var authorizeUser = filters.OfType<AuthorizeUserAttribute>().FirstOrDefault();
  287. var authorizeDevice = filters.OfType<AuthorizeDeviceAttribute>().FirstOrDefault();
  288. var authorizeDeviceOrUser = filters.OfType<AuthorizeDeviceOrUserAttribute>().FirstOrDefault();
  289. if (authorizeDeviceOrUser != null)
  290. return string.Format("Device and User ({0})", authorizeDeviceOrUser.Roles ?? "All Roles");
  291. if (authorizeUser != null)
  292. return string.Format("User ({0})", authorizeUser.Roles ?? "All Roles");
  293. if (authorizeDevice != null)
  294. return "Device";
  295. return "None";
  296. }
  297. private bool IsRequired(PropertyInfo property)
  298. {
  299. if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
  300. return false;
  301. if (property.PropertyType.IsValueType)
  302. return true;
  303. return property.GetCustomAttributes(typeof(RequiredAttribute), true).Any();
  304. }
  305. private string ToJsonType(Type type)
  306. {
  307. if (type == null)
  308. return null;
  309. if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
  310. type = type.GetGenericArguments().First();
  311. if (type == typeof(Guid))
  312. return "guid";
  313. if (Type.GetTypeCode(type) == TypeCode.Object)
  314. return "object";
  315. if (Type.GetTypeCode(type).ToString().StartsWith("Int") || Type.GetTypeCode(type).ToString().StartsWith("UInt"))
  316. return "integer";
  317. return type.Name.ToLower();
  318. }
  319. }
  320. }