// 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);
}
}
}
}