PageRenderTime 79ms CodeModel.GetById 19ms app.highlight 51ms RepoModel.GetById 1ms app.codeStats 0ms

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