// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Net.Http.Formatting; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using System.Web.Http; namespace System.Net.Http { /// /// Extension methods to allow strongly typed objects to be read from instances. /// [EditorBrowsable(EditorBrowsableState.Never)] public static class HttpContentExtensions { private static MediaTypeFormatterCollection _defaultMediaTypeFormatterCollection = null; // Using the JsonMediaTypeFormatter for the first time is rather expensive (due to reflection cost // when creating the default contract resolver). Hence we new up a static collection, such // that the second call is much faster. private static MediaTypeFormatterCollection DefaultMediaTypeFormatterCollection { get { if (_defaultMediaTypeFormatterCollection == null) { _defaultMediaTypeFormatterCollection = new MediaTypeFormatterCollection(); } return _defaultMediaTypeFormatterCollection; } } /// /// Returns a that will yield an object of the specified /// from the instance. /// /// This override use the built-in collection of formatters. /// The instance from which to read. /// The type of the object to read. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, Type type) { return content.ReadAsAsync(type, DefaultMediaTypeFormatterCollection); } /// /// Returns a that will yield an object of the specified /// from the instance. /// /// This override use the built-in collection of formatters. /// The instance from which to read. /// The type of the object to read. /// The token to monitor for cancellation requests. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, Type type, CancellationToken cancellationToken) { return content.ReadAsAsync(type, DefaultMediaTypeFormatterCollection, cancellationToken); } /// /// Returns a that will yield an object of the specified /// from the instance using one of the provided /// to deserialize the content. /// /// The instance from which to read. /// The type of the object to read. /// The collection of instances to use. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, Type type, IEnumerable formatters) { return ReadAsAsync(content, type, formatters, null); } /// /// Returns a that will yield an object of the specified /// from the instance using one of the provided /// to deserialize the content. /// /// The instance from which to read. /// The type of the object to read. /// The collection of instances to use. /// The token to monitor for cancellation requests. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, Type type, IEnumerable formatters, CancellationToken cancellationToken) { return ReadAsAsync(content, type, formatters, null, cancellationToken); } /// /// Returns a that will yield an object of the specified /// from the instance using one of the provided /// to deserialize the content. /// /// The instance from which to read. /// The type of the object to read. /// The collection of instances to use. /// The to log events to. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, Type type, IEnumerable formatters, IFormatterLogger formatterLogger) { return ReadAsAsync(content, type, formatters, formatterLogger); } /// /// Returns a that will yield an object of the specified /// from the instance using one of the provided /// to deserialize the content. /// /// The instance from which to read. /// The type of the object to read. /// The collection of instances to use. /// The to log events to. /// The token to monitor for cancellation requests. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, Type type, IEnumerable formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken) { return ReadAsAsync(content, type, formatters, formatterLogger, cancellationToken); } /// /// Returns a that will yield an object of the specified /// type from the instance. /// /// This override use the built-in collection of formatters. /// The type of the object to read. /// The instance from which to read. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content) { return content.ReadAsAsync(DefaultMediaTypeFormatterCollection); } /// /// Returns a that will yield an object of the specified /// type from the instance. /// /// This override use the built-in collection of formatters. /// The type of the object to read. /// The instance from which to read. /// The token to monitor for cancellation requests. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, CancellationToken cancellationToken) { return content.ReadAsAsync(DefaultMediaTypeFormatterCollection, cancellationToken); } /// /// Returns a that will yield an object of the specified /// type from the instance. /// /// The type of the object to read. /// The instance from which to read. /// The collection of instances to use. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, IEnumerable formatters) { return ReadAsAsync(content, typeof(T), formatters, null); } /// /// Returns a that will yield an object of the specified /// type from the instance. /// /// The type of the object to read. /// The instance from which to read. /// The collection of instances to use. /// The token to monitor for cancellation requests. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, IEnumerable formatters, CancellationToken cancellationToken) { return ReadAsAsync(content, typeof(T), formatters, null, cancellationToken); } /// /// Returns a that will yield an object of the specified /// type from the instance. /// /// The type of the object to read. /// The instance from which to read. /// The collection of instances to use. /// The to log events to. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, IEnumerable formatters, IFormatterLogger formatterLogger) { return ReadAsAsync(content, typeof(T), formatters, formatterLogger); } /// /// Returns a that will yield an object of the specified /// type from the instance. /// /// The type of the object to read. /// The instance from which to read. /// The collection of instances to use. /// The to log events to. /// The token to monitor for cancellation requests. /// A task object representing reading the content as an object of the specified type. public static Task ReadAsAsync(this HttpContent content, IEnumerable formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken) { return ReadAsAsync(content, typeof(T), formatters, formatterLogger, cancellationToken); } private static Task ReadAsAsync(HttpContent content, Type type, IEnumerable formatters, IFormatterLogger formatterLogger) { return ReadAsAsync(content, type, formatters, formatterLogger, CancellationToken.None); } // There are many helper overloads for ReadAs*(). Provide one worker function to ensure the logic is shared. // // For loosely typed, T = Object, type = specific class. // For strongly typed, T == type.GetType() private static Task ReadAsAsync(HttpContent content, Type type, IEnumerable formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken) { if (content == null) { throw Error.ArgumentNull("content"); } if (type == null) { throw Error.ArgumentNull("type"); } if (formatters == null) { throw Error.ArgumentNull("formatters"); } ObjectContent objectContent = content as ObjectContent; if (objectContent != null && objectContent.Value != null && type.IsAssignableFrom(objectContent.Value.GetType())) { return Task.FromResult((T)objectContent.Value); } MediaTypeFormatter formatter = null; // Default to "application/octet-stream" if there is no content-type in accordance with section 7.2.1 of the HTTP spec MediaTypeHeaderValue mediaType = content.Headers.ContentType ?? MediaTypeConstants.ApplicationOctetStreamMediaType; formatter = new MediaTypeFormatterCollection(formatters).FindReader(type, mediaType); if (formatter == null) { if (content.Headers.ContentLength == 0) { T defaultValue = (T)MediaTypeFormatter.GetDefaultValueForType(type); return Task.FromResult(defaultValue); } throw new UnsupportedMediaTypeException( Error.Format(Properties.Resources.NoReadSerializerAvailable, type.Name, mediaType.MediaType), mediaType); } return ReadAsAsyncCore(content, type, formatterLogger, formatter, cancellationToken); } private static async Task ReadAsAsyncCore(HttpContent content, Type type, IFormatterLogger formatterLogger, MediaTypeFormatter formatter, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); Stream stream = await content.ReadAsStreamAsync(); object result = await formatter.ReadFromStreamAsync(type, stream, content, formatterLogger, cancellationToken); return (T)result; } } }