PageRenderTime 48ms CodeModel.GetById 3ms app.highlight 37ms RepoModel.GetById 1ms app.codeStats 1ms

/WCFWebApi/src/Microsoft.ApplicationServer.Http/Microsoft/ApplicationServer/Http/Dispatcher/ResponseContentHandler.cs

#
C# | 457 lines | 276 code | 39 blank | 142 comment | 56 complexity | 900ca661fb86c4f2d7ac0f74ad0884e3 MD5 | raw file
  1// <copyright>
  2//   Copyright (c) Microsoft Corporation.  All rights reserved.
  3// </copyright>
  4
  5namespace Microsoft.ApplicationServer.Http.Dispatcher
  6{
  7    using System;
  8    using System.Collections.Generic;
  9    using System.Diagnostics.CodeAnalysis;
 10    using System.Net;
 11    using System.Net.Http;
 12    using System.Net.Http.Formatting;
 13    using System.Reflection;
 14    using Microsoft.ApplicationServer.Http.Description;
 15    using Microsoft.Server.Common;
 16
 17    /// <summary>
 18    /// A <see cref="HttpOperationHandler"/> that takes in a instance of some type
 19    /// and returns an <see cref="HttpResponseMessage"/> instance of that same type.
 20    /// </summary>
 21    public class ResponseContentHandler : HttpOperationHandler
 22    {
 23        private static readonly HttpResponseMessageConverter simpleHttpResponseMessageConverter = new SimpleHttpResponseMessageConverter();
 24        private static readonly HttpResponseMessageConverter httpContentMessageConverter = new HttpContentMessageConverter();
 25        private static readonly HttpResponseMessageConverter voidHttpResponseMessageConverter = new VoidHttpResponseMessageConverter();
 26        private static readonly Type httpResponseMessageConverterGenericType = typeof(HttpResponseMessageConverter<>);
 27        private static readonly Type responseContentHandlerType = typeof(ResponseContentHandler);
 28
 29        private HttpParameter outputParameter;
 30        private HttpParameter inputParameter;
 31        private HttpResponseMessageConverter responseMessageConverter;
 32        private bool isVoidResponseType;
 33
 34        /// <summary>
 35        /// Initializes a new instance of a <see cref="ResponseContentHandler"/> with the
 36        /// given <paramref name="responseContentParameter"/> and <paramref name="formatters"/>.
 37        /// </summary>
 38        /// <param name="responseContentParameter">The <see cref="HttpParameter"/> for the content of the response.</param>
 39        /// <param name="formatters">The collection of <see cref="MediaTypeFormatter"/> instances to use for deserializing the response content.</param>
 40        public ResponseContentHandler(HttpParameter responseContentParameter, IEnumerable<MediaTypeFormatter> formatters)
 41        {
 42            if (formatters == null)
 43            {
 44                throw Fx.Exception.ArgumentNull("formatters");
 45            }
 46
 47            Type paramType = responseContentParameter == null ?
 48                TypeHelper.VoidType :
 49                responseContentParameter.ParameterType;
 50
 51            if (paramType == TypeHelper.VoidType)
 52            {
 53                this.isVoidResponseType = true;
 54                this.responseMessageConverter = voidHttpResponseMessageConverter;
 55                this.outputParameter = HttpParameter.ResponseMessage;
 56            }
 57            else
 58            {
 59                paramType = HttpTypeHelper.GetHttpResponseOrContentInnerTypeOrNull(paramType) ?? paramType;
 60
 61                if (HttpTypeHelper.IsHttpRequest(paramType))
 62                {
 63                    throw Fx.Exception.AsError(
 64                        new InvalidOperationException(
 65                            Http.SR.InvalidParameterForContentHandler(
 66                                HttpParameter.HttpParameterType.Name,
 67                                responseContentParameter.Name,
 68                                responseContentParameter.ParameterType.Name,
 69                                responseContentHandlerType.Name)));
 70                }
 71
 72                Type outputParameterType;
 73                if (HttpTypeHelper.IsHttp(paramType))
 74                {
 75                    // HttpContent and non-generic ObjectContent will be embedded into an HttpResponseMessage
 76                    outputParameterType = HttpTypeHelper.IsHttpContent(paramType)
 77                                            ? HttpTypeHelper.HttpResponseMessageType
 78                                            : paramType;
 79                }
 80                else
 81                {
 82                    outputParameterType = HttpTypeHelper.MakeHttpResponseMessageOf(paramType);
 83                }
 84
 85                this.outputParameter = new HttpParameter(responseContentParameter.Name, outputParameterType);
 86
 87                this.inputParameter = responseContentParameter;
 88
 89                if (HttpTypeHelper.IsHttpResponse(paramType))
 90                {
 91                    this.responseMessageConverter = simpleHttpResponseMessageConverter;
 92                }
 93                else if (HttpTypeHelper.IsHttpContent(paramType))
 94                {
 95                    this.responseMessageConverter = httpContentMessageConverter;
 96                }
 97                else
 98                {
 99                    Type closedConverterType = httpResponseMessageConverterGenericType.MakeGenericType(new Type[] { paramType });
100                    ConstructorInfo constructor = closedConverterType.GetConstructor(Type.EmptyTypes);
101                    this.responseMessageConverter = constructor.Invoke(null) as HttpResponseMessageConverter;
102                }
103            }
104
105            this.Formatters = new MediaTypeFormatterCollection(formatters);
106        }
107
108        /// <summary>
109        /// Gets the default <see cref="MediaTypeFormatter"/> instances to use for the <see cref="HttpResponseMessage"/>
110        /// instances created by the <see cref="ResponseContentHandler"/>.
111        /// </summary>
112        public MediaTypeFormatterCollection Formatters { get; private set; }
113
114        /// <summary>
115        /// Retrieves the collection of <see cref="HttpParameter"/> instances describing the
116        /// input values for this <see cref="ResponseContentHandler"/>.
117        /// </summary>
118        /// <remarks>
119        /// The <see cref="UriTemplateHandler"/> always returns a single input of
120        /// <see cref="HttpParameter.ResponseMessage"/>.
121        /// </remarks>
122        /// <returns>A collection that consists of just the <see cref="HttpParameter.ResponseMessage"/>.</returns>
123        protected override sealed IEnumerable<HttpParameter> OnGetInputParameters()
124        {
125            return this.inputParameter != null ?
126                new HttpParameter[] { HttpParameter.RequestMessage, this.inputParameter } :
127                new HttpParameter[] { HttpParameter.RequestMessage };
128        }
129
130        /// <summary>
131        /// Retrieves the collection of <see cref="HttpParameter"/>s describing the
132        /// output values of this <see cref="ResponseContentHandler"/>.
133        /// </summary>
134        /// <remarks>
135        /// The <see cref="UriTemplateHandler"/> always returns the <see cref="HttpParameter"/> 
136        /// instance that was passed into the constructor of the <see cref="ResponseContentHandler"/>.
137        /// </remarks>
138        /// <returns>
139        /// A collection that consists of just the <see cref="HttpParameter"/> 
140        /// instance that was passed into the constructor of the <see cref="ResponseContentHandler"/>.
141        /// </returns>
142        protected override sealed IEnumerable<HttpParameter> OnGetOutputParameters()
143        {
144            return new HttpParameter[] { this.outputParameter };
145        }
146
147        /// <summary>
148        /// Called to execute this <see cref="ResponseContentHandler"/>.
149        /// </summary>
150        /// <param name="input">
151        /// The input values to handle corresponding to the <see cref="HttpParameter"/> 
152        /// returned by <see cref="OnGetInputParameters"/>.
153        /// </param>
154        /// <returns>
155        /// The output values corresponding to the <see cref="HttpParameter"/> returned 
156        /// by <see cref="OnGetOutputParameters"/>.
157        /// </returns>
158        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "disposed later.")]
159        protected override sealed object[] OnHandle(object[] input)
160        {
161            Fx.Assert(input != null, "The 'input' parameter should not be null.");
162
163#if DEBUG
164            // input[0] is always the HttpRequestMessage
165            // input[1] exists only for non-void types and contains the raw output from the service operation.
166            if (this.isVoidResponseType)
167            {
168                Fx.Assert(input.Length == 1, "A void response should have exactly one element in the 'input' array");
169            }
170            else
171            {
172                Fx.Assert(input.Length == 2, "A non-void response should have exactly 2 elements in the 'input' array");
173            }
174#endif
175
176            HttpRequestMessage requestMessage = input[0] as HttpRequestMessage;
177            if (requestMessage == null)
178            {
179                throw Fx.Exception.ArgumentNull(HttpParameter.ResponseMessage.Name);
180            }
181
182            // Void type creates an empty HttpResponseMessage with OK status.
183            // Non-void types create an HttpResponseMessage<T> converted from the output of the operation.
184            HttpResponseMessage convertedResponseMessage = this.isVoidResponseType
185                                                            ? new HttpResponseMessage() { RequestMessage = requestMessage }
186                                                            : this.responseMessageConverter.Convert(requestMessage, input[1], this.Formatters);
187
188            return new object[] { convertedResponseMessage };
189        }
190
191        /// <summary>
192        /// Abstract base class used by the <see cref="ResponseContentHandler"/> to create 
193        /// <see cref="HttpResponseMessage"/> instances for a particular type 
194        /// without the performance hit of using reflection for every new instance.
195        /// </summary>
196        private abstract class HttpResponseMessageConverter
197        {
198            /// <summary>
199            /// Base abstract method that is overridden by the <see cref="HttpResponseMessageConverter"/>
200            /// to convert an <see cref="HttpResponseMessage"/> into an <see cref="HttpResponseMessage"/> of
201            /// a particular type.
202            /// </summary>
203            /// <param name="requestMessage">The <see cref="HttpRequestMessage"/> to attach to the converted <see cref="HttpResponseMessage"/>.</param>
204            /// <param name="responseContent">The content of the response message.</param>
205            /// <param name="formatters">The <see cref="MediaTypeFormatter"/> collection to use with the <see cref="ObjectContent"/>
206            /// used by the converted <see cref="HttpResponseMessageConverter"/>.</param>
207            /// <returns>
208            /// The converted <see cref="HttpResponseMessageConverter"/>.
209            /// </returns>
210            public abstract HttpResponseMessage Convert(HttpRequestMessage requestMessage, object responseContent, IEnumerable<MediaTypeFormatter> formatters);
211        }
212
213        /// <summary>
214        /// An <see cref="HttpResponseMessageConverter"/> that is only used when the response content is a non-generic <see cref="HttpResponseMessage"/>.
215        /// </summary>
216        private class SimpleHttpResponseMessageConverter : HttpResponseMessageConverter
217        {
218            /// <summary>
219            /// Overridden method that simply sets the <see cref="HttpResponseMessage.RequestMessage"/> on the <see cref="HttpResponseMessage"/> instance that
220            /// is already provided as the <paramref name="responseContent"/>.
221            /// </summary>
222            /// <param name="requestMessage">The <see cref="HttpRequestMessage"/> to attach to the <see cref="HttpResponseMessage"/>.</param>
223            /// <param name="responseContent">The response message.</param>
224            /// <param name="formatters">The <see cref="MediaTypeFormatter"/> collection to use with the <see cref="ObjectContent"/>
225            /// used by the converted <see cref="HttpResponseMessageConverter"/>.</param>
226            /// <returns>
227            /// The <see cref="HttpResponseMessage"/>.
228            /// </returns>
229            [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "caller becomes owner.")]
230            public override HttpResponseMessage Convert(HttpRequestMessage requestMessage, object responseContent, IEnumerable<MediaTypeFormatter> formatters)
231            {
232                HttpResponseMessage response = responseContent as HttpResponseMessage;
233                if (response == null)
234                {
235                    response = new HttpResponseMessage();
236                }
237                else
238                {
239                    ObjectContent objectContent = response.Content as ObjectContent;
240                    if (objectContent != null)
241                    {
242                        foreach (MediaTypeFormatter formatter in formatters)
243                        {
244                            objectContent.Formatters.Add(formatter);
245                        }
246                    }
247                }
248
249                response.RequestMessage = requestMessage;
250
251                return response;
252            }
253        }
254
255        /// <summary>
256        /// An <see cref="VoidHttpResponseMessageConverter"/> that is only used when the response content is a void.
257        /// </summary>
258        private class VoidHttpResponseMessageConverter : HttpResponseMessageConverter
259        {
260            /// <summary>
261            /// Overridden method that simply sets the HttpReponseMessage.RequestMessage on a new <see cref="HttpResponseMessage"/> instance.
262            /// </summary>
263            /// <param name="requestMessage">The <see cref="HttpRequestMessage"/> to attach to the <see cref="HttpResponseMessage"/>.</param>
264            /// <param name="responseContent">The value is always null.</param>
265            /// <param name="formatters">The value is not used.</param>
266            /// <returns>
267            /// The <see cref="HttpResponseMessage"/>.
268            /// </returns>
269            [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "caller becomes owner.")]
270            public override HttpResponseMessage Convert(HttpRequestMessage requestMessage, object responseContent, IEnumerable<MediaTypeFormatter> formatters)
271            {
272                HttpResponseMessage response = new HttpResponseMessage();
273                response.RequestMessage = requestMessage;
274
275                return response;
276            }
277        }
278
279        /// <summary>
280        /// An <see cref="HttpResponseMessageConverter"/> that is only used when the response content is a an <see cref="HttpContent"/>.
281        /// </summary>
282        private class HttpContentMessageConverter : HttpResponseMessageConverter
283        {
284            /// <summary>
285            /// Overriden method that simply creates a new the <see cref="HttpResponseMessage"/> instance and sets the <see cref="HttpContent"/> that
286            /// is already provided as the <paramref name="responseContent"/>.
287            /// </summary>
288            /// <param name="requestMessage">The <see cref="HttpRequestMessage"/> to attach to the <see cref="HttpResponseMessage"/>.</param>
289            /// <param name="responseContent">The response message content.</param>
290            /// <param name="formatters">The <see cref="MediaTypeFormatter"/> collection to use with the <see cref="ObjectContent"/>
291            /// used by the converted <see cref="HttpResponseMessageConverter"/>.</param>
292            /// <returns>
293            /// The <see cref="HttpResponseMessage"/>.
294            /// </returns>
295            [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "caller becomes owner.")]
296            public override HttpResponseMessage Convert(HttpRequestMessage requestMessage, object responseContent, IEnumerable<MediaTypeFormatter> formatters)
297            {
298                HttpResponseMessage response = new HttpResponseMessage();
299                response.Content = responseContent as HttpContent;
300                response.RequestMessage = requestMessage;
301
302                ObjectContent objectContent = response.Content as ObjectContent;
303                if (objectContent != null)
304                {
305                    foreach (MediaTypeFormatter formatter in formatters)
306                    {
307                        objectContent.Formatters.Add(formatter);
308                    }
309                }
310
311                return response;
312            }
313        }
314
315        /// <summary>
316        /// Generic version of the <see cref="HttpResponseMessageConverter"/> used by the 
317        /// <see cref="ResponseContentHandler"/> to create <see cref="HttpResponseMessage"/> instances 
318        /// for a particular <typeparamref name="T"/> without the performance hit of using reflection
319        /// for every new instance.
320        /// </summary>
321        /// <typeparam name="T">The type with which to create new <see cref="HttpResponseMessage"/> instances.</typeparam>
322        private class HttpResponseMessageConverter<T> : HttpResponseMessageConverter
323        {
324            /// <summary>
325            /// Converts an <see cref="HttpResponseMessage"/> into an <see cref="HttpResponseMessage"/> of
326            /// a particular type.
327            /// </summary>
328            /// <param name="requestMessage">The <see cref="HttpRequestMessage"/> to attach to the converted <see cref="HttpResponseMessage"/>.</param>
329            /// <param name="responseContent">The content of the response message.</param>
330            /// <param name="formatters">The <see cref="MediaTypeFormatter"/> collection to use with the <see cref="ObjectContent"/>
331            /// used by the converted <see cref="HttpResponseMessageConverter"/>.</param>
332            /// <returns>
333            /// The converted <see cref="HttpResponseMessage"/>.
334            /// </returns>
335            [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "caller becomes owner.")]
336            [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Casting is limited to two times due to the if statements.")]
337            public override HttpResponseMessage Convert(HttpRequestMessage requestMessage, object responseContent, IEnumerable<MediaTypeFormatter> formatters)
338            {
339                Fx.Assert(requestMessage != null, "The 'requestMessage' parameter should not be null.");
340
341                HttpResponseMessage<T> convertedResponseMessage = null;
342                if (responseContent == null || responseContent is T)
343                {
344                    convertedResponseMessage = new HttpResponseMessage<T>((T)responseContent, formatters);
345                    convertedResponseMessage.RequestMessage = requestMessage;
346                    return convertedResponseMessage;
347                }
348
349                if (responseContent is ObjectContent<T>)
350                {
351                    convertedResponseMessage = new HttpResponseMessage<T>(HttpStatusCode.OK);
352                    convertedResponseMessage.Content = (ObjectContent<T>)responseContent;
353                }
354                else
355                {
356                    convertedResponseMessage = (HttpResponseMessage<T>)responseContent;
357                }
358
359                ObjectContent<T> objectContent = convertedResponseMessage.Content;
360                if (objectContent != null)
361                {
362                    MediaTypeFormatterCollection hostFormatters = new MediaTypeFormatterCollection(formatters);
363                    MediaTypeFormatterCollection operationFormatters = objectContent.Formatters;
364
365                    // have to use a List since objectContent.Formatter getter will new up a collection with default formatters
366                    List<MediaTypeFormatter> list = new List<MediaTypeFormatter>();
367                    bool addHostXmlFormatter = false;
368                    bool addHostJsonValueFormatter = false;
369                    bool addHostJsonFormatter = false;
370                    bool addHostFormUrlEncodedFormatter = false;
371
372                    // we should start with the formatters configured from objectContent.Formatters
373                    foreach (MediaTypeFormatter formatter in operationFormatters)
374                    {
375                        if (operationFormatters.XmlFormatter == formatter &&
376                            !operationFormatters.XmlFormatter.IsModified)
377                        {
378                            // we should take the one from the host
379                            addHostXmlFormatter = true;
380                        }
381                        else if (operationFormatters.JsonValueFormatter == formatter &&
382                             !operationFormatters.JsonValueFormatter.IsModified)
383                        {
384                            // we should take the one from the host
385                            addHostJsonValueFormatter = true;
386                        }
387                        else if (operationFormatters.JsonFormatter == formatter &&
388                             !operationFormatters.JsonFormatter.IsModified)
389                        {
390                            // we should take the one from the host
391                            addHostJsonFormatter = true;
392                        }
393                        else if (operationFormatters.FormUrlEncodedFormatter == formatter &&
394                             !operationFormatters.FormUrlEncodedFormatter.IsModified)
395                        {
396                            // we should take the one from the host
397                            addHostFormUrlEncodedFormatter = true;
398                        }
399                        else
400                        {
401                            list.Add(formatter);
402                        }
403                    }
404
405                    // now we add some additional custom formatter set through host level
406                    foreach (MediaTypeFormatter formatter in hostFormatters)
407                    {
408                        if (hostFormatters.XmlFormatter == formatter)
409                        {
410                            // we only add the host level Xml formatter when it has not been added
411                            if (addHostXmlFormatter)
412                            {
413                                list.Add(formatter);
414                            }
415                        }
416                        else if (hostFormatters.JsonValueFormatter == formatter)
417                        {
418                            // we only add the host level JsonValue formatter when it has not been added
419                            if (addHostJsonValueFormatter)
420                            {
421                                list.Add(formatter);
422                            }
423                        }
424                        else if (hostFormatters.JsonFormatter == formatter)
425                        {
426                            // we only add the host level Json formatter when it has not been added
427                            if (addHostJsonFormatter)
428                            {
429                                list.Add(formatter);
430                            }
431                        }
432                        else if (hostFormatters.FormUrlEncodedFormatter == formatter)
433                        {
434                            // we only add the host level form URL encoded formatter when it has not been added
435                            if (addHostFormUrlEncodedFormatter)
436                            {
437                                list.Add(formatter);
438                            }
439                        }
440                        else
441                        {
442                            // adding other user defined formatter
443                            list.Add(formatter);
444                        }
445                    }
446
447                    // then we should update the objectContent's formatters with the merged list
448                    objectContent.SetFormatters(list);
449                }
450
451                convertedResponseMessage.RequestMessage = requestMessage;
452
453                return convertedResponseMessage;
454            }
455        }
456    }
457}