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