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.

HelpPageConfigurationExtensions.cs 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.ComponentModel;
  5. using System.Diagnostics;
  6. using System.Diagnostics.CodeAnalysis;
  7. using System.Globalization;
  8. using System.Linq;
  9. using System.Net.Http;
  10. using System.Net.Http.Headers;
  11. using System.Web.Http;
  12. using System.Web.Http.Controllers;
  13. using System.Web.Http.Description;
  14. using Authentication_test.Areas.HelpPage.ModelDescriptions;
  15. using Authentication_test.Areas.HelpPage.Models;
  16. namespace Authentication_test.Areas.HelpPage
  17. {
  18. public static class HelpPageConfigurationExtensions
  19. {
  20. private const string ApiModelPrefix = "MS_HelpPageApiModel_";
  21. /// <summary>
  22. /// Sets the documentation provider for help page.
  23. /// </summary>
  24. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  25. /// <param name="documentationProvider">The documentation provider.</param>
  26. public static void SetDocumentationProvider(this HttpConfiguration config, IDocumentationProvider documentationProvider)
  27. {
  28. config.Services.Replace(typeof(IDocumentationProvider), documentationProvider);
  29. }
  30. /// <summary>
  31. /// Sets the objects that will be used by the formatters to produce sample requests/responses.
  32. /// </summary>
  33. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  34. /// <param name="sampleObjects">The sample objects.</param>
  35. public static void SetSampleObjects(this HttpConfiguration config, IDictionary<Type, object> sampleObjects)
  36. {
  37. config.GetHelpPageSampleGenerator().SampleObjects = sampleObjects;
  38. }
  39. /// <summary>
  40. /// Sets the sample request directly for the specified media type and action.
  41. /// </summary>
  42. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  43. /// <param name="sample">The sample request.</param>
  44. /// <param name="mediaType">The media type.</param>
  45. /// <param name="controllerName">Name of the controller.</param>
  46. /// <param name="actionName">Name of the action.</param>
  47. public static void SetSampleRequest(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName)
  48. {
  49. config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Request, controllerName, actionName, new[] { "*" }), sample);
  50. }
  51. /// <summary>
  52. /// Sets the sample request directly for the specified media type and action with parameters.
  53. /// </summary>
  54. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  55. /// <param name="sample">The sample request.</param>
  56. /// <param name="mediaType">The media type.</param>
  57. /// <param name="controllerName">Name of the controller.</param>
  58. /// <param name="actionName">Name of the action.</param>
  59. /// <param name="parameterNames">The parameter names.</param>
  60. public static void SetSampleRequest(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName, params string[] parameterNames)
  61. {
  62. config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Request, controllerName, actionName, parameterNames), sample);
  63. }
  64. /// <summary>
  65. /// Sets the sample request directly for the specified media type of the action.
  66. /// </summary>
  67. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  68. /// <param name="sample">The sample response.</param>
  69. /// <param name="mediaType">The media type.</param>
  70. /// <param name="controllerName">Name of the controller.</param>
  71. /// <param name="actionName">Name of the action.</param>
  72. public static void SetSampleResponse(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName)
  73. {
  74. config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Response, controllerName, actionName, new[] { "*" }), sample);
  75. }
  76. /// <summary>
  77. /// Sets the sample response directly for the specified media type of the action with specific parameters.
  78. /// </summary>
  79. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  80. /// <param name="sample">The sample response.</param>
  81. /// <param name="mediaType">The media type.</param>
  82. /// <param name="controllerName">Name of the controller.</param>
  83. /// <param name="actionName">Name of the action.</param>
  84. /// <param name="parameterNames">The parameter names.</param>
  85. public static void SetSampleResponse(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName, params string[] parameterNames)
  86. {
  87. config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Response, controllerName, actionName, parameterNames), sample);
  88. }
  89. /// <summary>
  90. /// Sets the sample directly for all actions with the specified media type.
  91. /// </summary>
  92. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  93. /// <param name="sample">The sample.</param>
  94. /// <param name="mediaType">The media type.</param>
  95. public static void SetSampleForMediaType(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType)
  96. {
  97. config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType), sample);
  98. }
  99. /// <summary>
  100. /// Sets the sample directly for all actions with the specified type and media type.
  101. /// </summary>
  102. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  103. /// <param name="sample">The sample.</param>
  104. /// <param name="mediaType">The media type.</param>
  105. /// <param name="type">The parameter type or return type of an action.</param>
  106. public static void SetSampleForType(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, Type type)
  107. {
  108. config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, type), sample);
  109. }
  110. /// <summary>
  111. /// Specifies the actual type of <see cref="System.Net.Http.ObjectContent{T}"/> passed to the <see cref="System.Net.Http.HttpRequestMessage"/> in an action.
  112. /// The help page will use this information to produce more accurate request samples.
  113. /// </summary>
  114. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  115. /// <param name="type">The type.</param>
  116. /// <param name="controllerName">Name of the controller.</param>
  117. /// <param name="actionName">Name of the action.</param>
  118. public static void SetActualRequestType(this HttpConfiguration config, Type type, string controllerName, string actionName)
  119. {
  120. config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Request, controllerName, actionName, new[] { "*" }), type);
  121. }
  122. /// <summary>
  123. /// Specifies the actual type of <see cref="System.Net.Http.ObjectContent{T}"/> passed to the <see cref="System.Net.Http.HttpRequestMessage"/> in an action.
  124. /// The help page will use this information to produce more accurate request samples.
  125. /// </summary>
  126. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  127. /// <param name="type">The type.</param>
  128. /// <param name="controllerName">Name of the controller.</param>
  129. /// <param name="actionName">Name of the action.</param>
  130. /// <param name="parameterNames">The parameter names.</param>
  131. public static void SetActualRequestType(this HttpConfiguration config, Type type, string controllerName, string actionName, params string[] parameterNames)
  132. {
  133. config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Request, controllerName, actionName, parameterNames), type);
  134. }
  135. /// <summary>
  136. /// Specifies the actual type of <see cref="System.Net.Http.ObjectContent{T}"/> returned as part of the <see cref="System.Net.Http.HttpRequestMessage"/> in an action.
  137. /// The help page will use this information to produce more accurate response samples.
  138. /// </summary>
  139. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  140. /// <param name="type">The type.</param>
  141. /// <param name="controllerName">Name of the controller.</param>
  142. /// <param name="actionName">Name of the action.</param>
  143. public static void SetActualResponseType(this HttpConfiguration config, Type type, string controllerName, string actionName)
  144. {
  145. config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Response, controllerName, actionName, new[] { "*" }), type);
  146. }
  147. /// <summary>
  148. /// Specifies the actual type of <see cref="System.Net.Http.ObjectContent{T}"/> returned as part of the <see cref="System.Net.Http.HttpRequestMessage"/> in an action.
  149. /// The help page will use this information to produce more accurate response samples.
  150. /// </summary>
  151. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  152. /// <param name="type">The type.</param>
  153. /// <param name="controllerName">Name of the controller.</param>
  154. /// <param name="actionName">Name of the action.</param>
  155. /// <param name="parameterNames">The parameter names.</param>
  156. public static void SetActualResponseType(this HttpConfiguration config, Type type, string controllerName, string actionName, params string[] parameterNames)
  157. {
  158. config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Response, controllerName, actionName, parameterNames), type);
  159. }
  160. /// <summary>
  161. /// Gets the help page sample generator.
  162. /// </summary>
  163. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  164. /// <returns>The help page sample generator.</returns>
  165. public static HelpPageSampleGenerator GetHelpPageSampleGenerator(this HttpConfiguration config)
  166. {
  167. return (HelpPageSampleGenerator)config.Properties.GetOrAdd(
  168. typeof(HelpPageSampleGenerator),
  169. k => new HelpPageSampleGenerator());
  170. }
  171. /// <summary>
  172. /// Sets the help page sample generator.
  173. /// </summary>
  174. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  175. /// <param name="sampleGenerator">The help page sample generator.</param>
  176. public static void SetHelpPageSampleGenerator(this HttpConfiguration config, HelpPageSampleGenerator sampleGenerator)
  177. {
  178. config.Properties.AddOrUpdate(
  179. typeof(HelpPageSampleGenerator),
  180. k => sampleGenerator,
  181. (k, o) => sampleGenerator);
  182. }
  183. /// <summary>
  184. /// Gets the model description generator.
  185. /// </summary>
  186. /// <param name="config">The configuration.</param>
  187. /// <returns>The <see cref="ModelDescriptionGenerator"/></returns>
  188. public static ModelDescriptionGenerator GetModelDescriptionGenerator(this HttpConfiguration config)
  189. {
  190. return (ModelDescriptionGenerator)config.Properties.GetOrAdd(
  191. typeof(ModelDescriptionGenerator),
  192. k => InitializeModelDescriptionGenerator(config));
  193. }
  194. /// <summary>
  195. /// Gets the model that represents an API displayed on the help page. The model is initialized on the first call and cached for subsequent calls.
  196. /// </summary>
  197. /// <param name="config">The <see cref="HttpConfiguration"/>.</param>
  198. /// <param name="apiDescriptionId">The <see cref="ApiDescription"/> ID.</param>
  199. /// <returns>
  200. /// An <see cref="HelpPageApiModel"/>
  201. /// </returns>
  202. public static HelpPageApiModel GetHelpPageApiModel(this HttpConfiguration config, string apiDescriptionId)
  203. {
  204. object model;
  205. string modelId = ApiModelPrefix + apiDescriptionId;
  206. if (!config.Properties.TryGetValue(modelId, out model))
  207. {
  208. Collection<ApiDescription> apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions;
  209. ApiDescription apiDescription = apiDescriptions.FirstOrDefault(api => String.Equals(api.GetFriendlyId(), apiDescriptionId, StringComparison.OrdinalIgnoreCase));
  210. if (apiDescription != null)
  211. {
  212. model = GenerateApiModel(apiDescription, config);
  213. config.Properties.TryAdd(modelId, model);
  214. }
  215. }
  216. return (HelpPageApiModel)model;
  217. }
  218. private static HelpPageApiModel GenerateApiModel(ApiDescription apiDescription, HttpConfiguration config)
  219. {
  220. HelpPageApiModel apiModel = new HelpPageApiModel()
  221. {
  222. ApiDescription = apiDescription,
  223. };
  224. ModelDescriptionGenerator modelGenerator = config.GetModelDescriptionGenerator();
  225. HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator();
  226. GenerateUriParameters(apiModel, modelGenerator);
  227. GenerateRequestModelDescription(apiModel, modelGenerator, sampleGenerator);
  228. GenerateResourceDescription(apiModel, modelGenerator);
  229. GenerateSamples(apiModel, sampleGenerator);
  230. return apiModel;
  231. }
  232. private static void GenerateUriParameters(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator)
  233. {
  234. ApiDescription apiDescription = apiModel.ApiDescription;
  235. foreach (ApiParameterDescription apiParameter in apiDescription.ParameterDescriptions)
  236. {
  237. if (apiParameter.Source == ApiParameterSource.FromUri)
  238. {
  239. HttpParameterDescriptor parameterDescriptor = apiParameter.ParameterDescriptor;
  240. Type parameterType = null;
  241. ModelDescription typeDescription = null;
  242. ComplexTypeModelDescription complexTypeDescription = null;
  243. if (parameterDescriptor != null)
  244. {
  245. parameterType = parameterDescriptor.ParameterType;
  246. typeDescription = modelGenerator.GetOrCreateModelDescription(parameterType);
  247. complexTypeDescription = typeDescription as ComplexTypeModelDescription;
  248. }
  249. // Example:
  250. // [TypeConverter(typeof(PointConverter))]
  251. // public class Point
  252. // {
  253. // public Point(int x, int y)
  254. // {
  255. // X = x;
  256. // Y = y;
  257. // }
  258. // public int X { get; set; }
  259. // public int Y { get; set; }
  260. // }
  261. // Class Point is bindable with a TypeConverter, so Point will be added to UriParameters collection.
  262. //
  263. // public class Point
  264. // {
  265. // public int X { get; set; }
  266. // public int Y { get; set; }
  267. // }
  268. // Regular complex class Point will have properties X and Y added to UriParameters collection.
  269. if (complexTypeDescription != null
  270. && !IsBindableWithTypeConverter(parameterType))
  271. {
  272. foreach (ParameterDescription uriParameter in complexTypeDescription.Properties)
  273. {
  274. apiModel.UriParameters.Add(uriParameter);
  275. }
  276. }
  277. else if (parameterDescriptor != null)
  278. {
  279. ParameterDescription uriParameter =
  280. AddParameterDescription(apiModel, apiParameter, typeDescription);
  281. if (!parameterDescriptor.IsOptional)
  282. {
  283. uriParameter.Annotations.Add(new ParameterAnnotation() { Documentation = "Required" });
  284. }
  285. object defaultValue = parameterDescriptor.DefaultValue;
  286. if (defaultValue != null)
  287. {
  288. uriParameter.Annotations.Add(new ParameterAnnotation() { Documentation = "Default value is " + Convert.ToString(defaultValue, CultureInfo.InvariantCulture) });
  289. }
  290. }
  291. else
  292. {
  293. Debug.Assert(parameterDescriptor == null);
  294. // If parameterDescriptor is null, this is an undeclared route parameter which only occurs
  295. // when source is FromUri. Ignored in request model and among resource parameters but listed
  296. // as a simple string here.
  297. ModelDescription modelDescription = modelGenerator.GetOrCreateModelDescription(typeof(string));
  298. AddParameterDescription(apiModel, apiParameter, modelDescription);
  299. }
  300. }
  301. }
  302. }
  303. private static bool IsBindableWithTypeConverter(Type parameterType)
  304. {
  305. if (parameterType == null)
  306. {
  307. return false;
  308. }
  309. return TypeDescriptor.GetConverter(parameterType).CanConvertFrom(typeof(string));
  310. }
  311. private static ParameterDescription AddParameterDescription(HelpPageApiModel apiModel,
  312. ApiParameterDescription apiParameter, ModelDescription typeDescription)
  313. {
  314. ParameterDescription parameterDescription = new ParameterDescription
  315. {
  316. Name = apiParameter.Name,
  317. Documentation = apiParameter.Documentation,
  318. TypeDescription = typeDescription,
  319. };
  320. apiModel.UriParameters.Add(parameterDescription);
  321. return parameterDescription;
  322. }
  323. private static void GenerateRequestModelDescription(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator, HelpPageSampleGenerator sampleGenerator)
  324. {
  325. ApiDescription apiDescription = apiModel.ApiDescription;
  326. foreach (ApiParameterDescription apiParameter in apiDescription.ParameterDescriptions)
  327. {
  328. if (apiParameter.Source == ApiParameterSource.FromBody)
  329. {
  330. Type parameterType = apiParameter.ParameterDescriptor.ParameterType;
  331. apiModel.RequestModelDescription = modelGenerator.GetOrCreateModelDescription(parameterType);
  332. apiModel.RequestDocumentation = apiParameter.Documentation;
  333. }
  334. else if (apiParameter.ParameterDescriptor != null &&
  335. apiParameter.ParameterDescriptor.ParameterType == typeof(HttpRequestMessage))
  336. {
  337. Type parameterType = sampleGenerator.ResolveHttpRequestMessageType(apiDescription);
  338. if (parameterType != null)
  339. {
  340. apiModel.RequestModelDescription = modelGenerator.GetOrCreateModelDescription(parameterType);
  341. }
  342. }
  343. }
  344. }
  345. private static void GenerateResourceDescription(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator)
  346. {
  347. ResponseDescription response = apiModel.ApiDescription.ResponseDescription;
  348. Type responseType = response.ResponseType ?? response.DeclaredType;
  349. if (responseType != null && responseType != typeof(void))
  350. {
  351. apiModel.ResourceDescription = modelGenerator.GetOrCreateModelDescription(responseType);
  352. }
  353. }
  354. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is recorded as ErrorMessages.")]
  355. private static void GenerateSamples(HelpPageApiModel apiModel, HelpPageSampleGenerator sampleGenerator)
  356. {
  357. try
  358. {
  359. foreach (var item in sampleGenerator.GetSampleRequests(apiModel.ApiDescription))
  360. {
  361. apiModel.SampleRequests.Add(item.Key, item.Value);
  362. LogInvalidSampleAsError(apiModel, item.Value);
  363. }
  364. foreach (var item in sampleGenerator.GetSampleResponses(apiModel.ApiDescription))
  365. {
  366. apiModel.SampleResponses.Add(item.Key, item.Value);
  367. LogInvalidSampleAsError(apiModel, item.Value);
  368. }
  369. }
  370. catch (Exception e)
  371. {
  372. apiModel.ErrorMessages.Add(String.Format(CultureInfo.CurrentCulture,
  373. "An exception has occurred while generating the sample. Exception message: {0}",
  374. HelpPageSampleGenerator.UnwrapException(e).Message));
  375. }
  376. }
  377. private static bool TryGetResourceParameter(ApiDescription apiDescription, HttpConfiguration config, out ApiParameterDescription parameterDescription, out Type resourceType)
  378. {
  379. parameterDescription = apiDescription.ParameterDescriptions.FirstOrDefault(
  380. p => p.Source == ApiParameterSource.FromBody ||
  381. (p.ParameterDescriptor != null && p.ParameterDescriptor.ParameterType == typeof(HttpRequestMessage)));
  382. if (parameterDescription == null)
  383. {
  384. resourceType = null;
  385. return false;
  386. }
  387. resourceType = parameterDescription.ParameterDescriptor.ParameterType;
  388. if (resourceType == typeof(HttpRequestMessage))
  389. {
  390. HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator();
  391. resourceType = sampleGenerator.ResolveHttpRequestMessageType(apiDescription);
  392. }
  393. if (resourceType == null)
  394. {
  395. parameterDescription = null;
  396. return false;
  397. }
  398. return true;
  399. }
  400. private static ModelDescriptionGenerator InitializeModelDescriptionGenerator(HttpConfiguration config)
  401. {
  402. ModelDescriptionGenerator modelGenerator = new ModelDescriptionGenerator(config);
  403. Collection<ApiDescription> apis = config.Services.GetApiExplorer().ApiDescriptions;
  404. foreach (ApiDescription api in apis)
  405. {
  406. ApiParameterDescription parameterDescription;
  407. Type parameterType;
  408. if (TryGetResourceParameter(api, config, out parameterDescription, out parameterType))
  409. {
  410. modelGenerator.GetOrCreateModelDescription(parameterType);
  411. }
  412. }
  413. return modelGenerator;
  414. }
  415. private static void LogInvalidSampleAsError(HelpPageApiModel apiModel, object sample)
  416. {
  417. InvalidSample invalidSample = sample as InvalidSample;
  418. if (invalidSample != null)
  419. {
  420. apiModel.ErrorMessages.Add(invalidSample.ErrorMessage);
  421. }
  422. }
  423. }
  424. }