PageRenderTime 50ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/WCFWebApi/src/Microsoft.ApplicationServer.Http/Microsoft/ApplicationServer/Http/Dispatcher/HelpPage.cs

#
C# | 1645 lines | 1483 code | 138 blank | 24 comment | 241 complexity | 9f683ae33460c35c409f396d04142923 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. // <copyright>
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. // </copyright>
  4. namespace Microsoft.ApplicationServer.Http.Dispatcher
  5. {
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. using System.Diagnostics.CodeAnalysis;
  10. using System.Globalization;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Net;
  14. using System.Net.Http;
  15. using System.Net.Http.Formatting;
  16. using System.Runtime.Serialization;
  17. using System.Runtime.Serialization.Json;
  18. using System.ServiceModel.Channels;
  19. using System.ServiceModel.Description;
  20. using System.ServiceModel.Dispatcher;
  21. using System.ServiceModel.Syndication;
  22. using System.ServiceModel.Web;
  23. using System.Text;
  24. using System.Web;
  25. using System.Xml;
  26. using System.Xml.Linq;
  27. using System.Xml.Schema;
  28. using System.Xml.Serialization;
  29. using Microsoft.Server.Common;
  30. using Microsoft.ApplicationServer.Http.Description;
  31. using Microsoft.Runtime.Serialization;
  32. using Microsoft.Runtime.Serialization.Json;
  33. internal class HelpPage
  34. {
  35. public const string OperationListHelpPageUriTemplate = "help";
  36. public const string OperationHelpPageUriTemplateBase = "help/operations/";
  37. public const string OperationHelpPageUriTemplate = "help/operations/{operation}";
  38. public const string HelpMethodName = "GetHelpPage";
  39. public const string HelpOperationMethodName = "GetOperationHelpPage";
  40. private static readonly Dictionary<string, string> ConstantValues = new Dictionary<string, string>()
  41. {
  42. { "anyURI", "http://www.example.com/" },
  43. { "base64Binary", "QmFzZSA2NCBTdHJlYW0=" },
  44. { "boolean", "true" },
  45. { "byte", "127" },
  46. { "date", "1999-05-31" },
  47. { "decimal", "12678967.543233" },
  48. { "double", "1.26743233E+15" },
  49. { "duration", "P428DT10H30M12.3S" },
  50. { "ENTITY", "NCNameString" },
  51. { "float", "1.26743237E+15" },
  52. { "gDay", "---31" },
  53. { "gMonth", "--05" },
  54. { "gMonthDay", "--05-31" },
  55. { "guid", "1627aea5-8e0a-4371-9022-9b504344e724" },
  56. { "gYear", "1999" },
  57. { "gYearMonth", "1999-02" },
  58. { "hexBinary", "GpM7" },
  59. { "ID", "NCNameString" },
  60. { "IDREF", "NCNameString" },
  61. { "IDREFS", "NCNameString" },
  62. { "int", "2147483647" },
  63. { "integer", "2147483647" },
  64. { "language", "Http.SR.HelpExampleGeneratorLanguage" },
  65. { "long", "9223372036854775807" },
  66. { "Name", "Name" },
  67. { "NCName", "NCNameString" },
  68. { "negativeInteger", "-12678967543233" },
  69. { "NMTOKEN", Http.SR.HelpExampleGeneratorStringContent },
  70. { "NMTOKENS", Http.SR.HelpExampleGeneratorStringContent },
  71. { "nonNegativeInteger", "+2147483647" },
  72. { "nonPositiveInteger", "-12678967543233" },
  73. { "normalizedString", Http.SR.HelpExampleGeneratorStringContent },
  74. { "NOTATION", "namespace:Name" },
  75. { "positiveInteger", "+2147483647" },
  76. { "QName", "namespace:Name" },
  77. { "short", "32767" },
  78. { "string", Http.SR.HelpExampleGeneratorStringContent },
  79. { "time", "13:20:00.000, 13:20:00.000-05:00" },
  80. { "token", Http.SR.HelpExampleGeneratorStringContent },
  81. { "unsignedByte", "255" },
  82. { "unsignedInt", "4294967295" },
  83. { "unsignedLong", "18446744073709551615" },
  84. { "unsignedShort", "65535" },
  85. };
  86. private static readonly Encoding utf8Encoding = new UTF8Encoding(true);
  87. private DateTime startupTime = DateTime.UtcNow;
  88. private Uri baseAddress;
  89. private Dictionary<string, OperationHelpInformation> operationInfoDictionary;
  90. private NameValueCache<string> operationPageCache;
  91. private NameValueCache<string> helpPageCache;
  92. public HelpPage(Uri baseAddress, string javascriptCallbackParameterName, ContractDescription description)
  93. {
  94. Fx.Assert(baseAddress != null, "The 'baseAddress' parameter should not be null.");
  95. this.baseAddress = baseAddress;
  96. this.operationInfoDictionary = new Dictionary<string, OperationHelpInformation>();
  97. this.operationPageCache = new NameValueCache<string>();
  98. this.helpPageCache = new NameValueCache<string>();
  99. foreach (OperationDescription od in description.Operations)
  100. {
  101. HttpOperationDescription httpOperationDescription = od.ToHttpOperationDescription();
  102. operationInfoDictionary.Add(
  103. httpOperationDescription.Name,
  104. new OperationHelpInformation(javascriptCallbackParameterName, WebMessageBodyStyle.Bare, httpOperationDescription));
  105. }
  106. }
  107. public static OperationDescription[] AddHelpOperations(ContractDescription contractDescription, DispatchRuntime dispatchRuntime)
  108. {
  109. Fx.Assert(contractDescription != null, "The 'contractDescription' parameter should not be null.");
  110. Fx.Assert(dispatchRuntime != null, "The 'dispatchRuntime' parameter should not be null.");
  111. Uri baseAddress = dispatchRuntime.EndpointDispatcher.EndpointAddress.Uri;
  112. HelpPage helpPage = new HelpPage(baseAddress, null, contractDescription);
  113. HttpOperationDescription helpPageOperation = new HttpOperationDescription(HelpPage.HelpMethodName, contractDescription);
  114. helpPageOperation.Behaviors.Add(new WebGetAttribute() { UriTemplate = HelpPage.OperationListHelpPageUriTemplate });
  115. helpPageOperation.InputParameters.Add(HttpParameter.RequestMessage);
  116. helpPageOperation.ReturnValue = HttpParameter.ResponseMessage;
  117. HttpOperationDescription operationhelpPageOperation = new HttpOperationDescription(HelpPage.HelpOperationMethodName, contractDescription);
  118. operationhelpPageOperation.Behaviors.Add(new WebGetAttribute() { UriTemplate = HelpPage.OperationHelpPageUriTemplate });
  119. operationhelpPageOperation.InputParameters.Add(HttpParameter.RequestMessage);
  120. operationhelpPageOperation.InputParameters.Add(new HttpParameter("operation", TypeHelper.StringType));
  121. operationhelpPageOperation.ReturnValue = HttpParameter.ResponseMessage;
  122. dispatchRuntime.Operations.Add(
  123. new DispatchOperation(dispatchRuntime, HelpPage.HelpMethodName, null, null) { Invoker = helpPage.GetHelpPageInvoker(true) });
  124. dispatchRuntime.Operations.Add(
  125. new DispatchOperation(dispatchRuntime, HelpPage.HelpOperationMethodName, null, null) { Invoker = helpPage.GetHelpPageInvoker(false) });
  126. return new OperationDescription[] { helpPageOperation.ToOperationDescription(), operationhelpPageOperation.ToOperationDescription() };
  127. }
  128. public static IEnumerable<KeyValuePair<UriTemplate, object>> GetOperationTemplatePairs()
  129. {
  130. return new KeyValuePair<UriTemplate, object>[]
  131. {
  132. new KeyValuePair<UriTemplate, object>(new UriTemplate(OperationListHelpPageUriTemplate), HelpMethodName),
  133. new KeyValuePair<UriTemplate, object>(new UriTemplate(OperationHelpPageUriTemplate), HelpOperationMethodName)
  134. };
  135. }
  136. public IOperationInvoker GetHelpPageInvoker(bool invokerForOperationListHelpPage)
  137. {
  138. return new HttpHelpOperationInvoker(this, invokerForOperationListHelpPage);
  139. }
  140. public void InvokeHelpPage(HttpResponseMessage response)
  141. {
  142. ApplyCaching();
  143. Uri baseUri = this.baseAddress.GetHostNormalizedUri(response.RequestMessage);
  144. string helpPage = this.helpPageCache.Lookup(baseUri.Authority);
  145. if (string.IsNullOrEmpty(helpPage))
  146. {
  147. helpPage = HelpHtmlPageBuilder.CreateHelpPage(baseUri, operationInfoDictionary.Values).ToString();
  148. if (HttpContext.Current == null)
  149. {
  150. this.helpPageCache.AddOrUpdate(baseUri.Authority, helpPage);
  151. }
  152. }
  153. response.Content = new ActionOfStreamContent(
  154. stream =>
  155. {
  156. byte[] preamble = utf8Encoding.GetPreamble();
  157. stream.Write(preamble, 0, preamble.Length);
  158. byte[] bytes = utf8Encoding.GetBytes(helpPage);
  159. stream.Write(bytes, 0, bytes.Length);
  160. });
  161. response.Content.Headers.ContentType = MediaTypeConstants.HtmlMediaType;
  162. }
  163. public void InvokeOperationHelpPage(string operationName, HttpResponseMessage response)
  164. {
  165. ApplyCaching();
  166. Uri requestUri = response.RequestMessage.RequestUri.GetHostNormalizedUri(response.RequestMessage);
  167. string helpPage = this.operationPageCache.Lookup(requestUri.AbsoluteUri);
  168. if (string.IsNullOrEmpty(helpPage))
  169. {
  170. OperationHelpInformation operationInfo;
  171. if (this.operationInfoDictionary.TryGetValue(operationName, out operationInfo))
  172. {
  173. Uri baseUri = this.baseAddress.GetHostNormalizedUri(response.RequestMessage);
  174. helpPage = HelpHtmlPageBuilder.CreateOperationHelpPage(baseUri, operationInfo).ToString();
  175. if (HttpContext.Current == null)
  176. {
  177. this.operationPageCache.AddOrUpdate(requestUri.AbsoluteUri, helpPage);
  178. }
  179. }
  180. else
  181. {
  182. throw Fx.Exception.AsError(new WebFaultException(HttpStatusCode.NotFound));
  183. }
  184. }
  185. response.Content = new ActionOfStreamContent(
  186. stream =>
  187. {
  188. byte[] preamble = utf8Encoding.GetPreamble();
  189. stream.Write(preamble, 0, preamble.Length);
  190. byte[] bytes = utf8Encoding.GetBytes(helpPage);
  191. stream.Write(bytes, 0, bytes.Length);
  192. });
  193. response.Content.Headers.ContentType = MediaTypeConstants.HtmlMediaType;
  194. }
  195. private void ApplyCaching()
  196. {
  197. if (HttpContext.Current != null)
  198. {
  199. HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public);
  200. HttpContext.Current.Response.Cache.SetMaxAge(TimeSpan.MaxValue);
  201. HttpContext.Current.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(this.CacheValidationCallback), this.startupTime);
  202. HttpContext.Current.Response.Cache.SetValidUntilExpires(true);
  203. }
  204. }
  205. private void CacheValidationCallback(HttpContext context, object state, ref HttpValidationStatus result)
  206. {
  207. if (((DateTime)state) == this.startupTime)
  208. {
  209. result = HttpValidationStatus.Valid;
  210. }
  211. else
  212. {
  213. result = HttpValidationStatus.Invalid;
  214. }
  215. }
  216. private class NameValueCache<T>
  217. {
  218. // The NameValueCache implements a structure that uses a dictionary to map objects to
  219. // indices of an array of cache entries. This allows us to store the cache entries in
  220. // the order in which they were added to the cache, and yet still lookup any cache entry.
  221. // The eviction policy of the cache is to evict the least-recently-added cache entry.
  222. // Using a pointer to the next available cache entry in the array, we can always be sure
  223. // that the given entry is the oldest entry.
  224. private Hashtable cache;
  225. private string[] currentKeys;
  226. private int nextAvailableCacheIndex;
  227. private object cachelock;
  228. internal const int maxNumberofEntriesInCache = 16;
  229. public NameValueCache()
  230. : this(maxNumberofEntriesInCache)
  231. {
  232. }
  233. public NameValueCache(int maxCacheEntries)
  234. {
  235. cache = new Hashtable();
  236. currentKeys = new string[maxCacheEntries];
  237. cachelock = new object();
  238. }
  239. public T Lookup(string key)
  240. {
  241. return (T)cache[key];
  242. }
  243. public void AddOrUpdate(string key, T value)
  244. {
  245. lock (this.cachelock)
  246. {
  247. if (!cache.ContainsKey(key))
  248. {
  249. if (!String.IsNullOrEmpty(currentKeys[nextAvailableCacheIndex]))
  250. {
  251. cache.Remove(currentKeys[nextAvailableCacheIndex]);
  252. }
  253. currentKeys[nextAvailableCacheIndex] = key;
  254. nextAvailableCacheIndex = ++nextAvailableCacheIndex % currentKeys.Length;
  255. }
  256. cache[key] = value;
  257. }
  258. }
  259. }
  260. private class OperationHelpInformation
  261. {
  262. HttpOperationDescription httpOperationDescription;
  263. MessageHelpInformation request;
  264. MessageHelpInformation response;
  265. string javascriptCallbackParameterName;
  266. WebMessageBodyStyle bodyStyle;
  267. internal OperationHelpInformation(string javascriptCallbackParameterName, WebMessageBodyStyle bodyStyle, HttpOperationDescription httpOperationDescription)
  268. {
  269. this.httpOperationDescription = httpOperationDescription;
  270. this.javascriptCallbackParameterName = javascriptCallbackParameterName;
  271. this.bodyStyle = bodyStyle;
  272. }
  273. public string Name
  274. {
  275. get
  276. {
  277. return httpOperationDescription.Name;
  278. }
  279. }
  280. public string UriTemplate
  281. {
  282. get
  283. {
  284. return httpOperationDescription.GetUriTemplate().ToString();
  285. }
  286. }
  287. public string Method
  288. {
  289. get
  290. {
  291. return httpOperationDescription.GetHttpMethod().Method;
  292. }
  293. }
  294. public string Description
  295. {
  296. get
  297. {
  298. return httpOperationDescription.GetDescription();
  299. }
  300. }
  301. public string JavascriptCallbackParameterName
  302. {
  303. get
  304. {
  305. if (this.Response.SupportsJson && string.Equals(this.Method, HttpMethod.Get.ToString()))
  306. {
  307. return this.javascriptCallbackParameterName;
  308. }
  309. return null;
  310. }
  311. }
  312. public WebMessageBodyStyle BodyStyle
  313. {
  314. get
  315. {
  316. return this.bodyStyle;
  317. }
  318. }
  319. public MessageHelpInformation Request
  320. {
  321. get
  322. {
  323. if (this.request == null)
  324. {
  325. this.request = new MessageHelpInformation(httpOperationDescription, true, GetRequestBodyType(httpOperationDescription, this.UriTemplate),
  326. this.BodyStyle == WebMessageBodyStyle.WrappedRequest || this.BodyStyle == WebMessageBodyStyle.Wrapped);
  327. }
  328. return this.request;
  329. }
  330. }
  331. public MessageHelpInformation Response
  332. {
  333. get
  334. {
  335. if (this.response == null)
  336. {
  337. this.response = new MessageHelpInformation(httpOperationDescription, false, GetResponseBodyType(httpOperationDescription),
  338. this.BodyStyle == WebMessageBodyStyle.WrappedResponse || this.BodyStyle == WebMessageBodyStyle.Wrapped);
  339. }
  340. return this.response;
  341. }
  342. }
  343. public static Type GetResponseBodyType(HttpOperationDescription description)
  344. {
  345. if (description.OutputParameters.Count > 0)
  346. {
  347. // If it is more than 0 the response is wrapped and not supported
  348. return null;
  349. }
  350. HttpParameter returnDescription = description.ReturnValue;
  351. return returnDescription == null ? null : returnDescription.ParameterType;
  352. }
  353. public static Type GetRequestBodyType(HttpOperationDescription description, string uriTemplate)
  354. {
  355. if (description.Behaviors.Contains(typeof(WebGetAttribute)))
  356. {
  357. return typeof(void);
  358. }
  359. UriTemplate template = new UriTemplate(uriTemplate);
  360. IEnumerable<HttpParameter> parameterDescriptions =
  361. from parameterDescription in description.InputParameters
  362. where !template.PathSegmentVariableNames.Contains(parameterDescription.Name.ToUpperInvariant()) && !template.QueryValueVariableNames.Contains(parameterDescription.Name.ToUpperInvariant())
  363. select parameterDescription;
  364. HttpParameter[] matchingParameters = parameterDescriptions.ToArray();
  365. // No match == void
  366. // One match == the body type
  367. // Multiple matches == the request is wrapped and not supported
  368. return matchingParameters.Length == 0
  369. ? typeof(void)
  370. : (matchingParameters.Length == 1)
  371. ? matchingParameters[0].ParameterType
  372. : null;
  373. }
  374. }
  375. private class MessageHelpInformation
  376. {
  377. private static readonly Type typeOfIQueryable = typeof(IQueryable);
  378. private static readonly Type typeOfIQueryableGeneric = typeof(IQueryable<>);
  379. private static readonly Type typeOfIEnumerable = typeof(IEnumerable);
  380. private static readonly Type typeOfIEnumerableGeneric = typeof(IEnumerable<>);
  381. public string BodyDescription { get; private set; }
  382. public string FormatString { get; private set; }
  383. public Type Type { get; private set; }
  384. public bool SupportsJson { get; private set; }
  385. public XmlSchemaSet SchemaSet { get; private set; }
  386. public XmlSchema Schema { get; private set; }
  387. public XElement XmlExample { get; private set; }
  388. public XElement JsonExample { get; private set; }
  389. [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This class requires the evaluation of many types.")]
  390. [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This class requires the evaluation of many types.")]
  391. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is handled.")]
  392. internal MessageHelpInformation(HttpOperationDescription description, bool isRequest, Type type, bool wrapped)
  393. {
  394. this.Type = type;
  395. this.SupportsJson = true;
  396. string direction = isRequest ? Http.SR.HelpPageRequest : Http.SR.HelpPageResponse;
  397. if (wrapped && !typeof(void).Equals(type))
  398. {
  399. this.BodyDescription = Http.SR.HelpPageBodyIsWrapped(direction);
  400. this.FormatString = Http.SR.HelpPageUnknown;
  401. }
  402. else if (typeof(void).Equals(type))
  403. {
  404. this.BodyDescription = Http.SR.HelpPageBodyIsEmpty(direction);
  405. this.FormatString = Http.SR.HelpPageNA;
  406. }
  407. else if (typeof(Message).IsAssignableFrom(type))
  408. {
  409. this.BodyDescription = Http.SR.HelpPageIsMessage(direction);
  410. this.FormatString = Http.SR.HelpPageUnknown;
  411. }
  412. else if (typeof(Stream).IsAssignableFrom(type))
  413. {
  414. this.BodyDescription = Http.SR.HelpPageIsStream(direction);
  415. this.FormatString = Http.SR.HelpPageUnknown;
  416. }
  417. else if (typeof(Atom10FeedFormatter).IsAssignableFrom(type))
  418. {
  419. this.BodyDescription = Http.SR.HelpPageIsAtom10Feed(direction);
  420. this.FormatString = WebMessageFormat.Xml.ToString();
  421. }
  422. else if (typeof(Atom10ItemFormatter).IsAssignableFrom(type))
  423. {
  424. this.BodyDescription = Http.SR.HelpPageIsAtom10Entry(direction);
  425. this.FormatString = WebMessageFormat.Xml.ToString();
  426. }
  427. else if (typeof(AtomPub10ServiceDocumentFormatter).IsAssignableFrom(type))
  428. {
  429. this.BodyDescription = Http.SR.HelpPageIsAtomPubServiceDocument(direction);
  430. this.FormatString = WebMessageFormat.Xml.ToString();
  431. }
  432. else if (typeof(AtomPub10CategoriesDocumentFormatter).IsAssignableFrom(type))
  433. {
  434. this.BodyDescription = Http.SR.HelpPageIsAtomPubCategoriesDocument(direction);
  435. this.FormatString = WebMessageFormat.Xml.ToString();
  436. }
  437. else if (typeof(Rss20FeedFormatter).IsAssignableFrom(type))
  438. {
  439. this.BodyDescription = Http.SR.HelpPageIsRSS20Feed(direction);
  440. this.FormatString = WebMessageFormat.Xml.ToString();
  441. }
  442. else if (typeof(SyndicationFeedFormatter).IsAssignableFrom(type))
  443. {
  444. this.BodyDescription = Http.SR.HelpPageIsSyndication(direction);
  445. this.FormatString = WebMessageFormat.Xml.ToString();
  446. }
  447. else if (typeof(XElement).IsAssignableFrom(type) || typeof(XmlElement).IsAssignableFrom(type))
  448. {
  449. this.BodyDescription = Http.SR.HelpPageIsXML(direction);
  450. this.FormatString = WebMessageFormat.Xml.ToString();
  451. }
  452. else
  453. {
  454. try
  455. {
  456. bool usesXmlSerializer = HttpOperationHandlerFactory.DetermineSerializerFormat(description) != XmlMediaTypeSerializerFormat.DataContractSerializer;
  457. XmlQualifiedName name;
  458. this.SchemaSet = new XmlSchemaSet();
  459. IDictionary<XmlQualifiedName, Type> knownTypes = new Dictionary<XmlQualifiedName, Type>();
  460. if (usesXmlSerializer)
  461. {
  462. XmlReflectionImporter importer = new XmlReflectionImporter();
  463. XmlTypeMapping typeMapping = importer.ImportTypeMapping(this.Type);
  464. name = new XmlQualifiedName(typeMapping.ElementName, typeMapping.Namespace);
  465. XmlSchemas schemas = new XmlSchemas();
  466. XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);
  467. exporter.ExportTypeMapping(typeMapping);
  468. foreach (XmlSchema schema in schemas)
  469. {
  470. this.SchemaSet.Add(schema);
  471. }
  472. }
  473. else
  474. {
  475. XsdDataContractExporter exporter = new XsdDataContractExporter();
  476. List<Type> listTypes = new List<Type>(description.KnownTypes);
  477. bool isQueryable;
  478. Type dataContractType = GetSubstituteDataContractType(this.Type, out isQueryable);
  479. listTypes.Add(dataContractType);
  480. exporter.Export(listTypes);
  481. if (!exporter.CanExport(dataContractType))
  482. {
  483. this.BodyDescription = Http.SR.HelpPageCouldNotGenerateSchema;
  484. this.FormatString = Http.SR.HelpPageUnknown;
  485. return;
  486. }
  487. name = exporter.GetRootElementName(dataContractType);
  488. DataContract typeDataContract = DataContract.GetDataContract(dataContractType);
  489. if (typeDataContract.KnownDataContracts != null)
  490. {
  491. foreach (XmlQualifiedName dataContractName in typeDataContract.KnownDataContracts.Keys)
  492. {
  493. knownTypes.Add(dataContractName, typeDataContract.KnownDataContracts[dataContractName].UnderlyingType);
  494. }
  495. }
  496. foreach (Type knownType in description.KnownTypes)
  497. {
  498. XmlQualifiedName knownTypeName = exporter.GetSchemaTypeName(knownType);
  499. if (!knownTypes.ContainsKey(knownTypeName))
  500. {
  501. knownTypes.Add(knownTypeName, knownType);
  502. }
  503. }
  504. foreach (XmlSchema schema in exporter.Schemas.Schemas())
  505. {
  506. this.SchemaSet.Add(schema);
  507. }
  508. }
  509. this.SchemaSet.Compile();
  510. XmlWriterSettings settings = new XmlWriterSettings
  511. {
  512. CloseOutput = false,
  513. Indent = true,
  514. };
  515. if (this.SupportsJson)
  516. {
  517. XDocument exampleDocument = new XDocument();
  518. using (XmlWriter writer = XmlWriter.Create(exampleDocument.CreateWriter(), settings))
  519. {
  520. HelpExampleGenerator.GenerateJsonSample(this.SchemaSet, name, writer, knownTypes);
  521. }
  522. this.JsonExample = exampleDocument.Root;
  523. }
  524. if (name.Namespace != "http://schemas.microsoft.com/2003/10/Serialization/")
  525. {
  526. foreach (XmlSchema schema in this.SchemaSet.Schemas(name.Namespace))
  527. {
  528. this.Schema = schema;
  529. }
  530. }
  531. XDocument XmlExampleDocument = new XDocument();
  532. using (XmlWriter writer = XmlWriter.Create(XmlExampleDocument.CreateWriter(), settings))
  533. {
  534. HelpExampleGenerator.GenerateXmlSample(this.SchemaSet, name, writer);
  535. }
  536. this.XmlExample = XmlExampleDocument.Root;
  537. }
  538. catch (Exception)
  539. {
  540. this.BodyDescription = Http.SR.HelpPageCouldNotGenerateSchema;
  541. this.FormatString = Http.SR.HelpPageUnknown;
  542. this.Schema = null;
  543. this.JsonExample = null;
  544. this.XmlExample = null;
  545. }
  546. }
  547. }
  548. private static Type GetSubstituteDataContractType(Type type, out bool isQueryable)
  549. {
  550. if (type == typeOfIQueryable)
  551. {
  552. isQueryable = true;
  553. return typeOfIEnumerable;
  554. }
  555. if (type.IsGenericType &&
  556. type.GetGenericTypeDefinition() == typeOfIQueryableGeneric)
  557. {
  558. isQueryable = true;
  559. return typeOfIEnumerableGeneric.MakeGenericType(type.GetGenericArguments());
  560. }
  561. isQueryable = false;
  562. return type;
  563. }
  564. }
  565. private class HelpHtmlPageBuilder : HtmlPageBuilder
  566. {
  567. internal const string HelpOperationPageUrl = "help/operations/{0}";
  568. private HelpHtmlPageBuilder()
  569. {
  570. }
  571. public static XDocument CreateHelpPage(Uri baseUri, IEnumerable<OperationHelpInformation> operations)
  572. {
  573. XDocument document = CreateBaseDocument(Http.SR.HelpPageOperationsAt(baseUri));
  574. XElement table = new XElement(HtmlTableElementName,
  575. new XElement(HtmlTrElementName,
  576. new XElement(HtmlThElementName, Http.SR.HelpPageUri),
  577. new XElement(HtmlThElementName, Http.SR.HelpPageMethod),
  578. new XElement(HtmlThElementName, Http.SR.HelpPageDescription)));
  579. string lastOperation = null;
  580. XElement firstTr = null;
  581. int rowspan = 0;
  582. foreach (OperationHelpInformation operationHelpInfo in operations.OrderBy(o => FilterQueryVariables(o.UriTemplate)))
  583. {
  584. string operationUri = FilterQueryVariables(operationHelpInfo.UriTemplate);
  585. string description = operationHelpInfo.Description;
  586. if (String.IsNullOrEmpty(description))
  587. {
  588. description = Http.SR.HelpPageDefaultDescription(BuildFullUriTemplate(baseUri, operationHelpInfo.UriTemplate));
  589. }
  590. XElement tr = new XElement(HtmlTrElementName,
  591. new XElement(HtmlTdElementName, new XAttribute(HtmlTitleAttributeName, BuildFullUriTemplate(baseUri, operationHelpInfo.UriTemplate)),
  592. new XElement(HtmlAElementName,
  593. new XAttribute(HtmlRelAttributeName, HtmlOperationClass),
  594. new XAttribute(HtmlHrefAttributeName, String.Format(CultureInfo.InvariantCulture, HelpOperationPageUrl, operationHelpInfo.Name)), operationHelpInfo.Method)),
  595. new XElement(HtmlTdElementName, description));
  596. table.Add(tr);
  597. if (operationUri != lastOperation)
  598. {
  599. XElement td = new XElement(HtmlTdElementName, operationUri == lastOperation ? String.Empty : operationUri);
  600. tr.AddFirst(td);
  601. if (firstTr != null && rowspan > 1)
  602. {
  603. firstTr.Descendants(HtmlTdElementName).First().Add(new XAttribute(HtmlRowspanAttributeName, rowspan));
  604. }
  605. firstTr = tr;
  606. rowspan = 0;
  607. lastOperation = operationUri;
  608. }
  609. ++rowspan;
  610. }
  611. if (firstTr != null && rowspan > 1)
  612. {
  613. firstTr.Descendants(HtmlTdElementName).First().Add(new XAttribute(HtmlRowspanAttributeName, rowspan));
  614. }
  615. document.Descendants(HtmlBodyElementName).First().Add(new XElement(HtmlDivElementName, new XAttribute(HtmlIdAttributeName, HtmlContentClass),
  616. new XElement(HtmlPElementName, new XAttribute(HtmlClassAttributeName, HtmlHeading1Class), Http.SR.HelpPageOperationsAt(baseUri)),
  617. new XElement(HtmlPElementName, Http.SR.HelpPageStaticText),
  618. table));
  619. return document;
  620. }
  621. public static XDocument CreateOperationHelpPage(Uri baseUri, OperationHelpInformation operationInfo)
  622. {
  623. XDocument document = CreateBaseDocument(Http.SR.HelpPageReferenceFor(BuildFullUriTemplate(baseUri, operationInfo.UriTemplate)));
  624. XElement table = new XElement(HtmlTableElementName,
  625. new XElement(HtmlTrElementName,
  626. new XElement(HtmlThElementName, Http.SR.HelpPageMessageDirection),
  627. new XElement(HtmlThElementName, Http.SR.HelpPageFormat),
  628. new XElement(HtmlThElementName, Http.SR.HelpPageBody)));
  629. RenderMessageInformation(table, operationInfo, true);
  630. RenderMessageInformation(table, operationInfo, false);
  631. XElement div = new XElement(HtmlDivElementName, new XAttribute(HtmlIdAttributeName, HtmlContentClass),
  632. new XElement(HtmlPElementName, new XAttribute(HtmlClassAttributeName, HtmlHeading1Class), Http.SR.HelpPageReferenceFor(BuildFullUriTemplate(baseUri, operationInfo.UriTemplate))),
  633. new XElement(HtmlPElementName, operationInfo.Description),
  634. XElement.Parse(Http.SR.HelpPageOperationUri(HttpUtility.HtmlEncode(BuildFullUriTemplate(baseUri, operationInfo.UriTemplate)))),
  635. XElement.Parse(Http.SR.HelpPageOperationMethod(HttpUtility.HtmlEncode(operationInfo.Method))));
  636. if (!String.IsNullOrEmpty(operationInfo.JavascriptCallbackParameterName))
  637. {
  638. div.Add(XElement.Parse(Http.SR.HelpPageCallbackText(HttpUtility.HtmlEncode(operationInfo.JavascriptCallbackParameterName))), table);
  639. }
  640. else
  641. {
  642. div.Add(table);
  643. }
  644. document.Descendants(HtmlBodyElementName).First().Add(div);
  645. CreateOperationSamples(document.Descendants(HtmlDivElementName).First(), operationInfo);
  646. return document;
  647. }
  648. private static string FilterQueryVariables(string uriTemplate)
  649. {
  650. int variablesIndex = uriTemplate.IndexOf('?');
  651. if (variablesIndex > 0)
  652. {
  653. return uriTemplate.Substring(0, variablesIndex);
  654. }
  655. return uriTemplate;
  656. }
  657. private static void RenderMessageInformation(XElement table, OperationHelpInformation operationInfo, bool isRequest)
  658. {
  659. MessageHelpInformation info = isRequest ? operationInfo.Request : operationInfo.Response;
  660. string direction = isRequest ? Http.SR.HelpPageRequest : Http.SR.HelpPageResponse;
  661. if (info.BodyDescription != null)
  662. {
  663. table.Add(new XElement(HtmlTrElementName,
  664. new XElement(HtmlTdElementName, direction),
  665. new XElement(HtmlTdElementName, info.FormatString),
  666. new XElement(HtmlTdElementName, info.BodyDescription)));
  667. }
  668. else
  669. {
  670. if (info.XmlExample != null || info.Schema != null)
  671. {
  672. XElement contentTd;
  673. table.Add(new XElement(HtmlTrElementName,
  674. new XElement(HtmlTdElementName, direction),
  675. new XElement(HtmlTdElementName, "Xml"),
  676. contentTd = new XElement(HtmlTdElementName)));
  677. if (info.XmlExample != null)
  678. {
  679. contentTd.Add(new XElement(HtmlAElementName, new XAttribute(HtmlHrefAttributeName, "#" + (isRequest ? HtmlRequestXmlId : HtmlResponseXmlId)), Http.SR.HelpPageExample));
  680. if (info.Schema != null)
  681. {
  682. contentTd.Add(",");
  683. }
  684. }
  685. if (info.Schema != null)
  686. {
  687. contentTd.Add(new XElement(HtmlAElementName, new XAttribute(HtmlHrefAttributeName, "#" + (isRequest ? HtmlRequestSchemaId : HtmlResponseSchemaId)), Http.SR.HelpPageSchema));
  688. }
  689. }
  690. if (info.JsonExample != null)
  691. {
  692. table.Add(new XElement(HtmlTrElementName,
  693. new XElement(HtmlTdElementName, direction),
  694. new XElement(HtmlTdElementName, "Json"),
  695. new XElement(HtmlTdElementName,
  696. new XElement(HtmlAElementName, new XAttribute(HtmlHrefAttributeName, "#" + (isRequest ? HtmlRequestJsonId : HtmlResponseJsonId)), Http.SR.HelpPageExample))));
  697. }
  698. }
  699. }
  700. private static void CreateOperationSamples(XElement element, OperationHelpInformation operationInfo)
  701. {
  702. if (operationInfo.Request.XmlExample != null)
  703. {
  704. element.Add(GenerateSampleXml(operationInfo.Request.XmlExample, Http.SR.HelpPageXmlRequest, HtmlRequestXmlId));
  705. }
  706. if (operationInfo.Request.JsonExample != null)
  707. {
  708. element.Add(AddSampleJson(operationInfo.Request.JsonExample, Http.SR.HelpPageJsonRequest, HtmlRequestJsonId));
  709. }
  710. if (operationInfo.Response.XmlExample != null)
  711. {
  712. element.Add(GenerateSampleXml(operationInfo.Response.XmlExample, Http.SR.HelpPageXmlResponse, HtmlResponseXmlId));
  713. }
  714. if (operationInfo.Response.JsonExample != null)
  715. {
  716. element.Add(AddSampleJson(operationInfo.Response.JsonExample, Http.SR.HelpPageJsonResponse, HtmlResponseJsonId));
  717. }
  718. if (operationInfo.Request.Schema != null)
  719. {
  720. element.Add(GenerateSampleXml(XmlSchemaToXElement(operationInfo.Request.Schema), Http.SR.HelpPageRequestSchema, HtmlRequestSchemaId));
  721. int count = 0;
  722. foreach (XmlSchema schema in operationInfo.Request.SchemaSet.Schemas())
  723. {
  724. if (schema.TargetNamespace != operationInfo.Request.Schema.TargetNamespace)
  725. {
  726. element.Add(GenerateSampleXml(XmlSchemaToXElement(schema), ++count == 1 ? Http.SR.HelpPageAdditionalRequestSchema : null, HtmlRequestSchemaId));
  727. }
  728. }
  729. }
  730. if (operationInfo.Response.Schema != null)
  731. {
  732. element.Add(GenerateSampleXml(XmlSchemaToXElement(operationInfo.Response.Schema), Http.SR.HelpPageResponseSchema, HtmlResponseSchemaId));
  733. int count = 0;
  734. foreach (XmlSchema schema in operationInfo.Response.SchemaSet.Schemas())
  735. {
  736. if (schema.TargetNamespace != operationInfo.Response.Schema.TargetNamespace)
  737. {
  738. element.Add(GenerateSampleXml(XmlSchemaToXElement(schema), ++count == 1 ? Http.SR.HelpPageAdditionalResponseSchema : null, HtmlResponseSchemaId));
  739. }
  740. }
  741. }
  742. }
  743. private static XElement XmlSchemaToXElement(XmlSchema schema)
  744. {
  745. XmlWriterSettings settings = new XmlWriterSettings
  746. {
  747. CloseOutput = false,
  748. Indent = true,
  749. };
  750. XDocument schemaDocument = new XDocument();
  751. using (XmlWriter writer = XmlWriter.Create(schemaDocument.CreateWriter(), settings))
  752. {
  753. schema.Write(writer);
  754. }
  755. return schemaDocument.Root;
  756. }
  757. private static XElement AddSample(object content, string title, string label)
  758. {
  759. if (String.IsNullOrEmpty(title))
  760. {
  761. return new XElement(HtmlPElementName,
  762. new XElement(HtmlPreElementName, new XAttribute(HtmlClassAttributeName, label), content));
  763. }
  764. else
  765. {
  766. return new XElement(HtmlPElementName,
  767. new XElement(HtmlAElementName, new XAttribute(HtmlNameAttributeName, "#" + label), title),
  768. new XElement(HtmlPreElementName, new XAttribute(HtmlClassAttributeName, label), content));
  769. }
  770. }
  771. private static XElement GenerateSampleXml(XElement content, string title, string label)
  772. {
  773. StringBuilder sample = new StringBuilder();
  774. using (XmlWriter writer = XmlTextWriter.Create(sample, new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
  775. {
  776. content.WriteTo(writer);
  777. }
  778. return AddSample(sample.ToString(), title, label);
  779. }
  780. [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "dispose is idempotent.")]
  781. private static XElement AddSampleJson(XElement content, string title, string label)
  782. {
  783. StringBuilder sample = new StringBuilder();
  784. using (MemoryStream stream = new MemoryStream())
  785. {
  786. using (XmlDictionaryWriter writer =
  787. JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.Unicode, false))
  788. {
  789. content.WriteTo(writer);
  790. }
  791. stream.Position = 0;
  792. sample.Append(new StreamReader(stream, Encoding.Unicode).ReadToEnd());
  793. }
  794. int depth = 0;
  795. bool inString = false;
  796. for (int i = 0; i < sample.Length; ++i)
  797. {
  798. if (sample[i] == '"')
  799. {
  800. inString = !inString;
  801. }
  802. else if (sample[i] == '{')
  803. {
  804. sample.Insert(i + 1, "\n" + new String('\t', ++depth));
  805. i += depth + 1;
  806. }
  807. else if (sample[i] == ',' && !inString)
  808. {
  809. sample.Insert(i + 1, "\n" + new String('\t', depth));
  810. }
  811. else if (sample[i] == '}' && depth > 0)
  812. {
  813. sample.Insert(i, "\n" + new String('\t', --depth));
  814. i += depth + 1;
  815. }
  816. }
  817. return AddSample(sample.ToString(), title, label);
  818. }
  819. private static string BuildFullUriTemplate(Uri baseUri, string uriTemplate)
  820. {
  821. UriTemplate template = new UriTemplate(uriTemplate);
  822. Uri result = template.BindByPosition(baseUri, template.PathSegmentVariableNames.Concat(template.QueryValueVariableNames).Select(name => "{" + name + "}").ToArray());
  823. return result.ToString();
  824. }
  825. }
  826. [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This type requires the evaluation of many types.")]
  827. private static class HelpExampleGenerator
  828. {
  829. private const int MaxDepthLevel = 256;
  830. private const string XmlSchemaNamespace = "http://www.w3.org/2001/XMLSchema";
  831. private const string XmlNamespacePrefix = "xmlns";
  832. private const string XmlSchemaInstanceNamespace = "http://www.w3.org/2001/XMLSchema-instance";
  833. private const string XmlSchemaInstanceNil = "nil";
  834. private const string XmlSchemaInstanceType = "type";
  835. private static Dictionary<Type, Action<XmlSchemaObject, HelpExampleGeneratorContext>> XmlObjectHandler = new Dictionary<Type, Action<XmlSchemaObject, HelpExampleGeneratorContext>>
  836. {
  837. { typeof(XmlSchemaComplexContent), ContentHandler },
  838. { typeof(XmlSchemaSimpleContent), ContentHandler },
  839. { typeof(XmlSchemaSimpleTypeRestriction), SimpleTypeRestrictionHandler },
  840. { typeof(XmlSchemaChoice), ChoiceHandler },
  841. // Nothing to do, inheritance is resolved by Schema compilation process
  842. { typeof(XmlSchemaComplexContentExtension), EmptyHandler },
  843. { typeof(XmlSchemaSimpleContentExtension), EmptyHandler },
  844. // No need to generate XML for these objects
  845. { typeof(XmlSchemaAny), EmptyHandler },
  846. { typeof(XmlSchemaAnyAttribute), EmptyHandler },
  847. { typeof(XmlSchemaAnnotated), EmptyHandler },
  848. { typeof(XmlSchema), EmptyHandler },
  849. // The following schema objects are not handled
  850. { typeof(XmlSchemaAttributeGroup), ErrorHandler },
  851. { typeof(XmlSchemaAttributeGroupRef), ErrorHandler },
  852. { typeof(XmlSchemaComplexContentRestriction), ErrorHandler },
  853. { typeof(XmlSchemaSimpleContentRestriction), ErrorHandler },
  854. // Enumerations are supported by the GenerateContentForSimpleType
  855. { typeof(XmlSchemaEnumerationFacet), EmptyHandler },
  856. { typeof(XmlSchemaMaxExclusiveFacet), ErrorHandler },
  857. { typeof(XmlSchemaMaxInclusiveFacet), ErrorHandler },
  858. { typeof(XmlSchemaMinExclusiveFacet), ErrorHandler },
  859. { typeof(XmlSchemaMinInclusiveFacet), ErrorHandler },
  860. { typeof(XmlSchemaNumericFacet), ErrorHandler },
  861. { typeof(XmlSchemaFractionDigitsFacet), ErrorHandler },
  862. { typeof(XmlSchemaLengthFacet), ErrorHandler },
  863. { typeof(XmlSchemaMaxLengthFacet), ErrorHandler },
  864. { typeof(XmlSchemaMinLengthFacet), ErrorHandler },
  865. { typeof(XmlSchemaTotalDigitsFacet), ErrorHandler },
  866. { typeof(XmlSchemaPatternFacet), ErrorHandler },
  867. { typeof(XmlSchemaWhiteSpaceFacet), ErrorHandler },
  868. { typeof(XmlSchemaGroup), ErrorHandler },
  869. { typeof(XmlSchemaIdentityConstraint), ErrorHandler },
  870. { typeof(XmlSchemaKey), ErrorHandler },
  871. { typeof(XmlSchemaKeyref), ErrorHandler },
  872. { typeof(XmlSchemaUnique), ErrorHandler },
  873. { typeof(XmlSchemaNotation), ErrorHandler },
  874. { typeof(XmlSchemaAll), ErrorHandler },
  875. { typeof(XmlSchemaGroupRef), ErrorHandler },
  876. { typeof(XmlSchemaSimpleTypeUnion), ErrorHandler },
  877. { typeof(XmlSchemaSimpleTypeList), XmlSimpleTypeListHandler },
  878. { typeof(XmlSchemaXPath), ErrorHandler },
  879. { typeof(XmlSchemaAttribute), XmlAttributeHandler },
  880. { typeof(XmlSchemaElement), XmlElementHandler },
  881. { typeof(XmlSchemaComplexType), XmlComplexTypeHandler },
  882. { typeof(XmlSchemaSequence), XmlSequenceHandler },
  883. { typeof(XmlSchemaSimpleType), XmlSimpleTypeHandler },
  884. };
  885. private static Dictionary<Type, Action<XmlSchemaObject, HelpExampleGeneratorContext>> JsonObjectHandler = new Dictionary<Type, Action<XmlSchemaObject, HelpExampleGeneratorContext>>
  886. {
  887. { typeof(XmlSchemaComplexContent), ContentHandler },
  888. { typeof(XmlSchemaSimpleContent), ContentHandler },
  889. { typeof(XmlSchemaSimpleTypeRestriction), SimpleTypeRestrictionHandler },
  890. { typeof(XmlSchemaChoice), ChoiceHandler },
  891. // Nothing to do, inheritance is resolved by Schema compilation process
  892. { typeof(XmlSchemaComplexContentExtension), EmptyHandler },
  893. { typeof(XmlSchemaSimpleContentExtension), EmptyHandler },
  894. // No need to generate XML for these objects

Large files files are truncated, but you can click here to view the full file