PageRenderTime 58ms CodeModel.GetById 15ms app.highlight 37ms RepoModel.GetById 2ms app.codeStats 0ms

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

#
C# | 619 lines | 414 code | 84 blank | 121 comment | 109 complexity | 4aa6771ffe6a509e4b160df87c0f50ba MD5 | raw file
  1// <copyright>
  2//   Copyright (c) Microsoft Corporation.  All rights reserved.
  3// </copyright>
  4
  5namespace System.Net.Http.Formatting
  6{
  7    using System;
  8    using System.Collections.Concurrent;
  9    using System.Collections.Generic;
 10    using System.Collections.ObjectModel;
 11    using System.Diagnostics.Contracts;
 12    using System.IO;
 13    using System.Linq;
 14    using System.Net;
 15    using System.Net.Http;
 16    using System.Net.Http.Headers;
 17    using System.Reflection;
 18    using System.Text;
 19    using System.Threading.Tasks;
 20
 21    /// <summary>
 22    /// Base class to handle serializing and deserializing strongly-typed objects using <see cref="ObjectContent"/>.
 23    /// </summary>
 24    public abstract class MediaTypeFormatter
 25    {
 26        private static ConcurrentDictionary<Type, Type> delegatingEnumerableCache = new ConcurrentDictionary<Type, Type>();
 27        private static ConcurrentDictionary<Type, ConstructorInfo> delegatingEnumerableConstructorCache = new ConcurrentDictionary<Type, ConstructorInfo>();
 28
 29        /// <summary>
 30        /// Initializes a new instance of the <see cref="MediaTypeFormatter"/> class.
 31        /// </summary>
 32        protected MediaTypeFormatter()
 33        {
 34            this.SupportedMediaTypes = new MediaTypeHeaderValueCollection();
 35            this.MediaTypeMappings = new Collection<MediaTypeMapping>();
 36        }
 37
 38        /// <summary>
 39        /// Gets the mutable collection of <see cref="MediaTypeHeaderValue"/> elements supported by
 40        /// this <see cref="MediaTypeFormatter"/> instance.
 41        /// </summary>
 42        public Collection<MediaTypeHeaderValue> SupportedMediaTypes { get; private set; }
 43
 44        /// <summary>
 45        /// Gets the mutable collection of <see cref="MediaTypeMapping"/> elements used
 46        /// by this <see cref="MediaTypeFormatter"/> instance to determine the 
 47        /// <see cref="MediaTypeHeaderValue"/> of requests or responses.
 48        /// </summary>
 49        public Collection<MediaTypeMapping> MediaTypeMappings { get; private set; }
 50
 51        /// <summary>
 52        /// Gets a value indicating whether this instance is modified from the default settings.
 53        /// </summary>
 54        /// <value>
 55        /// <c>true</c> if this instance is modified; otherwise, <c>false</c>.
 56        /// </value>
 57        internal virtual bool IsModified
 58        {
 59            get
 60            {
 61                return false;
 62            }
 63        }
 64
 65        /// <summary>
 66        /// Gets or sets the <see cref="Encoding"/> to use when reading and writing data.
 67        /// </summary>
 68        /// <value>
 69        /// The <see cref="Encoding"/> to use when reading and writing data.
 70        /// </value>
 71        protected Encoding Encoding { get; set; }
 72
 73        // If the type is IEnumerable<T> or an interface type implementing it, a DelegatingEnumerable<T> type is cached for use at serialization time.
 74        internal static bool TryGetDelegatingTypeForIEnumerableGenericOrSame(ref Type type)
 75        {
 76            if (type != null
 77             && type.IsInterface
 78             && type.IsGenericType
 79             && (type.GetInterface(FormattingUtilities.EnumerableInterfaceGenericType.FullName) != null
 80                 || type.GetGenericTypeDefinition().Equals(FormattingUtilities.EnumerableInterfaceGenericType)))
 81            {
 82                type = GetOrAddDelegatingType(type);
 83                return true;
 84            }
 85
 86            return false;
 87        }
 88
 89        // If the type is IQueryable<T> or an interface type implementing it, a DelegatingEnumerable<T> type is cached for use at serialization time.
 90        internal static bool TryGetDelegatingTypeForIQueryableGenericOrSame(ref Type type)
 91        {
 92            if (type != null
 93             && type.IsInterface
 94             && type.IsGenericType
 95             && (type.GetInterface(FormattingUtilities.QueryableInterfaceGenericType.FullName) != null
 96                 || type.GetGenericTypeDefinition().Equals(FormattingUtilities.QueryableInterfaceGenericType)))
 97            {
 98                type = GetOrAddDelegatingType(type);
 99                return true;
100            }
101
102            return false;
103        }
104
105        internal static ConstructorInfo GetTypeRemappingConstructor(Type type)
106        {
107            ConstructorInfo constructorInfo = null;
108            delegatingEnumerableConstructorCache.TryGetValue(type, out constructorInfo);
109            return constructorInfo;
110        }
111
112        internal bool CanReadAs(Type type, HttpContent content)
113        {
114            if (type == null)
115            {
116                throw new ArgumentNullException("type");
117            }
118
119            if (content == null)
120            {
121                throw new ArgumentNullException("content");
122            }
123
124            if (!this.CanReadType(type))
125            {
126                return false;
127            }
128
129            // Content type must be set and must be supported
130            MediaTypeHeaderValue mediaType = content.Headers.ContentType;
131            MediaTypeMatch mediaTypeMatch = null;
132            return mediaType == null ? false : this.TryMatchSupportedMediaType(mediaType, out mediaTypeMatch);
133        }
134
135        internal bool CanWriteAs(Type type, HttpContent content, out MediaTypeHeaderValue mediaType)
136        {
137            if (type == null)
138            {
139                throw new ArgumentNullException("type");
140            }
141
142            if (content == null)
143            {
144                throw new ArgumentNullException("content");
145            }
146
147            if (!this.CanWriteType(type))
148            {
149                mediaType = null;
150                return false;
151            }
152
153            // Content type must be set and must be supported
154            mediaType = content.Headers.ContentType;
155            MediaTypeMatch mediaTypeMatch = null;
156            return mediaType != null && this.TryMatchSupportedMediaType(mediaType, out mediaTypeMatch);
157        }
158
159        internal bool CanReadAs(Type type, HttpRequestMessage request)
160        {
161            if (type == null)
162            {
163                throw new ArgumentNullException("type");
164            }
165
166            if (request == null)
167            {
168                throw new ArgumentNullException("request");
169            }
170
171            if (!this.CanReadType(type))
172            {
173                return false;
174            }
175
176            // Content type must be set and must be supported
177            MediaTypeHeaderValue mediaType = request.Content.Headers.ContentType;
178            MediaTypeMatch mediaTypeMatch = null;
179            return mediaType != null && this.TryMatchSupportedMediaType(mediaType, out mediaTypeMatch);
180        }
181
182        internal bool CanWriteAs(Type type, HttpRequestMessage request, out MediaTypeHeaderValue mediaType)
183        {
184            if (type == null)
185            {
186                throw new ArgumentNullException("type");
187            }
188
189            if (request == null)
190            {
191                throw new ArgumentNullException("request");
192            }
193
194            mediaType = null;
195
196            if (!this.CanWriteType(type))
197            {
198                return false;
199            }
200
201            mediaType = request.Content.Headers.ContentType;
202            MediaTypeMatch mediaTypeMatch = null;
203            if (mediaType != null)
204            {
205                if (this.TryMatchSupportedMediaType(mediaType, out mediaTypeMatch))
206                {
207                    return true;
208                }
209            }
210            else
211            {
212                if (this.TryMatchMediaTypeMapping(request, out mediaTypeMatch))
213                {
214                    return true;
215                }
216            }
217
218            mediaType = null;
219            return false;
220        }
221
222        internal bool CanReadAs(Type type, HttpResponseMessage response)
223        {
224            if (type == null)
225            {
226                throw new ArgumentNullException("type");
227            }
228
229            if (response == null)
230            {
231                throw new ArgumentNullException("response");
232            }
233
234            if (!this.CanReadType(type))
235            {
236                return false;
237            }
238
239            // Content type must be set and must be supported
240            MediaTypeHeaderValue mediaType = response.Content.Headers.ContentType;
241            MediaTypeMatch mediaTypeMatch = null;
242            return mediaType != null && this.TryMatchSupportedMediaType(mediaType, out mediaTypeMatch);
243        }
244
245        internal ResponseMediaTypeMatch SelectResponseMediaType(Type type, HttpResponseMessage response)
246        {
247            if (type == null)
248            {
249                throw new ArgumentNullException("type");
250            }
251
252            if (response == null)
253            {
254                throw new ArgumentNullException("response");
255            }
256
257            MediaTypeHeaderValue mediaType = null;
258            MediaTypeMatch mediaTypeMatch = null;
259
260            if (!this.CanWriteType(type))
261            {
262                return null;
263            }
264
265            mediaType = response.Content == null ? null : response.Content.Headers.ContentType;
266            if (mediaType != null && this.TryMatchSupportedMediaType(mediaType, out mediaTypeMatch))
267            {
268                return new ResponseMediaTypeMatch(
269                            mediaTypeMatch, 
270                            ResponseFormatterSelectionResult.MatchOnResponseContentType);
271            }
272
273            HttpRequestMessage request = response.RequestMessage;
274            if (request != null)
275            {
276                IEnumerable<MediaTypeWithQualityHeaderValue> acceptHeaderMediaTypes = request.Headers.Accept.OrderBy((m) => m, MediaTypeHeaderValueComparer.Comparer);
277
278                if (this.TryMatchSupportedMediaType(acceptHeaderMediaTypes, out mediaTypeMatch))
279                {
280                    return new ResponseMediaTypeMatch(
281                                mediaTypeMatch, 
282                                ResponseFormatterSelectionResult.MatchOnRequestAcceptHeader);
283                }
284
285                if (this.TryMatchMediaTypeMapping(response, out mediaTypeMatch))
286                {
287                    return new ResponseMediaTypeMatch(
288                                mediaTypeMatch,
289                                ResponseFormatterSelectionResult.MatchOnRequestAcceptHeaderWithMediaTypeMapping);
290                }
291
292                HttpContent requestContent = request.Content;
293                if (requestContent != null)
294                {
295                    MediaTypeHeaderValue requestContentType = requestContent.Headers.ContentType;
296                    if (requestContentType != null && this.TryMatchSupportedMediaType(requestContentType, out mediaTypeMatch))
297                    {
298                        return new ResponseMediaTypeMatch(
299                                    mediaTypeMatch,
300                                    ResponseFormatterSelectionResult.MatchOnRequestContentType);
301                    }
302                }
303            }
304
305            mediaType = this.SupportedMediaTypes.FirstOrDefault();
306            if (mediaType != null && this.Encoding != null)
307            {
308                mediaType = (MediaTypeHeaderValue)((ICloneable)mediaType).Clone();
309                mediaType.CharSet = this.Encoding.WebName;
310            }
311
312            return new ResponseMediaTypeMatch(
313                            new MediaTypeMatch(mediaType), 
314                            ResponseFormatterSelectionResult.MatchOnCanWriteType);
315        }
316
317        internal Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders)
318        {
319            Contract.Assert(type != null, "type cannot be null.");
320            Contract.Assert(stream != null, "stream cannot be null.");
321            Contract.Assert(contentHeaders != null, "contentHeaders cannot be null.");
322
323            return this.OnReadFromStreamAsync(type, stream, contentHeaders);
324        }
325
326        internal Task WriteToStreamAsync(Type type, object instance, Stream stream, HttpContentHeaders contentHeaders, TransportContext context)
327        {
328            Contract.Assert(type != null, "type cannot be null.");
329            Contract.Assert(stream != null, "stream cannot be null.");
330            Contract.Assert(contentHeaders != null, "contentHeaders cannot be null.");
331
332            return this.OnWriteToStreamAsync(type, instance, stream, contentHeaders, context);
333        }
334
335        internal object ReadFromStream(Type type, Stream stream, HttpContentHeaders contentHeaders)
336        {
337            Contract.Assert(type != null, "type cannot be null.");
338            Contract.Assert(stream != null, "stream cannot be null.");
339            Contract.Assert(contentHeaders != null, "contentHeaders cannot be null.");
340
341            // TODO: CSDMain 235646 Introduce new MediaTypeFormatter exception that should be thrown from MediaTypeFormatter.WriteToStream and MediaTypeFormatter.ReadFromStream
342            return this.OnReadFromStream(type, stream, contentHeaders);
343        }
344
345        internal void WriteToStream(Type type, object instance, Stream stream, HttpContentHeaders contentHeaders, TransportContext context)
346        {
347            Contract.Assert(type != null, "type cannot be null.");
348            Contract.Assert(stream != null, "stream cannot be null.");
349            Contract.Assert(contentHeaders != null, "contentHeaders cannot be null.");
350
351            // TODO: CSDMain 235646 Introduce new MediaTypeFormatter exception that should be thrown from MediaTypeFormatter.WriteToStream and MediaTypeFormatter.ReadFromStream
352            this.OnWriteToStream(type, instance, stream, contentHeaders, context);
353        }
354
355        internal bool TryMatchSupportedMediaType(MediaTypeHeaderValue mediaType, out MediaTypeMatch mediaTypeMatch)
356        {
357            Contract.Assert(mediaType != null, "mediaType cannot be null.");
358
359            foreach (MediaTypeHeaderValue supportedMediaType in this.SupportedMediaTypes)
360            {
361                if (MediaTypeHeaderValueEqualityComparer.EqualityComparer.Equals(supportedMediaType, mediaType))
362                {
363                    // If the incoming media type had an associated quality factor, propagate it to the match
364                    MediaTypeWithQualityHeaderValue mediaTypeWithQualityHeaderValue = mediaType as MediaTypeWithQualityHeaderValue;
365                    double quality = mediaTypeWithQualityHeaderValue != null && mediaTypeWithQualityHeaderValue.Quality.HasValue
366                                        ? mediaTypeWithQualityHeaderValue.Quality.Value
367                                        : MediaTypeMatch.Match;
368
369                    MediaTypeHeaderValue effectiveMediaType = supportedMediaType;
370                    if (this.Encoding != null)
371                    {
372                        effectiveMediaType = (MediaTypeHeaderValue)((ICloneable)supportedMediaType).Clone();
373                        effectiveMediaType.CharSet = this.Encoding.WebName;
374                    }
375
376                    mediaTypeMatch = new MediaTypeMatch(effectiveMediaType, quality);
377                    return true;
378                }
379            }
380
381            mediaTypeMatch = null;
382            return false;
383        }
384
385        internal bool TryMatchSupportedMediaType(IEnumerable<MediaTypeHeaderValue> mediaTypes, out MediaTypeMatch mediaTypeMatch)
386        {
387            Contract.Assert(mediaTypes != null, "mediaTypes cannot be null.");
388            foreach (MediaTypeHeaderValue mediaType in mediaTypes)
389            {
390                if (this.TryMatchSupportedMediaType(mediaType, out mediaTypeMatch))
391                {
392                    return true;
393                }
394            }
395
396            mediaTypeMatch = null;
397            return false;
398        }
399
400        internal bool TryMatchMediaTypeMapping(HttpRequestMessage request, out MediaTypeMatch mediaTypeMatch)
401        {
402            Contract.Assert(request != null, "request cannot be null.");
403
404            foreach (MediaTypeMapping mapping in this.MediaTypeMappings)
405            {
406                // Collection<T> is not protected against null, so avoid them
407                double quality;
408                if (mapping != null && ((quality = mapping.TryMatchMediaType(request)) > 0.0))
409                {
410                    mediaTypeMatch = new MediaTypeMatch(mapping.MediaType, quality);
411                    return true;
412                }
413            }
414
415            mediaTypeMatch = null;
416            return false;
417        }
418
419        internal bool TryMatchMediaTypeMapping(HttpResponseMessage response, out MediaTypeMatch mediaTypeMatch)
420        {
421            Contract.Assert(response != null, "response cannot be null.");
422
423            foreach (MediaTypeMapping mapping in this.MediaTypeMappings)
424            {
425                // Collection<T> is not protected against null, so avoid them
426                double quality;
427                if (mapping != null && ((quality = mapping.TryMatchMediaType(response)) > 0.0))
428                {
429                    mediaTypeMatch = new MediaTypeMatch(mapping.MediaType, quality);
430                    return true;
431                }
432            }
433
434            mediaTypeMatch = null;
435            return false;
436        }
437
438        internal IEnumerable<KeyValuePair<string, string>> GetResponseHeaders(Type objectType, string mediaType, HttpResponseMessage responseMessage)
439        {
440            return this.OnGetResponseHeaders(objectType, mediaType, responseMessage);
441        }
442
443        /// <summary>
444        /// Called from <see cref="GetResponseHeaders"/> to retrieve the response headers.
445        /// </summary>
446        /// <param name="objectType">The type of the object.  See <see cref="ObjectContent"/>.</param>
447        /// <param name="mediaType">The media type.</param>
448        /// <param name="responseMessage">The <see cref="HttpResponseMessage"/>.</param>
449        /// <returns>The collection of response header key value pairs.</returns>
450        protected virtual IEnumerable<KeyValuePair<string, string>> OnGetResponseHeaders(Type objectType, string mediaType, HttpResponseMessage responseMessage)
451        {
452            return null;
453        }
454
455        /// <summary>
456        /// Determines whether this <see cref="MediaTypeFormatter"/> can deserialize
457        /// an object of the specified type.
458        /// </summary>
459        /// <remarks>
460        /// The base class unconditionally returns <c>true</c>.  Derived classes must
461        /// override this to exclude types they cannot deserialize.
462        /// </remarks>
463        /// <param name="type">The type of object that will be deserialized.</param>
464        /// <returns><c>true</c> if this <see cref="MediaTypeFormatter"/> can deserialize an object of that type; otherwise <c>false</c>.</returns>
465        protected virtual bool CanReadType(Type type)
466        {
467            if (type == null)
468            {
469                throw new ArgumentNullException("type");
470            }
471
472            return true;
473        }
474
475        /// <summary>
476        /// Determines whether this <see cref="MediaTypeFormatter"/> can serialize
477        /// an object of the specified type.
478        /// </summary>
479        /// <remarks>
480        /// The base class unconditionally returns <c>true</c>.  Derived classes must
481        /// override this to exclude types they cannot serialize.
482        /// </remarks>
483        /// <param name="type">The type of object that will be serialized.</param>
484        /// <returns><c>true</c> if this <see cref="MediaTypeFormatter"/> can serialize an object of that type; otherwise <c>false</c>.</returns>
485        protected virtual bool CanWriteType(Type type)
486        {
487            if (type == null)
488            {
489                throw new ArgumentNullException("type");
490            }
491
492            return true;
493        }
494
495        /// <summary>
496        /// Called to read an object from the <paramref name="stream"/> asynchronously.
497        /// Derived classes may override this to do custom deserialization.
498        /// </summary>
499        /// <param name="type">The type of the object to read.</param>
500        /// <param name="stream">The <see cref="Stream"/> from which to read.</param>
501        /// <param name="contentHeaders">The content headers from the respective request or response.</param>
502        /// <returns>A <see cref="Task"/> that will yield an object instance when it completes.</returns>
503        protected virtual Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders)
504        {
505            // Base implementation provides only a Task wrapper over the synchronous operation.
506            // More intelligent derived formatters should override.
507            return Task.Factory.StartNew<object>(() => this.OnReadFromStream(type, stream, contentHeaders));
508        }
509
510        /// <summary>
511        /// Called to write an object to the <paramref name="stream"/> asynchronously.
512        /// </summary>
513        /// <param name="type">The type of object to write.</param>
514        /// <param name="value">The object instance to write.</param>
515        /// <param name="stream">The <see cref="Stream"/> to which to write.</param>
516        /// <param name="contentHeaders">The content headers from the respective request or response.</param>
517        /// <param name="context">The <see cref="TransportContext"/>.</param>
518        /// <returns>A <see cref="Task"/> that will write the object to the stream asynchronously.</returns>
519        protected virtual Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext context)
520        {
521            // Base implementation provides only a Task wrapper over the synchronous operation.
522            // More intelligent derived formatters should override.
523            return Task.Factory.StartNew(() => this.OnWriteToStream(type, value, stream, contentHeaders, context));
524        }
525
526        /// <summary>
527        /// Called to read an object from the <paramref name="stream"/>.
528        /// Derived classes may override this to do custom deserialization.
529        /// </summary>
530        /// <param name="type">The type of the object to read.</param>
531        /// <param name="stream">The <see cref="Stream"/> from which to read.</param>
532        /// <param name="contentHeaders">The content headers from the respective request or response.</param>
533        /// <returns>The object instance read from the <paramref name="stream"/>.</returns>
534        protected abstract object OnReadFromStream(Type type, Stream stream, HttpContentHeaders contentHeaders);
535
536        /// <summary>
537        /// Called to write an object to the <paramref name="stream"/>.
538        /// </summary>
539        /// <param name="type">The type of object to write.</param>
540        /// <param name="value">The object instance to write.</param>
541        /// <param name="stream">The <see cref="Stream"/> to which to write.</param>
542        /// <param name="contentHeaders">The content headers from the respective request or response.</param>
543        /// <param name="context">The <see cref="TransportContext"/>.</param>
544        protected abstract void OnWriteToStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext context);
545
546        private static Type GetOrAddDelegatingType(Type type)
547        {
548            return delegatingEnumerableCache.GetOrAdd(
549                type,
550                (typeToRemap) =>
551                {
552                    // The current method is called by methods that already checked the type for is not null, is generic and is or implements IEnumerable<T>
553                    // This retrieves the T type of the IEnumerable<T> interface.
554                    Type elementType;
555                    if (typeToRemap.GetGenericTypeDefinition().Equals(FormattingUtilities.EnumerableInterfaceGenericType))
556                    {
557                        elementType = typeToRemap.GetGenericArguments()[0];
558                    }
559                    else
560                    {
561                        elementType = typeToRemap.GetInterface(FormattingUtilities.EnumerableInterfaceGenericType.FullName).GetGenericArguments()[0];
562                    }
563
564                    Type delegatingType = FormattingUtilities.DelegatingEnumerableGenericType.MakeGenericType(elementType);
565                    ConstructorInfo delegatingConstructor = delegatingType.GetConstructor(new Type[] { FormattingUtilities.EnumerableInterfaceGenericType.MakeGenericType(elementType) });
566                    delegatingEnumerableConstructorCache.TryAdd(delegatingType, delegatingConstructor);
567
568                    return delegatingType;
569                });
570        }
571
572        /// <summary>
573        /// Collection class that validates it contains only <see cref="MediaTypeHeaderValue"/> instances
574        /// that are not null and not media ranges.
575        /// </summary>
576        internal class MediaTypeHeaderValueCollection : Collection<MediaTypeHeaderValue>
577        {
578            private static readonly Type mediaTypeHeaderValueType = typeof(MediaTypeHeaderValue);
579
580            /// <summary>
581            /// Inserts the <paramref name="item"/> into the collection at the specified <paramref name="index"/>.
582            /// </summary>
583            /// <param name="index">The zero-based index at which item should be inserted.</param>
584            /// <param name="item">The object to insert. It cannot be <c>null</c>.</param>
585            protected override void InsertItem(int index, MediaTypeHeaderValue item)
586            {
587                ValidateMediaType(item);
588                base.InsertItem(index, item);
589            }
590
591            /// <summary>
592            /// Replaces the element at the specified <paramref name="index"/>.
593            /// </summary>
594            /// <param name="index">The zero-based index of the item that should be replaced.</param>
595            /// <param name="item">The new value for the element at the specified index.  It cannot be <c>null</c>.</param>
596            protected override void SetItem(int index, MediaTypeHeaderValue item)
597            {
598                ValidateMediaType(item);
599                base.SetItem(index, item);
600            }
601
602            private static void ValidateMediaType(MediaTypeHeaderValue item)
603            {
604                if (item == null)
605                {
606                    throw new ArgumentNullException("item");
607                }
608
609                ParsedMediaTypeHeaderValue parsedMediaType = new ParsedMediaTypeHeaderValue(item);
610                if (parsedMediaType.IsAllMediaRange || parsedMediaType.IsSubTypeMediaRange)
611                {
612                    throw new ArgumentException(
613                            SR.CannotUseMediaRangeForSupportedMediaType(mediaTypeHeaderValueType.Name, item.MediaType),
614                            "item");
615                }
616            }
617        }
618    }
619}