PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/WCFWebApi/src/Microsoft.Net.Http.Formatting/System/Net/Http/ObjectContent.cs

#
C# | 1145 lines | 795 code | 141 blank | 209 comment | 149 complexity | e01b37cd451ad99a52281139de6fb8d4 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
  1. // <copyright>
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. // </copyright>
  4. namespace System.Net.Http
  5. {
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Diagnostics.CodeAnalysis;
  9. using System.Diagnostics.Contracts;
  10. using System.IO;
  11. using System.Net;
  12. using System.Net.Http.Formatting;
  13. using System.Net.Http.Headers;
  14. using System.Threading.Tasks;
  15. /// <summary>
  16. /// Derived <see cref="HttpContent"/> class that contains a strongly typed object.
  17. /// </summary>
  18. public class ObjectContent : HttpContent
  19. {
  20. private const string HeadersContentTypeName = "Headers.ContentType";
  21. private static readonly Type ObjectContentType = typeof(ObjectContent);
  22. private static readonly Type HttpContentType = typeof(HttpContent);
  23. private static readonly Type MediaTypeHeaderValueType = typeof(MediaTypeHeaderValue);
  24. private static readonly Type MediaTypeFormatterType = typeof(MediaTypeFormatter);
  25. private static readonly Type NullableType = typeof(Nullable<>);
  26. private MediaTypeFormatterCollection formatters;
  27. private HttpRequestMessage requestMessage;
  28. private HttpResponseMessage responseMessage;
  29. private object defaultValue;
  30. private MediaTypeFormatter defaultFormatter;
  31. private MediaTypeFormatter selectedWriteFormatter;
  32. /// <summary>
  33. /// Initializes a new instance of the <see cref="ObjectContent"/> class.
  34. /// </summary>
  35. /// <param name="type">The type of object this instance will contain.</param>
  36. /// <param name="value">The value of the object this instance will contain.</param>
  37. public ObjectContent(Type type, object value)
  38. : this(type)
  39. {
  40. this.VerifyAndSetObject(value);
  41. }
  42. /// <summary>
  43. /// Initializes a new instance of the <see cref="ObjectContent"/> class.
  44. /// </summary>
  45. /// <param name="type">The type of object this instance will contain.</param>
  46. /// <param name="value">The value of the object this instance will contain.</param>
  47. /// <param name="mediaType">The media type to associate with this object.</param>
  48. public ObjectContent(Type type, object value, string mediaType)
  49. : this(type)
  50. {
  51. this.VerifyAndSetObjectAndMediaType(value, mediaType);
  52. }
  53. /// <summary>
  54. /// Initializes a new instance of the <see cref="ObjectContent"/> class.
  55. /// </summary>
  56. /// <param name="type">The type of object this instance will contain.</param>
  57. /// <param name="value">The value of the object this instance will contain.</param>
  58. /// <param name="mediaType">The media type to associate with this object.</param>
  59. public ObjectContent(Type type, object value, MediaTypeHeaderValue mediaType)
  60. : this(type)
  61. {
  62. this.VerifyAndSetObjectAndMediaType(value, mediaType);
  63. }
  64. /// <summary>
  65. /// Initializes a new instance of the <see cref="ObjectContent"/> class.
  66. /// </summary>
  67. /// <param name="type">The type of object this instance will contain.</param>
  68. /// <param name="content">An existing <see cref="HttpContent"/> instance to use for the object's content.</param>
  69. public ObjectContent(Type type, HttpContent content)
  70. : this(type)
  71. {
  72. this.VerifyAndSetHttpContent(content);
  73. }
  74. /// <summary>
  75. /// Initializes a new instance of the <see cref="ObjectContent"/> class.
  76. /// </summary>
  77. /// <param name="type">The type of object this instance will contain.</param>
  78. /// <param name="value">The value of the object this instance will contain.</param>
  79. /// <param name="formatters">The collection of <see cref="MediaTypeFormatter"/> instances
  80. /// to use for serialization or deserialization.</param>
  81. public ObjectContent(Type type, object value, IEnumerable<MediaTypeFormatter> formatters)
  82. : this(type, value)
  83. {
  84. if (formatters == null)
  85. {
  86. throw new ArgumentNullException("formatters");
  87. }
  88. this.formatters = new MediaTypeFormatterCollection(formatters);
  89. }
  90. /// <summary>
  91. /// Initializes a new instance of the <see cref="ObjectContent"/> class.
  92. /// </summary>
  93. /// <param name="type">The type of object this instance will contain.</param>
  94. /// <param name="value">The value of the object this instance will contain.</param>
  95. /// <param name="mediaType">The media type to associate with this object.</param>
  96. /// <param name="formatters">The collection of <see cref="MediaTypeFormatter"/> instances
  97. /// to use for serialization or deserialization.</param>
  98. public ObjectContent(Type type, object value, string mediaType, IEnumerable<MediaTypeFormatter> formatters)
  99. : this(type, value, mediaType)
  100. {
  101. if (formatters == null)
  102. {
  103. throw new ArgumentNullException("formatters");
  104. }
  105. this.formatters = new MediaTypeFormatterCollection(formatters);
  106. }
  107. /// <summary>
  108. /// Initializes a new instance of the <see cref="ObjectContent"/> class.
  109. /// </summary>
  110. /// <param name="type">The type of object this instance will contain.</param>
  111. /// <param name="value">The value of the object this instance will contain.</param>
  112. /// <param name="mediaType">The media type to associate with this object.</param>
  113. /// <param name="formatters">The collection of <see cref="MediaTypeFormatter"/> instances
  114. /// to use for serialization or deserialization.</param>
  115. public ObjectContent(Type type, object value, MediaTypeHeaderValue mediaType, IEnumerable<MediaTypeFormatter> formatters)
  116. : this(type, value, mediaType)
  117. {
  118. if (formatters == null)
  119. {
  120. throw new ArgumentNullException("formatters");
  121. }
  122. this.formatters = new MediaTypeFormatterCollection(formatters);
  123. }
  124. /// <summary>
  125. /// Initializes a new instance of the <see cref="ObjectContent"/> class.
  126. /// </summary>
  127. /// <param name="type">The type of object this instance will contain.</param>
  128. /// <param name="content">An existing <see cref="HttpContent"/> instance to use for the object's content.</param>
  129. /// <param name="formatters">The collection of <see cref="MediaTypeFormatter"/> instances
  130. /// to use for serialization or deserialization.</param>
  131. public ObjectContent(Type type, HttpContent content, IEnumerable<MediaTypeFormatter> formatters)
  132. : this(type, content)
  133. {
  134. if (formatters == null)
  135. {
  136. throw new ArgumentNullException("formatters");
  137. }
  138. this.formatters = new MediaTypeFormatterCollection(formatters);
  139. }
  140. private ObjectContent(Type type)
  141. {
  142. if (type == null)
  143. {
  144. throw new ArgumentNullException("type");
  145. }
  146. if (HttpContentType.IsAssignableFrom(type))
  147. {
  148. throw new ArgumentException(SR.CannotUseThisParameterType(HttpContentType.Name, ObjectContentType.Name), "type");
  149. }
  150. this.ObjectType = type;
  151. }
  152. /// <summary>
  153. /// Gets the type of object managed by this <see cref="ObjectContent"/> instance.
  154. /// </summary>
  155. public Type ObjectType { get; private set; }
  156. /// <summary>
  157. /// Gets the mutable collection of <see cref="MediaTypeFormatter"/> instances used to
  158. /// serialize or deserialize the value of this <see cref="ObjectContent"/>.
  159. /// </summary>
  160. public MediaTypeFormatterCollection Formatters
  161. {
  162. get
  163. {
  164. if (this.formatters == null)
  165. {
  166. this.formatters = new MediaTypeFormatterCollection();
  167. }
  168. return this.formatters;
  169. }
  170. }
  171. internal MediaTypeFormatter DefaultFormatter
  172. {
  173. get
  174. {
  175. if (this.defaultFormatter == null)
  176. {
  177. this.defaultFormatter = this.Formatters.XmlFormatter;
  178. if (this.defaultFormatter == null)
  179. {
  180. this.defaultFormatter = this.Formatters.JsonFormatter;
  181. }
  182. }
  183. return this.defaultFormatter;
  184. }
  185. set
  186. {
  187. this.defaultFormatter = value;
  188. }
  189. }
  190. internal HttpRequestMessage HttpRequestMessage
  191. {
  192. get
  193. {
  194. return this.requestMessage != null && object.ReferenceEquals(this.requestMessage.Content, this)
  195. ? this.requestMessage
  196. : null;
  197. }
  198. set
  199. {
  200. this.requestMessage = value;
  201. // Pairing to a request unpairs from response
  202. if (value != null)
  203. {
  204. this.HttpResponseMessage = null;
  205. }
  206. }
  207. }
  208. internal HttpResponseMessage HttpResponseMessage
  209. {
  210. get
  211. {
  212. return this.responseMessage != null && object.ReferenceEquals(this.responseMessage.Content, this)
  213. ? this.responseMessage
  214. : null;
  215. }
  216. set
  217. {
  218. this.responseMessage = value;
  219. // pairing to a response unpairs from a request
  220. if (value != null)
  221. {
  222. this.HttpRequestMessage = null;
  223. }
  224. }
  225. }
  226. /// <summary>
  227. /// Gets or sets the inner <see cref="HttpContent"/> wrapped by
  228. /// by the current <see cref="ObjectContent"/>.
  229. /// </summary>
  230. protected HttpContent HttpContent { get; set; }
  231. /// <summary>
  232. /// Gets or sets the value of the current <see cref="ObjectContent"/>.
  233. /// </summary>
  234. protected object Value { get; set; }
  235. private object DefaultValue
  236. {
  237. get
  238. {
  239. if (this.defaultValue == null)
  240. {
  241. this.defaultValue = GetDefaultValueForType(this.ObjectType);
  242. }
  243. return this.defaultValue;
  244. }
  245. }
  246. private bool IsValueCached
  247. {
  248. get
  249. {
  250. return this.HttpContent == null;
  251. }
  252. }
  253. private MediaTypeHeaderValue MediaType
  254. {
  255. get
  256. {
  257. return this.Headers.ContentType;
  258. }
  259. set
  260. {
  261. this.Headers.ContentType = value;
  262. }
  263. }
  264. /// <summary>
  265. /// Asynchronously returns the object instance for this <see cref="ObjectContent"/>.
  266. /// </summary>
  267. /// <returns>A <see cref="Task"/> instance that will yield the object instance.</returns>
  268. public Task<object> ReadAsAsync()
  269. {
  270. return this.ReadAsAsyncInternal(allowDefaultIfNoFormatter: false);
  271. }
  272. /// <summary>
  273. /// Asynchronously returns the object instance for this <see cref="ObjectContent"/>
  274. /// or the default value for the type if content is not available.
  275. /// </summary>
  276. /// <returns>A <see cref="Task"/> instance that will yield the object instance.</returns>
  277. public Task<object> ReadAsOrDefaultAsync()
  278. {
  279. return this.ReadAsAsyncInternal(allowDefaultIfNoFormatter: true);
  280. }
  281. /// <summary>
  282. /// Forces selection of the write <see cref="MediaTypeFormatter"/> and content-type.
  283. /// </summary>
  284. internal void DetermineWriteSerializerAndContentType()
  285. {
  286. this.selectedWriteFormatter = this.SelectAndValidateWriteFormatter();
  287. }
  288. internal void SetFormatters(IEnumerable<MediaTypeFormatter> list)
  289. {
  290. this.formatters = new MediaTypeFormatterCollection(list);
  291. }
  292. /// <summary>
  293. /// Asynchronously serializes the object's content to the given <paramref name="stream"/>.
  294. /// </summary>
  295. /// <param name="stream">The <see cref="Stream"/> to which to write.</param>
  296. /// <param name="context">The associated <see cref="TransportContext"/>.</param>
  297. /// <returns>A <see cref="Task"/> instance that is asynchronously serializing the object's content.</returns>
  298. protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
  299. {
  300. return this.WriteToStreamAsyncInternal(stream, context);
  301. }
  302. /// <summary>
  303. /// Asynchronously creates the content read stream.
  304. /// </summary>
  305. /// <returns>A <see cref="Task"/> instance that will yield a stream intended for reading.</returns>
  306. protected override Task<Stream> CreateContentReadStreamAsync()
  307. {
  308. if (this.HttpContent != null)
  309. {
  310. return this.HttpContent.ReadAsStreamAsync();
  311. }
  312. else
  313. {
  314. return base.CreateContentReadStreamAsync();
  315. }
  316. }
  317. /// <summary>
  318. /// Computes the length of the stream if possible.
  319. /// </summary>
  320. /// <param name="length">The computed length of the stream.</param>
  321. /// <returns><c>true</c> if the length has been computed; otherwise <c>false</c>.</returns>
  322. protected override bool TryComputeLength(out long length)
  323. {
  324. HttpContent httpContent = this.HttpContent;
  325. if (httpContent != null)
  326. {
  327. long? contentLength = httpContent.Headers.ContentLength;
  328. if (contentLength.HasValue)
  329. {
  330. length = contentLength.Value;
  331. return true;
  332. }
  333. }
  334. length = -1;
  335. return false;
  336. }
  337. /// <summary>
  338. /// Selects the appropriate <see cref="MediaTypeFormatter"/> to read the object content.
  339. /// </summary>
  340. /// <returns>The selected <see cref="MediaTypeFormatter"/> or null.</returns>
  341. protected MediaTypeFormatter SelectReadFormatter()
  342. {
  343. HttpRequestMessage request = this.HttpRequestMessage;
  344. HttpResponseMessage response = this.HttpResponseMessage;
  345. Type type = this.ObjectType;
  346. if (request != null)
  347. {
  348. foreach (MediaTypeFormatter formatter in this.Formatters)
  349. {
  350. if (formatter.CanReadAs(type, request))
  351. {
  352. return formatter;
  353. }
  354. }
  355. }
  356. else if (response != null)
  357. {
  358. foreach (MediaTypeFormatter formatter in this.Formatters)
  359. {
  360. if (formatter.CanReadAs(type, response))
  361. {
  362. return formatter;
  363. }
  364. }
  365. }
  366. else
  367. {
  368. foreach (MediaTypeFormatter formatter in this.Formatters)
  369. {
  370. if (formatter.CanReadAs(type, this))
  371. {
  372. return formatter;
  373. }
  374. }
  375. }
  376. return null;
  377. }
  378. /// <summary>
  379. /// Selects the appropriate <see cref="MediaTypeFormatter"/> to write the object content.
  380. /// </summary>
  381. /// <param name="mediaType">The <see cref="MediaTypeHeaderValue"/> to use to describe the object's content type.</param>
  382. /// <returns>The selected <see cref="MediaTypeFormatter"/> or null.</returns>
  383. [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "Out parameter is fine here.")]
  384. [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Complexity is acceptable for now. A larger refactor is planned.")]
  385. protected MediaTypeFormatter SelectWriteFormatter(out MediaTypeHeaderValue mediaType)
  386. {
  387. mediaType = null;
  388. // We are paired with a request, or a response, or neither.
  389. HttpRequestMessage request = this.HttpRequestMessage;
  390. HttpResponseMessage response = this.HttpResponseMessage;
  391. Type type = this.ObjectType;
  392. if (request != null)
  393. {
  394. foreach (MediaTypeFormatter formatter in this.Formatters)
  395. {
  396. if (formatter.CanWriteAs(type, request, out mediaType))
  397. {
  398. return formatter;
  399. }
  400. }
  401. }
  402. else if (response != null)
  403. {
  404. MediaTypeFormatter formatterMatchOnType = null;
  405. ResponseMediaTypeMatch mediaTypeMatchOnType = null;
  406. MediaTypeFormatter formatterMatchOnAcceptHeader = null;
  407. ResponseMediaTypeMatch mediaTypeMatchOnAcceptHeader = null;
  408. MediaTypeFormatter formatterMatchOnAcceptHeaderWithMapping = null;
  409. ResponseMediaTypeMatch mediaTypeMatchOnAcceptHeaderWithMapping = null;
  410. MediaTypeFormatter formatterMatchOnRequestContentType = null;
  411. ResponseMediaTypeMatch mediaTypeMatchOnRequestContentType = null;
  412. foreach (MediaTypeFormatter formatter in this.Formatters)
  413. {
  414. ResponseMediaTypeMatch match = formatter.SelectResponseMediaType(type, response);
  415. if (match == null)
  416. {
  417. // Null signifies no match
  418. continue;
  419. }
  420. ResponseFormatterSelectionResult matchResult = match.ResponseFormatterSelectionResult;
  421. switch (matchResult)
  422. {
  423. case ResponseFormatterSelectionResult.MatchOnCanWriteType:
  424. // First match by type trumps all other type matches
  425. if (formatterMatchOnType == null)
  426. {
  427. formatterMatchOnType = formatter;
  428. mediaTypeMatchOnType = match;
  429. }
  430. break;
  431. case ResponseFormatterSelectionResult.MatchOnResponseContentType:
  432. // Match on response content trumps all other choices
  433. return formatter;
  434. case ResponseFormatterSelectionResult.MatchOnRequestAcceptHeader:
  435. // Matches on accept headers must choose the highest quality match
  436. double thisQuality = match.MediaTypeMatch.Quality;
  437. if (formatterMatchOnAcceptHeader != null)
  438. {
  439. double bestQualitySeen = mediaTypeMatchOnAcceptHeader.MediaTypeMatch.Quality;
  440. if (thisQuality <= bestQualitySeen)
  441. {
  442. continue;
  443. }
  444. }
  445. formatterMatchOnAcceptHeader = formatter;
  446. mediaTypeMatchOnAcceptHeader = match;
  447. break;
  448. case ResponseFormatterSelectionResult.MatchOnRequestAcceptHeaderWithMediaTypeMapping:
  449. // Matches on accept headers using mappings must choose the highest quality match
  450. double thisMappingQuality = match.MediaTypeMatch.Quality;
  451. if (mediaTypeMatchOnAcceptHeaderWithMapping != null)
  452. {
  453. double bestMappingQualitySeen = mediaTypeMatchOnAcceptHeaderWithMapping.MediaTypeMatch.Quality;
  454. if (thisMappingQuality <= bestMappingQualitySeen)
  455. {
  456. continue;
  457. }
  458. }
  459. formatterMatchOnAcceptHeaderWithMapping = formatter;
  460. mediaTypeMatchOnAcceptHeaderWithMapping = match;
  461. break;
  462. case ResponseFormatterSelectionResult.MatchOnRequestContentType:
  463. // First match on request content type trumps other request content matches
  464. if (formatterMatchOnRequestContentType == null)
  465. {
  466. formatterMatchOnRequestContentType = formatter;
  467. mediaTypeMatchOnRequestContentType = match;
  468. }
  469. break;
  470. }
  471. }
  472. // If we received matches based on both supported media types and from media type mappings,
  473. // we want to give precedence to the media type mappings, but only if their quality is >= that of the supported media type.
  474. // We do this because media type mappings are the user's extensibility point and must take precedence over normal
  475. // supported media types in the case of a tie. The 99% case is where both have quality 1.0.
  476. if (mediaTypeMatchOnAcceptHeaderWithMapping != null && mediaTypeMatchOnAcceptHeader != null)
  477. {
  478. if (mediaTypeMatchOnAcceptHeader.MediaTypeMatch.Quality > mediaTypeMatchOnAcceptHeaderWithMapping.MediaTypeMatch.Quality)
  479. {
  480. formatterMatchOnAcceptHeaderWithMapping = null;
  481. }
  482. }
  483. // now select the formatter and media type
  484. // A MediaTypeMapping is highest precedence -- it is an extensibility point
  485. // allowing the user to override normal accept header matching
  486. if (formatterMatchOnAcceptHeaderWithMapping != null)
  487. {
  488. mediaType = mediaTypeMatchOnAcceptHeaderWithMapping.MediaTypeMatch.MediaType;
  489. return formatterMatchOnAcceptHeaderWithMapping;
  490. }
  491. else if (formatterMatchOnAcceptHeader != null)
  492. {
  493. mediaType = mediaTypeMatchOnAcceptHeader.MediaTypeMatch.MediaType;
  494. return formatterMatchOnAcceptHeader;
  495. }
  496. else if (formatterMatchOnRequestContentType != null)
  497. {
  498. mediaType = mediaTypeMatchOnRequestContentType.MediaTypeMatch.MediaType;
  499. return formatterMatchOnRequestContentType;
  500. }
  501. else if (formatterMatchOnType != null)
  502. {
  503. mediaType = mediaTypeMatchOnType.MediaTypeMatch.MediaType;
  504. return formatterMatchOnType;
  505. }
  506. }
  507. else
  508. {
  509. foreach (MediaTypeFormatter formatter in this.Formatters)
  510. {
  511. if (formatter.CanWriteAs(type, this, out mediaType))
  512. {
  513. return formatter;
  514. }
  515. }
  516. }
  517. mediaType = null;
  518. return null;
  519. }
  520. /// <summary>
  521. /// Determines if the given <paramref name="value"/> is an instance of
  522. /// <see cref="HttpContent"/> or is some type we automatically wrap inside
  523. /// <see cref="HttpContent"/>.
  524. /// </summary>
  525. /// <param name="value">The object value to test.</param>
  526. /// <returns>A non-null <see cref="HttpContent"/> if the <paramref name="value"/>
  527. /// was an instance of <see cref="HttpContent"/> or needed to be wrapped
  528. /// inside one. A <c>null</c> indicates the <paramref name="value"/> is not
  529. /// <see cref="HttpContent"/> or needed to be wrapped in one.</returns>
  530. private static HttpContent WrapOrCastAsHttpContent(object value)
  531. {
  532. Stream stream = value as Stream;
  533. return stream == null ? value as HttpContent : new StreamContent(stream);
  534. }
  535. private static object GetDefaultValueForType(Type type)
  536. {
  537. if (!type.IsValueType)
  538. {
  539. return null;
  540. }
  541. if (type.IsEnum)
  542. {
  543. Array enumValues = Enum.GetValues(type);
  544. if (enumValues.Length > 0)
  545. {
  546. return enumValues.GetValue(0);
  547. }
  548. }
  549. return Activator.CreateInstance(type);
  550. }
  551. private static bool IsTypeNullable(Type type)
  552. {
  553. return !type.IsValueType ||
  554. (type.IsGenericType &&
  555. type.GetGenericTypeDefinition() == NullableType);
  556. }
  557. private void CacheValueAndDisposeWrappedHttpContent(object value)
  558. {
  559. this.Value = value;
  560. if (this.HttpContent != null)
  561. {
  562. this.HttpContent.Dispose();
  563. this.HttpContent = null;
  564. }
  565. Contract.Assert(this.IsValueCached, "IsValueCached must be true.");
  566. }
  567. private object ReadAsInternal(bool allowDefaultIfNoFormatter)
  568. {
  569. if (this.IsValueCached)
  570. {
  571. return this.Value;
  572. }
  573. object value;
  574. HttpContent httpContent = this.HttpContent;
  575. Contract.Assert(httpContent.Headers != null, "HttpContent headers are never null.");
  576. if (httpContent.Headers.ContentLength == 0)
  577. {
  578. value = this.DefaultValue;
  579. }
  580. else
  581. {
  582. MediaTypeFormatter formatter = this.SelectAndValidateReadFormatter(acceptNullFormatter: allowDefaultIfNoFormatter);
  583. if (formatter == null)
  584. {
  585. Contract.Assert(allowDefaultIfNoFormatter, "allowDefaultIfNoFormatter should always be true here.");
  586. value = this.DefaultValue;
  587. }
  588. else
  589. {
  590. // Delegate to the wrapped HttpContent for the stream
  591. value = formatter.ReadFromStream(this.ObjectType, httpContent.ReadAsStreamAsync().Result, this.Headers);
  592. }
  593. }
  594. this.CacheValueAndDisposeWrappedHttpContent(value);
  595. return value;
  596. }
  597. private Task<object> ReadAsAsyncInternal(bool allowDefaultIfNoFormatter)
  598. {
  599. if (this.IsValueCached)
  600. {
  601. return Task.Factory.StartNew<object>(() => this.Value);
  602. }
  603. HttpContent httpContent = this.HttpContent;
  604. Contract.Assert(httpContent.Headers != null, "HttpContent headers are never null.");
  605. if (httpContent.Headers.ContentLength == 0)
  606. {
  607. object defaultValue = this.DefaultValue;
  608. this.CacheValueAndDisposeWrappedHttpContent(defaultValue);
  609. return Task.Factory.StartNew<object>(() => defaultValue);
  610. }
  611. MediaTypeFormatter formatter = this.SelectAndValidateReadFormatter(acceptNullFormatter: allowDefaultIfNoFormatter);
  612. if (formatter == null)
  613. {
  614. Contract.Assert(allowDefaultIfNoFormatter, "allowDefaultIfNoFormatter should always be true here.");
  615. object defaultValue = this.DefaultValue;
  616. this.CacheValueAndDisposeWrappedHttpContent(defaultValue);
  617. return Task.Factory.StartNew<object>(() => defaultValue);
  618. }
  619. // If we wrap an HttpContent, delegate to its stream..
  620. return formatter.ReadFromStreamAsync(
  621. this.ObjectType,
  622. httpContent.ReadAsStreamAsync().Result,
  623. this.Headers)
  624. .ContinueWith<object>((task) =>
  625. {
  626. object value = task.Result;
  627. this.CacheValueAndDisposeWrappedHttpContent(value);
  628. return value;
  629. });
  630. }
  631. private MediaTypeFormatter SelectAndValidateReadFormatter(bool acceptNullFormatter)
  632. {
  633. MediaTypeFormatter formatter = this.SelectReadFormatter();
  634. if (formatter == null)
  635. {
  636. if (!acceptNullFormatter)
  637. {
  638. MediaTypeHeaderValue mediaType = this.Headers.ContentType;
  639. string mediaTypeAsString = mediaType != null ? mediaType.MediaType : SR.UndefinedMediaType;
  640. throw new InvalidOperationException(
  641. SR.NoReadSerializerAvailable(MediaTypeFormatterType.Name, this.ObjectType.Name, mediaTypeAsString));
  642. }
  643. }
  644. return formatter;
  645. }
  646. private Task WriteToStreamAsyncInternal(Stream stream, TransportContext context)
  647. {
  648. if (this.HttpContent != null)
  649. {
  650. return this.HttpContent.CopyToAsync(stream, context);
  651. }
  652. MediaTypeFormatter formatter = this.selectedWriteFormatter ?? this.SelectAndValidateWriteFormatter();
  653. return formatter.WriteToStreamAsync(this.ObjectType, this.Value, stream, this.Headers, context);
  654. }
  655. private MediaTypeFormatter SelectAndValidateWriteFormatter()
  656. {
  657. MediaTypeHeaderValue mediaType = null;
  658. MediaTypeFormatter formatter = this.SelectWriteFormatter(out mediaType);
  659. if (formatter == null)
  660. {
  661. if (this.DefaultFormatter != null &&
  662. this.DefaultFormatter.SupportedMediaTypes.Count > 0)
  663. {
  664. formatter = this.DefaultFormatter;
  665. mediaType = this.DefaultFormatter.SupportedMediaTypes[0];
  666. }
  667. else
  668. {
  669. string errorMessage = this.MediaType == null
  670. ? SR.MediaTypeMustBeSetBeforeWrite(HeadersContentTypeName, ObjectContentType.Name)
  671. : SR.NoWriteSerializerAvailable(MediaTypeFormatterType.Name, this.ObjectType.Name, this.MediaType.ToString());
  672. throw new InvalidOperationException(errorMessage);
  673. }
  674. }
  675. // Update our MediaType based on what the formatter said it would produce
  676. // TODO: 228498 MediaType should provide an extensibility point here so that we could avoid special casing
  677. // ODataMediaTypeFormatter here
  678. if (mediaType != null)
  679. {
  680. IEnumerable<KeyValuePair<string, string>> headers = formatter.GetResponseHeaders(this.ObjectType, mediaType.MediaType, this.responseMessage);
  681. if (headers == null)
  682. {
  683. this.MediaType = mediaType;
  684. }
  685. else
  686. {
  687. // we need to set the content type based on the headers set by the serializer
  688. foreach (KeyValuePair<string, string> header in headers)
  689. {
  690. this.Headers.AddWithoutValidation(header.Key, header.Value);
  691. }
  692. }
  693. }
  694. return formatter;
  695. }
  696. private void VerifyAndSetObject(object value)
  697. {
  698. Contract.Assert(this.ObjectType != null, "this.Type cannot be null");
  699. if (value == null)
  700. {
  701. // Null may not be assigned to value types (unless Nullable<T>)
  702. // We allow an ObjectContent of type void and value null as a special case
  703. if (this.ObjectType != typeof(void) && !IsTypeNullable(this.ObjectType))
  704. {
  705. throw new InvalidOperationException(SR.CannotUseNullValueType(ObjectContentType.Name, this.ObjectType.Name));
  706. }
  707. }
  708. else
  709. {
  710. // It is possible to pass HttpContent as object and arrive at this
  711. // code path. Detect and redirect.
  712. HttpContent objAsHttpContent = WrapOrCastAsHttpContent(value);
  713. if (objAsHttpContent != null)
  714. {
  715. this.VerifyAndSetHttpContent(objAsHttpContent);
  716. return;
  717. }
  718. else
  719. {
  720. // Non-null objects must be a type assignable to this.Type
  721. Type objectType = value.GetType();
  722. if (!this.ObjectType.IsAssignableFrom(objectType))
  723. {
  724. throw new ArgumentException(
  725. SR.ObjectAndTypeDisagree(objectType.Name, this.ObjectType.Name),
  726. "value");
  727. }
  728. }
  729. }
  730. this.Value = value;
  731. }
  732. private void VerifyAndSetObjectAndMediaType(object value, MediaTypeHeaderValue mediaType)
  733. {
  734. Contract.Assert(this.ObjectType != null, "this.ObjectType cannot be null");
  735. // It is possible to pass HttpContent as object and arrive at this
  736. // code path. Detect and redirect. We do not use the media type
  737. // specified unless the given HttpContent's media type is null.
  738. HttpContent objAsHttpContent = WrapOrCastAsHttpContent(value);
  739. if (objAsHttpContent != null)
  740. {
  741. this.VerifyAndSetHttpContent(objAsHttpContent);
  742. if (objAsHttpContent.Headers.ContentType == null)
  743. {
  744. this.VerifyAndSetMediaType(mediaType);
  745. }
  746. }
  747. else
  748. {
  749. this.VerifyAndSetObject(value);
  750. this.VerifyAndSetMediaType(mediaType);
  751. }
  752. }
  753. private void VerifyAndSetObjectAndMediaType(object value, string mediaType)
  754. {
  755. Contract.Assert(this.ObjectType != null, "this.ObjectType cannot be null");
  756. // It is possible to pass HttpContent as object and arrive at this
  757. // code path. Detect and redirect. We do not use the media type
  758. // specified unless the given HttpContent's media type is null.
  759. HttpContent objAsHttpContent = WrapOrCastAsHttpContent(value);
  760. if (objAsHttpContent != null)
  761. {
  762. this.VerifyAndSetHttpContent(objAsHttpContent);
  763. if (objAsHttpContent.Headers.ContentType == null)
  764. {
  765. this.VerifyAndSetMediaType(mediaType);
  766. }
  767. }
  768. else
  769. {
  770. this.VerifyAndSetObject(value);
  771. this.VerifyAndSetMediaType(mediaType);
  772. }
  773. }
  774. private void VerifyAndSetHttpContent(HttpContent content)
  775. {
  776. if (content == null)
  777. {
  778. throw new ArgumentNullException("content");
  779. }
  780. this.HttpContent = content;
  781. content.Headers.CopyTo(this.Headers);
  782. }
  783. private void VerifyAndSetMediaType(MediaTypeHeaderValue mediaType)
  784. {
  785. if (mediaType == null)
  786. {
  787. throw new ArgumentNullException("mediaType");
  788. }
  789. if (mediaType.IsMediaRange())
  790. {
  791. throw new InvalidOperationException(
  792. SR.MediaTypeCanNotBeMediaRange(mediaType.MediaType));
  793. }
  794. this.MediaType = mediaType;
  795. }
  796. private void VerifyAndSetMediaType(string mediaType)
  797. {
  798. if (string.IsNullOrWhiteSpace(mediaType))
  799. {
  800. throw new ArgumentNullException("mediaType");
  801. }
  802. MediaTypeHeaderValue mediaTypeHeaderValue = null;
  803. try
  804. {
  805. mediaTypeHeaderValue = new MediaTypeHeaderValue(mediaType);
  806. }
  807. catch (FormatException formatException)
  808. {
  809. throw new ArgumentException(
  810. SR.InvalidMediaType(mediaType, MediaTypeHeaderValueType.Name),
  811. "mediaType",
  812. formatException);
  813. }
  814. this.VerifyAndSetMediaType(mediaTypeHeaderValue);
  815. }
  816. }
  817. /// <summary>
  818. /// Generic form of <see cref="ObjectContent"/>.
  819. /// </summary>
  820. /// <typeparam name="T">The type of object this <see cref="ObjectContent"/> class will contain.</typeparam>
  821. [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Class contains generic forms")]
  822. public class ObjectContent<T> : ObjectContent
  823. {
  824. private static readonly Type MediaTypeFormatterType = typeof(MediaTypeFormatter);
  825. /// <summary>
  826. /// Initializes a new instance of the <see cref="ObjectContent{T}"/> class.
  827. /// </summary>
  828. /// <param name="value">The value of the object this instance will contain.</param>
  829. public ObjectContent(T value)
  830. : base(typeof(T), value)
  831. {
  832. this.Value = value;
  833. }
  834. /// <summary>
  835. /// Initializes a new instance of the <see cref="ObjectContent{T}"/> class.
  836. /// </summary>
  837. /// <param name="value">The value of the object this instance will contain.</param>
  838. /// <param name="mediaType">The media type to associate with this object.</param>
  839. public ObjectContent(T value, string mediaType)
  840. : base(typeof(T), value, mediaType)
  841. {
  842. this.Value = value;
  843. }
  844. /// <summary>
  845. /// Initializes a new instance of the <see cref="ObjectContent{T}"/> class.
  846. /// </summary>
  847. /// <param name="value">The value of the object this instance will contain.</param>
  848. /// <param name="mediaType">The media type to associate with this object.</param>
  849. public ObjectContent(T value, MediaTypeHeaderValue mediaType)
  850. : base(typeof(T), value, mediaType)
  851. {
  852. this.Value = value;
  853. }
  854. /// <summary>
  855. /// Initializes a new instance of the <see cref="ObjectContent{T}"/> class.
  856. /// </summary>
  857. /// <param name="content">An existing <see cref="HttpContent"/> instance to use for the object's content.</param>
  858. public ObjectContent(HttpContent content)
  859. : base(typeof(T), content)
  860. {
  861. this.HttpContent = content;
  862. }
  863. /// <summary>
  864. /// Initializes a new instance of the <see cref="ObjectContent{T}"/> class.
  865. /// </summary>
  866. /// <param name="value">The value of the object this instance will contain.</param>
  867. /// <param name="formatters">The collection of <see cref="MediaTypeFormatter"/> instances
  868. /// to serialize or deserialize the object content.</param>
  869. public ObjectContent(T value, IEnumerable<MediaTypeFormatter> formatters)
  870. : base(typeof(T), value, formatters)
  871. {
  872. this.Value = value;
  873. }
  874. /// <summary>
  875. /// Initializes a new instance of the <see cref="ObjectContent{T}"/> class.
  876. /// </summary>
  877. /// <param name="value">The value of the object this instance will contain.</param>
  878. /// <param name="mediaType">The media type to associate with this object.</param>
  879. /// <param name="formatters">The collection of <see cref="MediaTypeFormatter"/> instances
  880. /// to serialize or deserialize the object content.</param>
  881. public ObjectContent(T value, string mediaType, IEnumerable<MediaTypeFormatter> formatters)
  882. : base(typeof(T), value, mediaType, formatters)
  883. {
  884. this.Value = value;
  885. }
  886. /// <summary>
  887. /// Initializes a new instance of the <see cref="ObjectContent{T}"/> class.
  888. /// </summary>
  889. /// <param name="value">The value of the object this instance will contain.</param>
  890. /// <param name="mediaType">The media type to associate with this object.</param>
  891. /// <param name="formatters">The collection of <see cref="MediaTypeFormatter"/> instances
  892. /// to serialize or deserialize the object content.</param>
  893. public ObjectContent(T value, MediaTypeHeaderValue mediaType, IEnumerable<MediaTypeFormatter> formatters)
  894. : base(typeof(T), value, mediaType, formatters)
  895. {
  896. this.Value = value;
  897. }
  898. /// <summary>
  899. /// Initializes a new instance of the <see cref="ObjectContent{T}"/> class.
  900. /// </summary>
  901. /// <param name="content">An existing <see cref="HttpContent"/> instance to use for the object's content.</param>
  902. /// <param name="formatters">The collection of <see cref="MediaTypeFormatter"/> instances
  903. /// to serialize or deserialize the object content.</param>
  904. public ObjectContent(HttpContent content, IEnumerable<MediaTypeFormatter> formatters)
  905. : base(typeof(T), content, formatters)
  906. {
  907. this.HttpContent = content;
  908. }
  909. private MediaTypeHeaderValue MediaType
  910. {
  911. get
  912. {
  913. return this.Headers.ContentType;
  914. }
  915. set
  916. {
  917. this.Headers.ContentType = value;
  918. }
  919. }
  920. private bool IsValueCached
  921. {
  922. get
  923. {
  924. return this.HttpContent == null;
  925. }
  926. }
  927. /// <summary>
  928. /// Returns a <see cref="Task"/> instance to yield the object instance for this <see cref="ObjectContent"/>.
  929. /// </summary>
  930. /// <returns>A <see cref="Task"/> that will yield the object instance.</returns>
  931. public new Task<T> ReadAsAsync()
  932. {
  933. return this.ReadAsAsyncInternal(allowDefaultIfNoFormatter: false);
  934. }
  935. /// <summary>
  936. /// Returns a <see cref="Task"/> instance to yield the object instance for this <see cref="ObjectContent"/>
  937. /// or the default value for the type if content is not available.
  938. /// </summary>
  939. /// <returns>A <see cref="Task"/> that will yield the object instance.</returns>
  940. public new Task<T> ReadAsOrDefaultAsync()
  941. {
  942. return this.ReadAsAsyncInternal(allowDefaultIfNoFormatter: true);
  943. }
  944. private void CacheValueAndDisposeWrappedHttpContent(T value)
  945. {
  946. this.Value = value;
  947. if (this.HttpContent != null)
  948. {
  949. this.HttpContent.Dispose();
  950. this.HttpContent = null;
  951. }
  952. Contract.Assert(this.IsValueCached, "IsValueCached must be true.");
  953. }
  954. private Task<T> ReadAsAsyncInternal(bool allowDefaultIfNoFormatter)
  955. {
  956. if (this.IsValueCached)
  957. {
  958. return Task.Factory.StartNew<T>(() => (T)this.Value);
  959. }
  960. HttpContent httpContent = this.HttpContent;
  961. Contract.Assert(httpContent.Headers != null, "HttpContent headers are never null.");
  962. if (httpContent.Headers.ContentLength == 0)
  963. {
  964. T defaultValue = default(T);
  965. this.CacheValueAndDisposeWrappedHttpContent(defaultValue);
  966. return Task.Factory.StartNew<T>(() => defaultValue);
  967. }
  968. MediaTypeFormatter formatter = this.SelectAndValidateReadFormatter(acceptNullFormatter: allowDefaultIfNoFormatter);
  969. if (formatter == null)
  970. {
  971. Contract.Assert(allowDefaultIfNoFormatter, "allowDefaultIfNoFormatter must be true to execute this code path.");
  972. T defaultValue = default(T);
  973. this.CacheValueAndDisposeWrappedHttpContent(defaultValue);
  974. return Task.Factory.StartNew<T>(() => defaultValue);
  975. }
  976. // If we wrap an HttpContent, delegate to its stream.
  977. return formatter.ReadFromStreamAsync(
  978. this.ObjectType,
  979. httpContent.ReadAsStreamAsync().Result,
  980. this.Headers)
  981. .ContinueWith<T>((task) =>
  982. {
  983. T value = (T)task.Result;
  984. this.CacheValueAndDisposeWrappedHttpContent(value);
  985. return value;
  986. });
  987. }
  988. private MediaTypeFormatter SelectAndValidateReadFormatter(bool acceptNullFormatter)
  989. {
  990. MediaTypeFormatter formatter = this.SelectReadFormatter();
  991. if (formatter == null)
  992. {
  993. if (!acceptNullFormatter)
  994. {
  995. MediaTypeHeaderValue mediaType = this.Headers.ContentType;
  996. string mediaTypeAsString = mediaType != null ? mediaType.MediaType : SR.UndefinedMediaType;
  997. throw new InvalidOperationException(
  998. SR.NoReadSerializerAvailable(MediaTypeFormatterType.Name, this.ObjectType.Name, mediaTypeAsString));
  999. }
  1000. }
  1001. return formatter;
  1002. }
  1003. }
  1004. }