1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Collections.Concurrent; 4 using System.Collections.Generic; 5 using System.Collections.ObjectModel; 6 using System.Collections.Specialized; 7 using System.Configuration; 8 using System.Diagnostics.CodeAnalysis; 9 using System.Diagnostics.Contracts; 10 using System.IO; 11 using System.Linq; 12 using System.Net.Http.Headers; 13 using System.Reflection; 14 using System.Text; 15 using System.Threading.Tasks; 16 17 namespace System.Net.Http.Formatting 18 { 19 /// <summary> 20 /// Base class to handle serializing and deserializing strongly-typed objects using <see cref="ObjectContent"/>. 21 /// </summary> 22 public abstract class MediaTypeFormatter 23 { 24 private const int DefaultMinHttpCollectionKeys = 1; 25 private const int DefaultMaxHttpCollectionKeys = 1000; // same default as ASPNET 26 private const string IWellKnownComparerTypeName = "System.IWellKnownStringEqualityComparer, mscorlib, Version=4.0.0.0, PublicKeyToken=b77a5c561934e089"; 27 28 private static readonly ConcurrentDictionary<Type, Type> _delegatingEnumerableCache = new ConcurrentDictionary<Type, Type>(); 29 private static ConcurrentDictionary<Type, ConstructorInfo> _delegatingEnumerableConstructorCache = new ConcurrentDictionary<Type, ConstructorInfo>(); 30 private static Lazy<int> _defaultMaxHttpCollectionKeys = new Lazy<int>(InitializeDefaultCollectionKeySize, true); // Max number of keys is 1000 31 private static int _maxHttpCollectionKeys = -1; 32 33 /// <summary> 34 /// Initializes a new instance of the <see cref="MediaTypeFormatter"/> class. 35 /// </summary> MediaTypeFormatter()36 protected MediaTypeFormatter() 37 { 38 SupportedMediaTypes = new MediaTypeHeaderValueCollection(); 39 SupportedEncodings = new Collection<Encoding>(); 40 MediaTypeMappings = new Collection<MediaTypeMapping>(); 41 } 42 43 /// <summary> 44 /// Gets or sets the maximum number of keys stored in a NameValueCollection. 45 /// </summary> 46 public static int MaxHttpCollectionKeys 47 { 48 get 49 { 50 if (_maxHttpCollectionKeys < 0) 51 { 52 _maxHttpCollectionKeys = _defaultMaxHttpCollectionKeys.Value; 53 } 54 55 return _maxHttpCollectionKeys; 56 } 57 set 58 { 59 if (value < DefaultMinHttpCollectionKeys) 60 { 61 throw new ArgumentOutOfRangeException("value", value, RS.Format(Properties.Resources.ArgumentMustBeGreaterThanOrEqualTo, DefaultMinHttpCollectionKeys)); 62 } 63 64 _maxHttpCollectionKeys = value; 65 } 66 } 67 68 /// <summary> 69 /// Gets the mutable collection of <see cref="MediaTypeHeaderValue"/> elements supported by 70 /// this <see cref="MediaTypeFormatter"/> instance. 71 /// </summary> 72 public Collection<MediaTypeHeaderValue> SupportedMediaTypes { get; private set; } 73 74 /// <summary> 75 /// Gets the mutable collection of character encodings supported by 76 /// this <see cref="MediaTypeFormatter"/> instance. The encodings are 77 /// used when reading or writing data. 78 /// </summary> 79 public Collection<Encoding> SupportedEncodings { get; private set; } 80 81 /// <summary> 82 /// Gets the mutable collection of <see cref="MediaTypeMapping"/> elements used 83 /// by this <see cref="MediaTypeFormatter"/> instance to determine the 84 /// <see cref="MediaTypeHeaderValue"/> of requests or responses. 85 /// </summary> 86 public Collection<MediaTypeMapping> MediaTypeMappings { get; private set; } 87 88 /// <summary> 89 /// Gets or sets the <see cref="IRequiredMemberSelector"/> used to determine required members. 90 /// </summary> 91 public IRequiredMemberSelector RequiredMemberSelector { get; set; } 92 93 /// <summary> 94 /// Returns a <see cref="Task"/> to deserialize an object of the given <paramref name="type"/> from the given <paramref name="stream"/> 95 /// </summary> 96 /// <remarks> 97 /// <para>This implementation throws a <see cref="NotSupportedException"/>. Derived types should override this method if the formatter 98 /// supports reading.</para> 99 /// <para>An implementation of this method should NOT close <paramref name="stream"/> upon completion. The stream will be closed independently when 100 /// the <see cref="HttpContent"/> instance is disposed. 101 /// </para> 102 /// </remarks> 103 /// <param name="type">The type of the object to deserialize.</param> 104 /// <param name="stream">The <see cref="Stream"/> to read.</param> 105 /// <param name="contentHeaders">The <see cref="HttpContentHeaders"/> if available. It may be <c>null</c>.</param> 106 /// <param name="formatterLogger">The <see cref="IFormatterLogger"/> to log events to.</param> 107 /// <returns>A <see cref="Task"/> whose result will be an object of the given type.</returns> 108 /// <exception cref="NotSupportedException">Derived types need to support reading.</exception> 109 /// <seealso cref="CanWriteType(Type)"/> ReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)110 public virtual Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger) 111 { 112 throw new NotSupportedException( 113 RS.Format(Properties.Resources.MediaTypeFormatterCannotRead, GetType().Name)); 114 } 115 116 /// <summary> 117 /// Returns a <see cref="Task"/> that serializes the given <paramref name="value"/> of the given <paramref name="type"/> 118 /// to the given <paramref name="stream"/>. 119 /// </summary> 120 /// <remarks> 121 /// <para>This implementation throws a <see cref="NotSupportedException"/>. Derived types should override this method if the formatter 122 /// supports reading.</para> 123 /// <para>An implementation of this method should NOT close <paramref name="stream"/> upon completion. The stream will be closed independently when 124 /// the <see cref="HttpContent"/> instance is disposed. 125 /// </para> 126 /// </remarks> 127 /// <param name="type">The type of the object to write.</param> 128 /// <param name="value">The object value to write. It may be <c>null</c>.</param> 129 /// <param name="stream">The <see cref="Stream"/> to which to write.</param> 130 /// <param name="contentHeaders">The <see cref="HttpContentHeaders"/> if available. It may be <c>null</c>.</param> 131 /// <param name="transportContext">The <see cref="TransportContext"/> if available. It may be <c>null</c>.</param> 132 /// <returns>A <see cref="Task"/> that will perform the write.</returns> 133 /// <exception cref="NotSupportedException">Derived types need to support writing.</exception> 134 /// <seealso cref="CanReadType(Type)"/> WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)135 public virtual Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext) 136 { 137 throw new NotSupportedException( 138 RS.Format(Properties.Resources.MediaTypeFormatterCannotWrite, GetType().Name)); 139 } 140 TryGetDelegatingType(Type interfaceType, ref Type type)141 private static bool TryGetDelegatingType(Type interfaceType, ref Type type) 142 { 143 if (type != null 144 && type.IsInterface 145 && type.IsGenericType 146 && (type.GetInterface(interfaceType.FullName) != null || type.GetGenericTypeDefinition().Equals(interfaceType))) 147 { 148 type = GetOrAddDelegatingType(type); 149 return true; 150 } 151 152 return false; 153 } 154 InitializeDefaultCollectionKeySize()155 private static int InitializeDefaultCollectionKeySize() 156 { 157 // we first detect if we are running on 4.5, return Max value if we are. 158 Type comparerType = Type.GetType(IWellKnownComparerTypeName, throwOnError: false); 159 160 if (comparerType != null) 161 { 162 return Int32.MaxValue; 163 } 164 165 // we should try to read it from the AppSettings 166 // if we found the aspnet settings configured, we will use that. Otherwise, we used the default 167 NameValueCollection settings = ConfigurationManager.AppSettings; 168 int result; 169 170 if (settings == null || !Int32.TryParse(settings["aspnet:MaxHttpCollectionKeys"], out result) || result < 0) 171 { 172 result = DefaultMaxHttpCollectionKeys; 173 } 174 175 return result; 176 } 177 178 /// <summary> 179 /// This method converts <see cref="IEnumerable{T}"/> (and interfaces that mandate it) to a <see cref="DelegatingEnumerable{T}"/> for serialization purposes. 180 /// </summary> 181 /// <param name="type">The type to potentially be wrapped. If the type is wrapped, it's changed in place.</param> 182 /// <returns>Returns <c>true</c> if the type was wrapped; <c>false</c>, otherwise</returns> 183 [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "0#", Justification = "This API is designed to morph the type parameter appropriately")] TryGetDelegatingTypeForIEnumerableGenericOrSame(ref Type type)184 internal static bool TryGetDelegatingTypeForIEnumerableGenericOrSame(ref Type type) 185 { 186 return TryGetDelegatingType(FormattingUtilities.EnumerableInterfaceGenericType, ref type); 187 } 188 189 /// <summary> 190 /// This method converts <see cref="IQueryable{T}"/> (and interfaces that mandate it) to a <see cref="DelegatingEnumerable{T}"/> for serialization purposes. 191 /// </summary> 192 /// <param name="type">The type to potentially be wrapped. If the type is wrapped, it's changed in place.</param> 193 /// <returns>Returns <c>true</c> if the type was wrapped; <c>false</c>, otherwise</returns> 194 [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "0#", Justification = "This API is designed to morph the type parameter appropriately")] TryGetDelegatingTypeForIQueryableGenericOrSame(ref Type type)195 internal static bool TryGetDelegatingTypeForIQueryableGenericOrSame(ref Type type) 196 { 197 return TryGetDelegatingType(FormattingUtilities.QueryableInterfaceGenericType, ref type); 198 } 199 GetTypeRemappingConstructor(Type type)200 internal static ConstructorInfo GetTypeRemappingConstructor(Type type) 201 { 202 ConstructorInfo constructorInfo; 203 _delegatingEnumerableConstructorCache.TryGetValue(type, out constructorInfo); 204 return constructorInfo; 205 } 206 207 /// <summary> 208 /// Determines the best <see cref="Encoding"/> amongst the supported encodings 209 /// for reading or writing an HTTP entity body based on the provided <paramref name="contentHeaders"/>. 210 /// </summary> 211 /// <param name="contentHeaders">The content headers provided as part of the request or response.</param> 212 /// <returns>The <see cref="Encoding"/> to use when reading the request or writing the response.</returns> SelectCharacterEncoding(HttpContentHeaders contentHeaders)213 protected Encoding SelectCharacterEncoding(HttpContentHeaders contentHeaders) 214 { 215 Encoding encoding = null; 216 if (contentHeaders != null && contentHeaders.ContentType != null) 217 { 218 // Find encoding based on content type charset parameter 219 string charset = contentHeaders.ContentType.CharSet; 220 if (!String.IsNullOrWhiteSpace(charset)) 221 { 222 encoding = 223 SupportedEncodings.FirstOrDefault( 224 enc => charset.Equals(enc.WebName, StringComparison.OrdinalIgnoreCase)); 225 } 226 } 227 228 if (encoding == null) 229 { 230 // We didn't find a character encoding match based on the content headers. 231 // Instead we try getting the default character encoding. 232 encoding = SupportedEncodings.FirstOrDefault(); 233 } 234 235 if (encoding == null) 236 { 237 // No supported encoding was found so there is no way for us to start reading or writing. 238 throw new InvalidOperationException(RS.Format(Properties.Resources.MediaTypeFormatterNoEncoding, GetType().Name)); 239 } 240 241 return encoding; 242 } 243 CanReadAs(Type type, MediaTypeHeaderValue mediaType)244 internal bool CanReadAs(Type type, MediaTypeHeaderValue mediaType) 245 { 246 if (type == null) 247 { 248 throw new ArgumentNullException("type"); 249 } 250 251 if (mediaType == null) 252 { 253 throw new ArgumentNullException("mediaType"); 254 } 255 256 if (!CanReadType(type)) 257 { 258 return false; 259 } 260 261 MediaTypeMatch mediaTypeMatch; 262 return TryMatchSupportedMediaType(mediaType, out mediaTypeMatch); 263 } 264 CanWriteAs(Type type, MediaTypeHeaderValue mediaType, out MediaTypeHeaderValue matchedMediaType)265 internal bool CanWriteAs(Type type, MediaTypeHeaderValue mediaType, out MediaTypeHeaderValue matchedMediaType) 266 { 267 if (type == null) 268 { 269 throw new ArgumentNullException("type"); 270 } 271 272 if (mediaType == null) 273 { 274 throw new ArgumentNullException("mediaType"); 275 } 276 277 if (!CanWriteType(type)) 278 { 279 matchedMediaType = null; 280 return false; 281 } 282 283 MediaTypeMatch mediaTypeMatch; 284 if (TryMatchSupportedMediaType(mediaType, out mediaTypeMatch)) 285 { 286 matchedMediaType = mediaTypeMatch.MediaType; 287 return true; 288 } 289 290 matchedMediaType = null; 291 return false; 292 } 293 SelectResponseMediaType(Type type, HttpRequestMessage request)294 internal ResponseMediaTypeMatch SelectResponseMediaType(Type type, HttpRequestMessage request) 295 { 296 if (type == null) 297 { 298 throw new ArgumentNullException("type"); 299 } 300 301 if (request == null) 302 { 303 throw new ArgumentNullException("request"); 304 } 305 306 if (!CanWriteType(type)) 307 { 308 return null; 309 } 310 311 // Determine the best character encoding if we have any registered encoders. 312 // Note that it is ok for a formatter not to register any encoders in case it doesn't 313 // do any structured reading or writing. 314 Encoding characterEncodingMatch = SupportedEncodings.Any() ? SelectResponseCharacterEncoding(request) : null; 315 316 // Determine the best media type 317 MediaTypeMatch mediaTypeMatch = null; 318 319 // Match against media type mapping first 320 if (TryMatchMediaTypeMapping(request, out mediaTypeMatch)) 321 { 322 mediaTypeMatch.SetEncoding(characterEncodingMatch); 323 return new ResponseMediaTypeMatch( 324 mediaTypeMatch, 325 ResponseFormatterSelectionResult.MatchOnRequestWithMediaTypeMapping); 326 } 327 328 // Match against the accept header. 329 if (TryMatchSupportedMediaType(request, out mediaTypeMatch)) 330 { 331 mediaTypeMatch.SetEncoding(characterEncodingMatch); 332 return new ResponseMediaTypeMatch( 333 mediaTypeMatch, 334 ResponseFormatterSelectionResult.MatchOnRequestAcceptHeader); 335 } 336 337 // Match against request's content type 338 HttpContent requestContent = request.Content; 339 if (requestContent != null) 340 { 341 MediaTypeHeaderValue requestContentType = requestContent.Headers.ContentType; 342 if (requestContentType != null && TryMatchSupportedMediaType(requestContentType, out mediaTypeMatch)) 343 { 344 mediaTypeMatch.SetEncoding(characterEncodingMatch); 345 return new ResponseMediaTypeMatch( 346 mediaTypeMatch, 347 ResponseFormatterSelectionResult.MatchOnRequestContentType); 348 } 349 } 350 351 // No match at all. 352 // Pick the first supported media type and indicate we've matched only on type 353 MediaTypeHeaderValue mediaType = SupportedMediaTypes.FirstOrDefault(); 354 355 mediaTypeMatch = new MediaTypeMatch(mediaType); 356 mediaTypeMatch.SetEncoding(characterEncodingMatch); 357 return new ResponseMediaTypeMatch( 358 mediaTypeMatch, 359 ResponseFormatterSelectionResult.MatchOnCanWriteType); 360 } 361 362 /// <summary> 363 /// Determine the best character encoding for writing the response. First we look 364 /// for accept-charset headers and if not found then we try to match 365 /// any charset encoding in the request (in case of PUT, POST, etc.) 366 /// If no encoding is found then we use the default for the formatter. 367 /// </summary> 368 /// <returns>The <see cref="Encoding"/> determined to be the best match.</returns> SelectResponseCharacterEncoding(HttpRequestMessage request)369 internal Encoding SelectResponseCharacterEncoding(HttpRequestMessage request) 370 { 371 // Sort accept-charset headers in descending order based on q factor 372 IEnumerable<StringWithQualityHeaderValue> acceptCharsetValues = 373 request.Headers.AcceptCharset.OrderByDescending(m => m, StringWithQualityHeaderValueComparer.QualityComparer); 374 375 // Check for match based on accept-charset headers 376 foreach (StringWithQualityHeaderValue acceptCharset in acceptCharsetValues) 377 { 378 foreach (Encoding encoding in SupportedEncodings) 379 { 380 if (acceptCharset.Value.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase) || 381 acceptCharset.Value.Equals("*", StringComparison.OrdinalIgnoreCase)) 382 { 383 return encoding; 384 } 385 } 386 } 387 388 // Check for match based on any request entity body 389 return SelectCharacterEncoding(request.Content != null ? request.Content.Headers : null); 390 } 391 TryMatchSupportedMediaType(MediaTypeHeaderValue mediaType, out MediaTypeMatch mediaTypeMatch)392 internal bool TryMatchSupportedMediaType(MediaTypeHeaderValue mediaType, out MediaTypeMatch mediaTypeMatch) 393 { 394 Contract.Assert(mediaType != null); 395 396 foreach (MediaTypeHeaderValue supportedMediaType in SupportedMediaTypes) 397 { 398 if (supportedMediaType.IsSubsetOf(mediaType)) 399 { 400 // If the incoming media type had an associated quality factor, propagate it to the match 401 MediaTypeWithQualityHeaderValue mediaTypeWithQualityHeaderValue = mediaType as MediaTypeWithQualityHeaderValue; 402 double quality = mediaTypeWithQualityHeaderValue != null && mediaTypeWithQualityHeaderValue.Quality.HasValue 403 ? mediaTypeWithQualityHeaderValue.Quality.Value 404 : MediaTypeMatch.Match; 405 406 mediaTypeMatch = new MediaTypeMatch(supportedMediaType, quality); 407 return true; 408 } 409 } 410 411 mediaTypeMatch = null; 412 return false; 413 } 414 TryMatchSupportedMediaType(HttpRequestMessage request, out MediaTypeMatch mediaTypeMatch)415 internal bool TryMatchSupportedMediaType(HttpRequestMessage request, out MediaTypeMatch mediaTypeMatch) 416 { 417 Contract.Assert(request != null); 418 419 IEnumerable<MediaTypeWithQualityHeaderValue> acceptMediaTypeValues = SortByQFactor(request.Headers.Accept); 420 421 foreach (MediaTypeHeaderValue acceptMediaTypeValue in acceptMediaTypeValues) 422 { 423 if (TryMatchSupportedMediaType(acceptMediaTypeValue, out mediaTypeMatch)) 424 { 425 return true; 426 } 427 } 428 429 mediaTypeMatch = null; 430 return false; 431 } 432 SortByQFactor(HttpHeaderValueCollection<MediaTypeWithQualityHeaderValue> acceptHeaders)433 private static IEnumerable<MediaTypeWithQualityHeaderValue> SortByQFactor(HttpHeaderValueCollection<MediaTypeWithQualityHeaderValue> acceptHeaders) 434 { 435 if (acceptHeaders.Count > 1) 436 { 437 // Sort accept headers (if more than 1) in descending order based on q factor 438 // Use OrderBy() instead of Array.Sort() as it performs fewer comparisons. In this case the comparisons 439 // are quite expensive so OrderBy() performs better. 440 return acceptHeaders.OrderByDescending(m => m, MediaTypeWithQualityHeaderValueComparer.QualityComparer); 441 } 442 else 443 { 444 return acceptHeaders; 445 } 446 } 447 TryMatchMediaTypeMapping(HttpRequestMessage request, out MediaTypeMatch mediaTypeMatch)448 internal bool TryMatchMediaTypeMapping(HttpRequestMessage request, out MediaTypeMatch mediaTypeMatch) 449 { 450 Contract.Assert(request != null, "request cannot be null."); 451 452 foreach (MediaTypeMapping mapping in MediaTypeMappings) 453 { 454 // Collection<T> is not protected against null, so avoid them 455 double quality; 456 if (mapping != null && ((quality = mapping.TryMatchMediaType(request)) > 0.0)) 457 { 458 mediaTypeMatch = new MediaTypeMatch(mapping.MediaType, quality); 459 return true; 460 } 461 } 462 463 mediaTypeMatch = null; 464 return false; 465 } 466 467 /// <summary> 468 /// Sets the default headers for content that will be formatted using this formatter. This method 469 /// is called from the <see cref="ObjectContent"/> constructor. 470 /// This implementation sets the Content-Type header to the value of <paramref name="mediaType"/> if it is 471 /// not <c>null</c>. If it is <c>null</c> it sets the Content-Type to the default media type of this formatter. 472 /// If the Content-Type does not specify a charset it will set it using this formatters configured 473 /// <see cref="Encoding"/>. 474 /// </summary> 475 /// <remarks> 476 /// Subclasses can override this method to set content headers such as Content-Type etc. Subclasses should 477 /// call the base implementation. Subclasses should treat the passed in <paramref name="mediaType"/> (if not <c>null</c>) 478 /// as the authoritative media type and use that as the Content-Type. 479 /// </remarks> 480 /// <param name="type">The type of the object being serialized. See <see cref="ObjectContent"/>.</param> 481 /// <param name="headers">The content headers that should be configured.</param> 482 /// <param name="mediaType">The authoritative media type. Can be <c>null</c>.</param> SetDefaultContentHeaders(Type type, HttpContentHeaders headers, string mediaType)483 public virtual void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, string mediaType) 484 { 485 if (type == null) 486 { 487 throw new ArgumentNullException("type"); 488 } 489 if (headers == null) 490 { 491 throw new ArgumentNullException("headers"); 492 } 493 494 if (!String.IsNullOrEmpty(mediaType)) 495 { 496 var parsedMediaType = MediaTypeHeaderValue.Parse(mediaType); 497 headers.ContentType = parsedMediaType; 498 } 499 500 // If content type is not set then set it based on supported media types. 501 if (headers.ContentType == null) 502 { 503 MediaTypeHeaderValue defaultMediaType = SupportedMediaTypes.FirstOrDefault(); 504 if (defaultMediaType != null) 505 { 506 headers.ContentType = defaultMediaType.Clone(); 507 } 508 } 509 510 // If content type charset parameter is not set then set it based on the supported encodings. 511 if (headers.ContentType != null && headers.ContentType.CharSet == null) 512 { 513 Encoding defaultEncoding = SupportedEncodings.FirstOrDefault(); 514 if (defaultEncoding != null) 515 { 516 headers.ContentType.CharSet = defaultEncoding.WebName; 517 } 518 } 519 } 520 521 /// <summary> 522 /// Returns a specialized instance of the <see cref="MediaTypeFormatter"/> that can handle formatting a response for the given 523 /// parameters. This method is called by <see cref="DefaultContentNegotiator"/> after a formatter has been selected through content 524 /// negotiation. 525 /// </summary> 526 /// <remarks> 527 /// The default implementation returns <c>this</c> instance. Derived classes can choose to return a new instance if 528 /// they need to close over any of the parameters. 529 /// </remarks> 530 /// <param name="type">The type being serialized.</param> 531 /// <param name="request">The request.</param> 532 /// <param name="mediaType">The media type chosen for the serialization. Can be <c>null</c>.</param> 533 /// <returns>An instance that can format a response to the given <paramref name="request"/>.</returns> GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)534 public virtual MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType) 535 { 536 if (type == null) 537 { 538 throw new ArgumentNullException("type"); 539 } 540 if (request == null) 541 { 542 throw new ArgumentNullException("request"); 543 } 544 545 return this; 546 } 547 548 /// <summary> 549 /// Determines whether this <see cref="MediaTypeFormatter"/> can deserialize 550 /// an object of the specified type. 551 /// </summary> 552 /// <remarks> 553 /// Derived classes must implement this method and indicate if a type can or cannot be deserialized. 554 /// </remarks> 555 /// <param name="type">The type of object that will be deserialized.</param> 556 /// <returns><c>true</c> if this <see cref="MediaTypeFormatter"/> can deserialize an object of that type; otherwise <c>false</c>.</returns> CanReadType(Type type)557 public abstract bool CanReadType(Type type); 558 559 /// <summary> 560 /// Determines whether this <see cref="MediaTypeFormatter"/> can serialize 561 /// an object of the specified type. 562 /// </summary> 563 /// <remarks> 564 /// Derived classes must implement this method and indicate if a type can or cannot be serialized. 565 /// </remarks> 566 /// <param name="type">The type of object that will be serialized.</param> 567 /// <returns><c>true</c> if this <see cref="MediaTypeFormatter"/> can serialize an object of that type; otherwise <c>false</c>.</returns> CanWriteType(Type type)568 public abstract bool CanWriteType(Type type); 569 GetOrAddDelegatingType(Type type)570 private static Type GetOrAddDelegatingType(Type type) 571 { 572 return _delegatingEnumerableCache.GetOrAdd( 573 type, 574 (typeToRemap) => 575 { 576 // The current method is called by methods that already checked the type for is not null, is generic and is or implements IEnumerable<T> 577 // This retrieves the T type of the IEnumerable<T> interface. 578 Type elementType; 579 if (typeToRemap.GetGenericTypeDefinition().Equals(FormattingUtilities.EnumerableInterfaceGenericType)) 580 { 581 elementType = typeToRemap.GetGenericArguments()[0]; 582 } 583 else 584 { 585 elementType = typeToRemap.GetInterface(FormattingUtilities.EnumerableInterfaceGenericType.FullName).GetGenericArguments()[0]; 586 } 587 588 Type delegatingType = FormattingUtilities.DelegatingEnumerableGenericType.MakeGenericType(elementType); 589 ConstructorInfo delegatingConstructor = delegatingType.GetConstructor(new Type[] { FormattingUtilities.EnumerableInterfaceGenericType.MakeGenericType(elementType) }); 590 _delegatingEnumerableConstructorCache.TryAdd(delegatingType, delegatingConstructor); 591 592 return delegatingType; 593 }); 594 } 595 596 /// <summary> 597 /// Gets the default value for the specified type. 598 /// </summary> GetDefaultValueForType(Type type)599 protected internal static object GetDefaultValueForType(Type type) 600 { 601 if (type == null) 602 { 603 throw new ArgumentNullException("type"); 604 } 605 606 if (type.IsValueType) 607 { 608 return Activator.CreateInstance(type); 609 } 610 return null; 611 } 612 613 /// <summary> 614 /// Collection class that validates it contains only <see cref="MediaTypeHeaderValue"/> instances 615 /// that are not null and not media ranges. 616 /// </summary> 617 internal class MediaTypeHeaderValueCollection : Collection<MediaTypeHeaderValue> 618 { 619 private static readonly Type _mediaTypeHeaderValueType = typeof(MediaTypeHeaderValue); 620 621 /// <summary> 622 /// Inserts the <paramref name="item"/> into the collection at the specified <paramref name="index"/>. 623 /// </summary> 624 /// <param name="index">The zero-based index at which item should be inserted.</param> 625 /// <param name="item">The object to insert. It cannot be <c>null</c>.</param> InsertItem(int index, MediaTypeHeaderValue item)626 protected override void InsertItem(int index, MediaTypeHeaderValue item) 627 { 628 ValidateMediaType(item); 629 base.InsertItem(index, item); 630 } 631 632 /// <summary> 633 /// Replaces the element at the specified <paramref name="index"/>. 634 /// </summary> 635 /// <param name="index">The zero-based index of the item that should be replaced.</param> 636 /// <param name="item">The new value for the element at the specified index. It cannot be <c>null</c>.</param> SetItem(int index, MediaTypeHeaderValue item)637 protected override void SetItem(int index, MediaTypeHeaderValue item) 638 { 639 ValidateMediaType(item); 640 base.SetItem(index, item); 641 } 642 ValidateMediaType(MediaTypeHeaderValue item)643 private static void ValidateMediaType(MediaTypeHeaderValue item) 644 { 645 if (item == null) 646 { 647 throw new ArgumentNullException("item"); 648 } 649 650 ParsedMediaTypeHeaderValue parsedMediaType = new ParsedMediaTypeHeaderValue(item); 651 if (parsedMediaType.IsAllMediaRange || parsedMediaType.IsSubTypeMediaRange) 652 { 653 throw new ArgumentException( 654 RS.Format(Properties.Resources.CannotUseMediaRangeForSupportedMediaType, _mediaTypeHeaderValueType.Name, item.MediaType), 655 "item"); 656 } 657 } 658 } 659 } 660 } 661