// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.Net.Http.Formatting; using System.Net.Http.Headers; using System.Web.Http; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.WebApiCompatShim; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using ShimResources = Microsoft.AspNetCore.Mvc.WebApiCompatShim.Resources; namespace System.Net.Http { /// /// Provides extension methods for the class. /// public static class HttpRequestMessageExtensions { #if !NETSTANDARD1_3 /// /// Helper method for creating an message with a "416 (Requested Range Not /// Satisfiable)" status code. This response can be used in combination with the /// to indicate that the requested range or /// ranges do not overlap with the current resource. The response contains a "Content-Range" header indicating /// the valid upper and lower bounds for requested ranges. /// /// The request. /// An instance, typically /// thrown by a instance. /// /// An 416 (Requested Range Not Satisfiable) error response with a Content-Range header indicating the valid /// range. /// public static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, InvalidByteRangeException invalidByteRangeException) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (invalidByteRangeException == null) { throw new ArgumentNullException(nameof(invalidByteRangeException)); } var rangeNotSatisfiableResponse = request.CreateErrorResponse( HttpStatusCode.RequestedRangeNotSatisfiable, invalidByteRangeException); rangeNotSatisfiableResponse.Content.Headers.ContentRange = invalidByteRangeException.ContentRange; return rangeNotSatisfiableResponse; } #endif /// /// Helper method that performs content negotiation and creates a /// representing an error with an instance of wrapping an /// with message . If no formatter is found, this method /// returns a response with status 406 NotAcceptable. /// /// /// This method requires that has been associated with an instance of /// . /// /// The request. /// The status code of the created response. /// The error message. /// /// An error response with error message and status code /// . /// public static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, HttpStatusCode statusCode, string message) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (message == null) { throw new ArgumentNullException(nameof(message)); } return request.CreateErrorResponse(statusCode, new HttpError(message)); } /// /// Helper method that performs content negotiation and creates a /// representing an error with an instance of wrapping an /// with error message for exception /// . If no formatter is found, this method returns a response with status 406 /// NotAcceptable. /// /// /// This method requires that has been associated with an instance of /// . /// /// The request. /// The status code of the created response. /// The error message. /// The exception. /// An error response for with error message /// and status code . public static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, HttpStatusCode statusCode, string message, Exception exception) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (message == null) { throw new ArgumentNullException(nameof(message)); } if (exception == null) { throw new ArgumentNullException(nameof(exception)); } var error = new HttpError(exception, includeErrorDetail: false) { Message = message }; return request.CreateErrorResponse(statusCode, error); } /// /// Helper method that performs content negotiation and creates a /// representing an error with an instance of wrapping an /// for exception . If no formatter is found, this method /// returns a response with status 406 NotAcceptable. /// /// /// This method requires that has been associated with an instance of /// . /// /// The request. /// The status code of the created response. /// The exception. /// /// An error response for with status code . /// public static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, HttpStatusCode statusCode, Exception exception) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (exception == null) { throw new ArgumentNullException(nameof(exception)); } return request.CreateErrorResponse(statusCode, new HttpError(exception, includeErrorDetail: false)); } /// /// Helper method that performs content negotiation and creates a /// representing an error with an instance of wrapping an /// for model state . If no formatter is found, this /// method returns a response with status 406 NotAcceptable. /// /// /// This method requires that has been associated with an instance of /// . /// /// The request. /// The status code of the created response. /// The model state. /// /// An error response for with status code . /// public static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, HttpStatusCode statusCode, ModelStateDictionary modelState) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (modelState == null) { throw new ArgumentNullException(nameof(modelState)); } return request.CreateErrorResponse(statusCode, new HttpError(modelState, includeErrorDetail: false)); } /// /// Helper method that performs content negotiation and creates a /// representing an error with an instance of wrapping /// as the content. If no formatter is found, this method returns a response with status 406 NotAcceptable. /// /// /// This method requires that has been associated with an instance of /// . /// /// The request. /// The status code of the created response. /// The error to wrap. /// /// An error response wrapping with status code . /// public static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, HttpStatusCode statusCode, HttpError error) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (error == null) { throw new ArgumentNullException(nameof(error)); } return request.CreateResponse(statusCode, error); } /// /// Helper method that performs content negotiation and creates a with an /// instance of as the content and /// as the status code if a formatter can be found. If no formatter is found, this method returns a response /// with status 406 NotAcceptable. /// /// /// This method requires that has been associated with an instance of /// . /// /// The type of the value. /// The request. /// The value to wrap. Can be null. /// /// A response wrapping with status code. /// public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, T value) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } return request.CreateResponse(HttpStatusCode.OK, value, formatters: null); } /// /// Helper method that performs content negotiation and creates a with an /// instance of as the content if a formatter can be found. If no formatter is /// found, this method returns a response with status 406 NotAcceptable. /// configuration. /// /// /// This method requires that has been associated with an instance of /// . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. /// A response wrapping with . public static HttpResponseMessage CreateResponse( this HttpRequestMessage request, HttpStatusCode statusCode, T value) { return request.CreateResponse(statusCode, value, formatters: null); } /// /// Helper method that performs content negotiation and creates a with an /// instance of as the content if a formatter can be found. If no formatter is /// found, this method returns a response with status 406 NotAcceptable. /// /// /// This method will get the instance associated with . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. /// The set of objects from which to choose. /// A response wrapping with . public static HttpResponseMessage CreateResponse( this HttpRequestMessage request, HttpStatusCode statusCode, T value, IEnumerable formatters) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var context = GetHttpContext(request); if (formatters == null) { // Get the default formatters from options var options = context.RequestServices.GetRequiredService>(); formatters = options.Value.Formatters; } var contentNegotiator = context.RequestServices.GetRequiredService(); var result = contentNegotiator.Negotiate(typeof(T), request, formatters); if (result?.Formatter == null) { // Return a 406 when we're actually performing conneg and it fails to find a formatter. return new HttpResponseMessage(HttpStatusCode.NotAcceptable) { RequestMessage = request }; } else { return request.CreateResponse(statusCode, value, result.Formatter, result.MediaType); } } /// /// Helper method that creates a with an /// instance containing the provided . The given is used /// to find an instance of . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. /// /// The media type used to look up an instance of . /// /// A response wrapping with . public static HttpResponseMessage CreateResponse( this HttpRequestMessage request, HttpStatusCode statusCode, T value, string mediaType) { return request.CreateResponse(statusCode, value, new MediaTypeHeaderValue(mediaType)); } /// /// Helper method that creates a with an /// instance containing the provided . The given is used /// to find an instance of . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. /// /// The media type used to look up an instance of . /// /// A response wrapping with . public static HttpResponseMessage CreateResponse( this HttpRequestMessage request, HttpStatusCode statusCode, T value, MediaTypeHeaderValue mediaType) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } if (mediaType == null) { throw new ArgumentNullException(nameof(mediaType)); } var context = GetHttpContext(request); // Get the default formatters from options var options = context.RequestServices.GetRequiredService>(); var formatters = options.Value.Formatters; var formatter = formatters.FindWriter(typeof(T), mediaType); if (formatter == null) { var message = ShimResources.FormatHttpRequestMessage_CouldNotFindMatchingFormatter( mediaType.ToString(), value.GetType()); throw new InvalidOperationException(message); } return request.CreateResponse(statusCode, value, formatter, mediaType); } /// /// Helper method that creates a with an /// instance containing the provided and the given . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. /// The formatter to use. /// A response wrapping with . public static HttpResponseMessage CreateResponse( this HttpRequestMessage request, HttpStatusCode statusCode, T value, MediaTypeFormatter formatter) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } if (formatter == null) { throw new ArgumentNullException(nameof(formatter)); } return request.CreateResponse(statusCode, value, formatter, (MediaTypeHeaderValue)null); } /// /// Helper method that creates a with an /// instance containing the provided and the given . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. /// The formatter to use. /// /// The media type override to set on the response's content. Can be null. /// /// A response wrapping with . public static HttpResponseMessage CreateResponse( this HttpRequestMessage request, HttpStatusCode statusCode, T value, MediaTypeFormatter formatter, string mediaType) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } if (formatter == null) { throw new ArgumentNullException(nameof(formatter)); } var mediaTypeHeader = mediaType != null ? new MediaTypeHeaderValue(mediaType) : null; return request.CreateResponse(statusCode, value, formatter, mediaTypeHeader); } /// /// Helper method that creates a with an /// instance containing the provided and the given . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. /// The formatter to use. /// /// The media type override to set on the response's content. Can be null. /// /// A response wrapping with . public static HttpResponseMessage CreateResponse( this HttpRequestMessage request, HttpStatusCode statusCode, T value, MediaTypeFormatter formatter, MediaTypeHeaderValue mediaType) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (formatter == null) { throw new ArgumentNullException(nameof(formatter)); } var response = new HttpResponseMessage(statusCode) { RequestMessage = request, }; response.Content = new ObjectContent(value, formatter, mediaType); return response; } private static HttpContext GetHttpContext(HttpRequestMessage request) { var context = request.GetProperty(nameof(HttpContext)); if (context == null) { var message = ShimResources.FormatHttpRequestMessage_MustHaveHttpContext( nameof(HttpRequestMessage), "HttpRequestMessageHttpContextExtensions.GetHttpRequestMessage"); throw new InvalidOperationException(message); } return context; } private static T GetProperty(this HttpRequestMessage request, string key) { object value; request.Properties.TryGetValue(key, out value); if (value is T) { return (T)value; } else { return default(T); } } } }