Fix issue #1282 - Add Request.CreateResponse extension methods
Adds the set of CreateResponse/CreateErrorResponse extension methods that return an HttpResponseMessage. For the overloads that perform content negotiation they will access the collection of MediaTypeFormatters through the shim options. Note that CreateResponse and friends use the OLD formatters. Also, HttpError and CreateErrorResponse assume ErrorDetail == false. Using the shim you will not get detailed error messages unless you construct the HttpError instance yourself.
This commit is contained in:
parent
5a83383179
commit
3968df90e4
|
|
@ -0,0 +1,265 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Schema;
|
||||
using System.Xml.Serialization;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using ShimResources = Microsoft.AspNet.Mvc.WebApiCompatShim.Resources;
|
||||
|
||||
namespace System.Web.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a serializable container for storing error information. This information is stored
|
||||
/// as key/value pairs. The dictionary keys to look up standard error information are available
|
||||
/// on the <see cref="HttpErrorKeys"/> type.
|
||||
/// </summary>
|
||||
[XmlRoot("Error")]
|
||||
public sealed class HttpError : Dictionary<string, object>, IXmlSerializable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpError"/> class.
|
||||
/// </summary>
|
||||
public HttpError()
|
||||
: base(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpError"/> class containing error message <paramref name="message"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message to associate with this instance.</param>
|
||||
public HttpError([NotNull] string message)
|
||||
: this()
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpError"/> class for <paramref name="exception"/>.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception to use for error information.</param>
|
||||
/// <param name="includeErrorDetail"><c>true</c> to include the exception information in the error; <c>false</c> otherwise</param>
|
||||
public HttpError([NotNull] Exception exception, bool includeErrorDetail)
|
||||
: this()
|
||||
{
|
||||
Message = ShimResources.HttpError_GenericError;
|
||||
|
||||
if (includeErrorDetail)
|
||||
{
|
||||
Add(HttpErrorKeys.ExceptionMessageKey, exception.Message);
|
||||
Add(HttpErrorKeys.ExceptionTypeKey, exception.GetType().FullName);
|
||||
Add(HttpErrorKeys.StackTraceKey, exception.StackTrace);
|
||||
if (exception.InnerException != null)
|
||||
{
|
||||
Add(HttpErrorKeys.InnerExceptionKey, new HttpError(exception.InnerException, includeErrorDetail));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpError"/> class for <paramref name="modelState"/>.
|
||||
/// </summary>
|
||||
/// <param name="modelState">The invalid model state to use for error information.</param>
|
||||
/// <param name="includeErrorDetail"><c>true</c> to include exception messages in the error; <c>false</c> otherwise</param>
|
||||
public HttpError([NotNull] ModelStateDictionary modelState, bool includeErrorDetail)
|
||||
: this()
|
||||
{
|
||||
if (modelState.IsValid)
|
||||
{
|
||||
throw new ArgumentException(ShimResources.HttpError_ValidModelState, nameof(modelState));
|
||||
}
|
||||
|
||||
Message = ShimResources.HttpError_BadRequest;
|
||||
|
||||
var modelStateError = new HttpError();
|
||||
foreach (KeyValuePair<string, ModelState> keyModelStatePair in modelState)
|
||||
{
|
||||
var key = keyModelStatePair.Key;
|
||||
var errors = keyModelStatePair.Value.Errors;
|
||||
if (errors != null && errors.Count > 0)
|
||||
{
|
||||
var errorMessages = errors.Select(error =>
|
||||
{
|
||||
if (includeErrorDetail && error.Exception != null)
|
||||
{
|
||||
return error.Exception.Message;
|
||||
}
|
||||
else
|
||||
{
|
||||
return
|
||||
string.IsNullOrEmpty(error.ErrorMessage) ?
|
||||
ShimResources.HttpError_GenericError :
|
||||
error.ErrorMessage;
|
||||
}
|
||||
}).ToArray();
|
||||
modelStateError.Add(key, errorMessages);
|
||||
}
|
||||
}
|
||||
|
||||
Add(HttpErrorKeys.ModelStateKey, modelStateError);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The high-level, user-visible message explaining the cause of the error. Information carried in this field
|
||||
/// should be considered public in that it will go over the wire regardless of the value of error detail policy.
|
||||
/// As a result care should be taken not to disclose sensitive information about the server or the application.
|
||||
/// </summary>
|
||||
public string Message
|
||||
{
|
||||
get { return GetPropertyValue<String>(HttpErrorKeys.MessageKey); }
|
||||
set { this[HttpErrorKeys.MessageKey] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ModelState"/> containing information about the errors that occurred during model binding.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The inclusion of <see cref="System.Exception"/> information carried in the <see cref="ModelState"/> is
|
||||
/// controlled by the error detail policy. All other information in the <see cref="ModelState"/>
|
||||
/// should be considered public in that it will go over the wire. As a result care should be taken not to
|
||||
/// disclose sensitive information about the server or the application.
|
||||
/// </remarks>
|
||||
public HttpError ModelState
|
||||
{
|
||||
get { return GetPropertyValue<HttpError>(HttpErrorKeys.ModelStateKey); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A detailed description of the error intended for the developer to understand exactly what failed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The inclusion of this field is controlled by the error detail policy. The
|
||||
/// field is expected to contain information about the server or the application that should not
|
||||
/// be disclosed broadly.
|
||||
/// </remarks>
|
||||
public string MessageDetail
|
||||
{
|
||||
get { return GetPropertyValue<String>(HttpErrorKeys.MessageDetailKey); }
|
||||
set { this[HttpErrorKeys.MessageDetailKey] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The message of the <see cref="System.Exception"/> if available.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The inclusion of this field is controlled by the error detail policy. The
|
||||
/// field is expected to contain information about the server or the application that should not
|
||||
/// be disclosed broadly.
|
||||
/// </remarks>
|
||||
public string ExceptionMessage
|
||||
{
|
||||
get { return GetPropertyValue<String>(HttpErrorKeys.ExceptionMessageKey); }
|
||||
set { this[HttpErrorKeys.ExceptionMessageKey] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of the <see cref="System.Exception"/> if available.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The inclusion of this field is controlled by the error detail policy. The
|
||||
/// field is expected to contain information about the server or the application that should not
|
||||
/// be disclosed broadly.
|
||||
/// </remarks>
|
||||
public string ExceptionType
|
||||
{
|
||||
get { return GetPropertyValue<String>(HttpErrorKeys.ExceptionTypeKey); }
|
||||
set { this[HttpErrorKeys.ExceptionTypeKey] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The stack trace information associated with this instance if available.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The inclusion of this field is controlled by the error detail policy. The
|
||||
/// field is expected to contain information about the server or the application that should not
|
||||
/// be disclosed broadly.
|
||||
/// </remarks>
|
||||
public string StackTrace
|
||||
{
|
||||
get { return GetPropertyValue<String>(HttpErrorKeys.StackTraceKey); }
|
||||
set { this[HttpErrorKeys.StackTraceKey] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The inner <see cref="System.Exception"/> associated with this instance if available.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The inclusion of this field is controlled by the error detail policy. The
|
||||
/// field is expected to contain information about the server or the application that should not
|
||||
/// be disclosed broadly.
|
||||
/// </remarks>
|
||||
public HttpError InnerException
|
||||
{
|
||||
get { return GetPropertyValue<HttpError>(HttpErrorKeys.InnerExceptionKey); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a particular property value from this error instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The type of the property.</typeparam>
|
||||
/// <param name="key">The name of the error property.</param>
|
||||
/// <returns>The value of the error property.</returns>
|
||||
public TValue GetPropertyValue<TValue>(string key)
|
||||
{
|
||||
object value;
|
||||
if (TryGetValue(key, out value) && value is TValue)
|
||||
{
|
||||
return (TValue)value;
|
||||
}
|
||||
|
||||
return default(TValue);
|
||||
}
|
||||
|
||||
XmlSchema IXmlSerializable.GetSchema()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
void IXmlSerializable.ReadXml(XmlReader reader)
|
||||
{
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return;
|
||||
}
|
||||
|
||||
reader.ReadStartElement();
|
||||
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
|
||||
{
|
||||
var key = XmlConvert.DecodeName(reader.LocalName);
|
||||
var value = reader.ReadInnerXml();
|
||||
|
||||
Add(key, value);
|
||||
reader.MoveToContent();
|
||||
}
|
||||
reader.ReadEndElement();
|
||||
}
|
||||
|
||||
void IXmlSerializable.WriteXml(XmlWriter writer)
|
||||
{
|
||||
foreach (var keyValuePair in this)
|
||||
{
|
||||
var key = keyValuePair.Key;
|
||||
var value = keyValuePair.Value;
|
||||
writer.WriteStartElement(XmlConvert.EncodeLocalName(key));
|
||||
if (value != null)
|
||||
{
|
||||
var innerError = value as HttpError;
|
||||
if (innerError == null)
|
||||
{
|
||||
writer.WriteValue(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
((IXmlSerializable)innerError).WriteXml(writer);
|
||||
}
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace System.Web.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides keys to look up error information stored in the <see cref="HttpError"/> dictionary.
|
||||
/// </summary>
|
||||
public static class HttpErrorKeys
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a key for the Message.
|
||||
/// </summary>
|
||||
public static readonly string MessageKey = "Message";
|
||||
|
||||
/// <summary>
|
||||
/// Provides a key for the MessageDetail.
|
||||
/// </summary>
|
||||
public static readonly string MessageDetailKey = "MessageDetail";
|
||||
|
||||
/// <summary>
|
||||
/// Provides a key for the ModelState.
|
||||
/// </summary>
|
||||
public static readonly string ModelStateKey = "ModelState";
|
||||
|
||||
/// <summary>
|
||||
/// Provides a key for the ExceptionMessage.
|
||||
/// </summary>
|
||||
public static readonly string ExceptionMessageKey = "ExceptionMessage";
|
||||
|
||||
/// <summary>
|
||||
/// Provides a key for the ExceptionType.
|
||||
/// </summary>
|
||||
public static readonly string ExceptionTypeKey = "ExceptionType";
|
||||
|
||||
/// <summary>
|
||||
/// Provides a key for the StackTrace.
|
||||
/// </summary>
|
||||
public static readonly string StackTraceKey = "StackTrace";
|
||||
|
||||
/// <summary>
|
||||
/// Provides a key for the InnerException.
|
||||
/// </summary>
|
||||
public static readonly string InnerExceptionKey = "InnerException";
|
||||
|
||||
/// <summary>
|
||||
/// Provides a key for the MessageLanguage.
|
||||
/// </summary>
|
||||
public static readonly string MessageLanguageKey = "MessageLanguage";
|
||||
|
||||
/// <summary>
|
||||
/// Provides a key for the ErrorCode.
|
||||
/// </summary>
|
||||
public static readonly string ErrorCodeKey = "ErrorCode";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,382 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Web.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
using ShimResources = Microsoft.AspNet.Mvc.WebApiCompatShim.Resources;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace System.Net.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for the <see cref="HttpRequestMessage"/> class.
|
||||
/// </summary>
|
||||
public static class HttpRequestMessageExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper method for creating an <see cref="HttpResponseMessage"/> message with a "416 (Requested Range Not Satisfiable)" status code.
|
||||
/// This response can be used in combination with the <see cref="ByteRangeStreamContent"/> 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.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="invalidByteRangeException">An <see cref="InvalidByteRangeException"/> instance, typically thrown by a
|
||||
/// <see cref="ByteRangeStreamContent"/> instance.</param>
|
||||
/// <returns>An 416 (Requested Range Not Satisfiable) error response with a Content-Range header indicating the valid range.</returns>
|
||||
public static HttpResponseMessage CreateErrorResponse(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
[NotNull] InvalidByteRangeException invalidByteRangeException)
|
||||
{
|
||||
var rangeNotSatisfiableResponse = request.CreateErrorResponse(
|
||||
HttpStatusCode.RequestedRangeNotSatisfiable,
|
||||
invalidByteRangeException);
|
||||
rangeNotSatisfiableResponse.Content.Headers.ContentRange = invalidByteRangeException.ContentRange;
|
||||
return rangeNotSatisfiableResponse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that performs content negotiation and creates a <see cref="HttpResponseMessage"/> representing an error
|
||||
/// with an instance of <see cref="ObjectContent{T}"/> wrapping an <see cref="HttpError"/> with message <paramref name="message"/>.
|
||||
/// If no formatter is found, this method returns a response with status 406 NotAcceptable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method requires that <paramref name="request"/> has been associated with an instance of
|
||||
/// <see cref="HttpContext"/>.
|
||||
/// </remarks>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="message">The error message.</param>
|
||||
/// <returns>An error response with error message <paramref name="message"/> and status code <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateErrorResponse(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
HttpStatusCode statusCode,
|
||||
[NotNull] string message)
|
||||
{
|
||||
return request.CreateErrorResponse(statusCode, new HttpError(message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that performs content negotiation and creates a <see cref="HttpResponseMessage"/> representing an error
|
||||
/// with an instance of <see cref="ObjectContent{T}"/> wrapping an <see cref="HttpError"/> with error message <paramref name="message"/>
|
||||
/// for exception <paramref name="exception"/>. If no formatter is found, this method returns a response with status 406 NotAcceptable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method requires that <paramref name="request"/> has been associated with an instance of
|
||||
/// <see cref="HttpContext"/>.
|
||||
/// </remarks>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="message">The error message.</param>
|
||||
/// <param name="exception">The exception.</param>
|
||||
/// <returns>An error response for <paramref name="exception"/> with error message <paramref name="message"/>
|
||||
/// and status code <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateErrorResponse(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
HttpStatusCode statusCode,
|
||||
[NotNull] string message,
|
||||
[NotNull] Exception exception)
|
||||
{
|
||||
var error = new HttpError(exception, includeErrorDetail: false) { Message = message };
|
||||
return request.CreateErrorResponse(statusCode, error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that performs content negotiation and creates a <see cref="HttpResponseMessage"/> representing an error
|
||||
/// with an instance of <see cref="ObjectContent{T}"/> wrapping an <see cref="HttpError"/> for exception <paramref name="exception"/>.
|
||||
/// If no formatter is found, this method returns a response with status 406 NotAcceptable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method requires that <paramref name="request"/> has been associated with an instance of
|
||||
/// <see cref="HttpContext"/>.
|
||||
/// </remarks>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="exception">The exception.</param>
|
||||
/// <returns>An error response for <paramref name="exception"/> with status code <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateErrorResponse(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
HttpStatusCode statusCode,
|
||||
[NotNull] Exception exception)
|
||||
{
|
||||
return request.CreateErrorResponse(statusCode, new HttpError(exception, includeErrorDetail: false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that performs content negotiation and creates a <see cref="HttpResponseMessage"/> representing an error
|
||||
/// with an instance of <see cref="ObjectContent{T}"/> wrapping an <see cref="HttpError"/> for model state <paramref name="modelState"/>.
|
||||
/// If no formatter is found, this method returns a response with status 406 NotAcceptable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method requires that <paramref name="request"/> has been associated with an instance of
|
||||
/// <see cref="HttpContext"/>.
|
||||
/// </remarks>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="modelState">The model state.</param>
|
||||
/// <returns>An error response for <paramref name="modelState"/> with status code <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateErrorResponse(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
HttpStatusCode statusCode,
|
||||
[NotNull] ModelStateDictionary modelState)
|
||||
{
|
||||
return request.CreateErrorResponse(statusCode, new HttpError(modelState, includeErrorDetail: false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that performs content negotiation and creates a <see cref="HttpResponseMessage"/> representing an error
|
||||
/// with an instance of <see cref="ObjectContent{T}"/> wrapping <paramref name="error"/> as the content. If no formatter
|
||||
/// is found, this method returns a response with status 406 NotAcceptable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method requires that <paramref name="request"/> has been associated with an instance of
|
||||
/// <see cref="HttpContext"/>.
|
||||
/// </remarks>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="error">The error to wrap.</param>
|
||||
/// <returns>An error response wrapping <paramref name="error"/> with status code <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateErrorResponse(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
HttpStatusCode statusCode,
|
||||
[NotNull] HttpError error)
|
||||
{
|
||||
return request.CreateResponse<HttpError>(statusCode, error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that performs content negotiation and creates a <see cref="HttpResponseMessage"/> with an instance
|
||||
/// of <see cref="ObjectContent{T}"/> as the content and <see cref="System.Net.HttpStatusCode.OK"/> as the status code
|
||||
/// if a formatter can be found. If no formatter is found, this method returns a response with status 406 NotAcceptable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method requires that <paramref name="request"/> has been associated with an instance of
|
||||
/// <see cref="HttpContext"/>.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="value">The value to wrap. Can be <c>null</c>.</param>
|
||||
/// <returns>A response wrapping <paramref name="value"/> with <see cref="System.Net.HttpStatusCode.OK"/> status code.</returns>
|
||||
public static HttpResponseMessage CreateResponse<T>([NotNull] this HttpRequestMessage request, T value)
|
||||
{
|
||||
return request.CreateResponse<T>(HttpStatusCode.OK, value, formatters: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that performs content negotiation and creates a <see cref="HttpResponseMessage"/> with an instance
|
||||
/// of <see cref="ObjectContent{T}"/> as the content if a formatter can be found. If no formatter is found, this
|
||||
/// method returns a response with status 406 NotAcceptable.
|
||||
/// configuration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method requires that <paramref name="request"/> has been associated with an instance of
|
||||
/// <see cref="HttpContext"/>.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="value">The value to wrap. Can be <c>null</c>.</param>
|
||||
/// <returns>A response wrapping <paramref name="value"/> with <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateResponse<T>(this HttpRequestMessage request, HttpStatusCode statusCode, T value)
|
||||
{
|
||||
return request.CreateResponse<T>(statusCode, value, formatters: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that performs content negotiation and creates a <see cref="HttpResponseMessage"/> with an instance
|
||||
/// of <see cref="ObjectContent{T}"/> as the content if a formatter can be found. If no formatter is found, this
|
||||
/// method returns a response with status 406 NotAcceptable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method will use the provided <paramref name="configuration"/> or it will get the
|
||||
/// <see cref="HttpContext"/> instance associated with <paramref name="request"/>.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="value">The value to wrap. Can be <c>null</c>.</param>
|
||||
/// <param name="configuration">The configuration to use. Can be <c>null</c>.</param>
|
||||
/// <returns>A response wrapping <paramref name="value"/> with <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateResponse<T>(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
HttpStatusCode statusCode,
|
||||
T value,
|
||||
IEnumerable<MediaTypeFormatter> formatters)
|
||||
{
|
||||
var context = GetHttpContext(request);
|
||||
|
||||
if (formatters == null)
|
||||
{
|
||||
// Get the default formatters from options
|
||||
var options = context.RequestServices.GetService<IOptionsAccessor<WebApiCompatShimOptions>>();
|
||||
formatters = options.Options.Formatters;
|
||||
}
|
||||
|
||||
var contentNegotiator = context.RequestServices.GetService<IContentNegotiator>();
|
||||
|
||||
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 request.CreateResponse(HttpStatusCode.NotAcceptable);
|
||||
}
|
||||
else
|
||||
{
|
||||
return request.CreateResponse(statusCode, value, result.Formatter, result.MediaType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that creates a <see cref="HttpResponseMessage"/> with an <see cref="ObjectContent{T}"/> instance containing the provided
|
||||
/// <paramref name="value"/>. The given <paramref name="mediaType"/> is used to find an instance of <see cref="MediaTypeFormatter"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="value">The value to wrap. Can be <c>null</c>.</param>
|
||||
/// <param name="mediaType">The media type used to look up an instance of <see cref="MediaTypeFormatter"/>.</param>
|
||||
/// <returns>A response wrapping <paramref name="value"/> with <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateResponse<T>(this HttpRequestMessage request, HttpStatusCode statusCode, T value, string mediaType)
|
||||
{
|
||||
return request.CreateResponse(statusCode, value, new MediaTypeHeaderValue(mediaType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that creates a <see cref="HttpResponseMessage"/> with an <see cref="ObjectContent{T}"/> instance containing the provided
|
||||
/// <paramref name="value"/>. The given <paramref name="mediaType"/> is used to find an instance of <see cref="MediaTypeFormatter"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="value">The value to wrap. Can be <c>null</c>.</param>
|
||||
/// <param name="mediaType">The media type used to look up an instance of <see cref="MediaTypeFormatter"/>.</param>
|
||||
/// <returns>A response wrapping <paramref name="value"/> with <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateResponse<T>(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
HttpStatusCode statusCode,
|
||||
[NotNull] T value,
|
||||
[NotNull] MediaTypeHeaderValue mediaType)
|
||||
{
|
||||
var context = GetHttpContext(request);
|
||||
|
||||
// Get the default formatters from options
|
||||
var options = context.RequestServices.GetService<IOptionsAccessor<WebApiCompatShimOptions>>();
|
||||
var formatters = options.Options.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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that creates a <see cref="HttpResponseMessage"/> with an <see cref="ObjectContent{T}"/> instance containing the provided
|
||||
/// <paramref name="value"/> and the given <paramref name="formatter"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="value">The value to wrap. Can be <c>null</c>.</param>
|
||||
/// <param name="formatter">The formatter to use.</param>
|
||||
/// <returns>A response wrapping <paramref name="value"/> with <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateResponse<T>(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
HttpStatusCode statusCode,
|
||||
[NotNull] T value,
|
||||
[NotNull] MediaTypeFormatter formatter)
|
||||
{
|
||||
return request.CreateResponse(statusCode, value, formatter, (MediaTypeHeaderValue)null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that creates a <see cref="HttpResponseMessage"/> with an <see cref="ObjectContent{T}"/> instance containing the provided
|
||||
/// <paramref name="value"/> and the given <paramref name="formatter"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="value">The value to wrap. Can be <c>null</c>.</param>
|
||||
/// <param name="formatter">The formatter to use.</param>
|
||||
/// <param name="mediaType">The media type override to set on the response's content. Can be <c>null</c>.</param>
|
||||
/// <returns>A response wrapping <paramref name="value"/> with <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateResponse<T>(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
HttpStatusCode statusCode,
|
||||
[NotNull] T value,
|
||||
[NotNull] MediaTypeFormatter formatter,
|
||||
string mediaType)
|
||||
{
|
||||
var mediaTypeHeader = mediaType != null ? new MediaTypeHeaderValue(mediaType) : null;
|
||||
return request.CreateResponse(statusCode, value, formatter, mediaTypeHeader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that creates a <see cref="HttpResponseMessage"/> with an <see cref="ObjectContent{T}"/> instance containing the provided
|
||||
/// <paramref name="value"/> and the given <paramref name="formatter"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="statusCode">The status code of the created response.</param>
|
||||
/// <param name="value">The value to wrap. Can be <c>null</c>.</param>
|
||||
/// <param name="formatter">The formatter to use.</param>
|
||||
/// <param name="mediaType">The media type override to set on the response's content. Can be <c>null</c>.</param>
|
||||
/// <returns>A response wrapping <paramref name="value"/> with <paramref name="statusCode"/>.</returns>
|
||||
public static HttpResponseMessage CreateResponse<T>(
|
||||
[NotNull] this HttpRequestMessage request,
|
||||
HttpStatusCode statusCode,
|
||||
T value,
|
||||
[NotNull] MediaTypeFormatter formatter,
|
||||
MediaTypeHeaderValue mediaType)
|
||||
{
|
||||
var response = new HttpResponseMessage(statusCode)
|
||||
{
|
||||
RequestMessage = request,
|
||||
};
|
||||
|
||||
response.Content = new ObjectContent<T>(value, formatter, mediaType);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static HttpContext GetHttpContext(HttpRequestMessage request)
|
||||
{
|
||||
var context = request.GetProperty<HttpContext>(nameof(HttpContext));
|
||||
if (context == null)
|
||||
{
|
||||
var message = ShimResources.FormatHttpRequestMessage_MustHaveHttpContext(
|
||||
nameof(HttpRequestMessage),
|
||||
"HttpRequestMessageHttpContextExtensions.GetHttpRequestMessage");
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private static T GetProperty<T>(this HttpRequestMessage request, string key)
|
||||
{
|
||||
object value;
|
||||
request.Properties.TryGetValue(key, out value);
|
||||
|
||||
if (value is T)
|
||||
{
|
||||
return (T)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,86 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNet.Mvc.WebApiCompatShim.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// The request is invalid.
|
||||
/// </summary>
|
||||
internal static string HttpError_BadRequest
|
||||
{
|
||||
get { return GetString("HttpError_BadRequest"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The request is invalid.
|
||||
/// </summary>
|
||||
internal static string FormatHttpError_BadRequest()
|
||||
{
|
||||
return GetString("HttpError_BadRequest");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An error has occurred.
|
||||
/// </summary>
|
||||
internal static string HttpError_GenericError
|
||||
{
|
||||
get { return GetString("HttpError_GenericError"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An error has occurred.
|
||||
/// </summary>
|
||||
internal static string FormatHttpError_GenericError()
|
||||
{
|
||||
return GetString("HttpError_GenericError");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The model state is valid.
|
||||
/// </summary>
|
||||
internal static string HttpError_ValidModelState
|
||||
{
|
||||
get { return GetString("HttpError_ValidModelState"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The model state is valid.
|
||||
/// </summary>
|
||||
internal static string FormatHttpError_ValidModelState()
|
||||
{
|
||||
return GetString("HttpError_ValidModelState");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could not find a formatter matching the media type '{0}' that can write an instance of '{1}'.
|
||||
/// </summary>
|
||||
internal static string HttpRequestMessage_CouldNotFindMatchingFormatter
|
||||
{
|
||||
get { return GetString("HttpRequestMessage_CouldNotFindMatchingFormatter"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could not find a formatter matching the media type '{0}' that can write an instance of '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatHttpRequestMessage_CouldNotFindMatchingFormatter(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("HttpRequestMessage_CouldNotFindMatchingFormatter"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The {0} instance is not properly initialized. Use {1} to create an {0} for the current request.
|
||||
/// </summary>
|
||||
internal static string HttpRequestMessage_MustHaveHttpContext
|
||||
{
|
||||
get { return GetString("HttpRequestMessage_MustHaveHttpContext"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The {0} instance is not properly initialized. Use {1} to create an {0} for the current request.
|
||||
/// </summary>
|
||||
internal static string FormatHttpRequestMessage_MustHaveHttpContext(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("HttpRequestMessage_MustHaveHttpContext"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The {0} only supports writing objects of type {1}.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -117,6 +117,21 @@
|
|||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="HttpError_BadRequest" xml:space="preserve">
|
||||
<value>The request is invalid.</value>
|
||||
</data>
|
||||
<data name="HttpError_GenericError" xml:space="preserve">
|
||||
<value>An error has occurred.</value>
|
||||
</data>
|
||||
<data name="HttpError_ValidModelState" xml:space="preserve">
|
||||
<value>The model state is valid.</value>
|
||||
</data>
|
||||
<data name="HttpRequestMessage_CouldNotFindMatchingFormatter" xml:space="preserve">
|
||||
<value>Could not find a formatter matching the media type '{0}' that can write an instance of '{1}'.</value>
|
||||
</data>
|
||||
<data name="HttpRequestMessage_MustHaveHttpContext" xml:space="preserve">
|
||||
<value>The {0} instance is not properly initialized. Use {1} to create an {0} for the current request.</value>
|
||||
</data>
|
||||
<data name="HttpResponseMessageFormatter_UnsupportedType" xml:space="preserve">
|
||||
<value>The {0} only supports writing objects of type {1}.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Net.Http.Formatting;
|
||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
|
||||
namespace Microsoft.Framework.DependencyInjection
|
||||
|
|
@ -10,6 +11,11 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
public static IServiceCollection AddWebApiConventions(this IServiceCollection services)
|
||||
{
|
||||
services.AddOptionsAction<WebApiCompatShimOptionsSetup>();
|
||||
|
||||
// The constructors on DefaultContentNegotiator aren't DI friendly, so just
|
||||
// new it up.
|
||||
services.AddInstance<IContentNegotiator>(new DefaultContentNegotiator());
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ using System.Collections.Generic;
|
|||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -223,6 +225,108 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal(new string[] { "Hello!" }, values);
|
||||
Assert.Equal(true, response.Headers.TransferEncodingChunked);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("application/json", "application/json")]
|
||||
[InlineData("text/xml", "text/xml")]
|
||||
[InlineData("text/plain, text/xml; q=0.5", "text/xml")]
|
||||
[InlineData("application/*", "application/json")]
|
||||
public async Task ApiController_CreateResponse_Conneg(string accept, string mediaType)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
"http://localhost/api/Blog/HttpRequestMessage/GetUser");
|
||||
|
||||
request.Headers.Accept.ParseAdd(accept);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var user = await response.Content.ReadAsAsync<WebApiCompatShimWebSite.User>();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("Test User", user.Name);
|
||||
Assert.Equal(mediaType, response.Content.Headers.ContentType.MediaType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("application/json")]
|
||||
[InlineData("text/xml")]
|
||||
public async Task ApiController_CreateResponse_HardcodedMediaType(string mediaType)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
"http://localhost/api/Blog/HttpRequestMessage/GetUser?mediaType=" + mediaType);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var user = await response.Content.ReadAsAsync<WebApiCompatShimWebSite.User>();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("Test User", user.Name);
|
||||
Assert.Equal(mediaType, response.Content.Headers.ContentType.MediaType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("application/json", "application/json")]
|
||||
[InlineData("text/xml", "text/xml")]
|
||||
[InlineData("text/plain, text/xml; q=0.5", "text/xml")]
|
||||
[InlineData("application/*", "application/json")]
|
||||
public async Task ApiController_CreateResponse_Conneg_Error(string accept, string mediaType)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
"http://localhost/api/Blog/HttpRequestMessage/Fail");
|
||||
|
||||
request.Headers.Accept.ParseAdd(accept);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var error = await response.Content.ReadAsAsync<HttpError>();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||
Assert.Equal("It failed.", error.Message);
|
||||
Assert.Equal(mediaType, response.Content.Headers.ContentType.MediaType);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task ApiController_CreateResponse_HardcodedFormatter()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
"http://localhost/api/Blog/HttpRequestMessage/GetUserJson");
|
||||
|
||||
// Accept header will be ignored
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml"));
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var user = await response.Content.ReadAsAsync<WebApiCompatShimWebSite.User>();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("Test User", user.Name);
|
||||
Assert.Equal("text/json", response.Content.Headers.ContentType.MediaType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http.Formatting;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace System.Web.Http.Dispatcher
|
||||
{
|
||||
public class HttpErrorTest
|
||||
{
|
||||
public static IEnumerable<object[]> ErrorKeyValue
|
||||
{
|
||||
get
|
||||
{
|
||||
var httpError = new HttpError();
|
||||
yield return new object[] { httpError, (Func<string>)(() => httpError.Message), "Message", "Message_Value" };
|
||||
yield return new object[] { httpError, (Func<string>)(() => httpError.MessageDetail), "MessageDetail", "MessageDetail_Value" };
|
||||
yield return new object[] { httpError, (Func<string>)(() => httpError.ExceptionMessage), "ExceptionMessage", "ExceptionMessage_Value" };
|
||||
yield return new object[] { httpError, (Func<string>)(() => httpError.ExceptionType), "ExceptionType", "ExceptionType_Value" };
|
||||
yield return new object[] { httpError, (Func<string>)(() => httpError.StackTrace), "StackTrace", "StackTrace_Value" };
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> HttpErrors
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new[] { new HttpError() };
|
||||
yield return new[] { new HttpError("error") };
|
||||
yield return new[] { new HttpError(new NotImplementedException(), true) };
|
||||
yield return new[] { new HttpError(new ModelStateDictionary() { { "key", new ModelState() { Errors = { new ModelError("error") } } } }, true) };
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringConstructor_AddsCorrectDictionaryItems()
|
||||
{
|
||||
HttpError error = new HttpError("something bad happened");
|
||||
|
||||
Assert.Contains(new KeyValuePair<string, object>("Message", "something bad happened"), error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionConstructorWithDetail_AddsCorrectDictionaryItems()
|
||||
{
|
||||
HttpError error = new HttpError(new ArgumentException("error", new Exception()), true);
|
||||
|
||||
Assert.Contains(new KeyValuePair<string, object>("Message", "An error has occurred."), error);
|
||||
Assert.Contains(new KeyValuePair<string, object>("ExceptionMessage", "error"), error);
|
||||
Assert.Contains(new KeyValuePair<string, object>("ExceptionType", "System.ArgumentException"), error);
|
||||
Assert.True(error.ContainsKey("StackTrace"));
|
||||
Assert.True(error.ContainsKey("InnerException"));
|
||||
Assert.IsType<HttpError>(error["InnerException"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelStateConstructorWithDetail_AddsCorrectDictionaryItems()
|
||||
{
|
||||
ModelStateDictionary modelState = new ModelStateDictionary();
|
||||
modelState.AddModelError("[0].Name", "error1");
|
||||
modelState.AddModelError("[0].Name", "error2");
|
||||
modelState.AddModelError("[0].Address", "error");
|
||||
modelState.AddModelError("[2].Name", new Exception("OH NO"));
|
||||
|
||||
HttpError error = new HttpError(modelState, true);
|
||||
HttpError modelStateError = error["ModelState"] as HttpError;
|
||||
|
||||
Assert.Contains(new KeyValuePair<string, object>("Message", "The request is invalid."), error);
|
||||
Assert.Contains("error1", modelStateError["[0].Name"] as IEnumerable<string>);
|
||||
Assert.Contains("error2", modelStateError["[0].Name"] as IEnumerable<string>);
|
||||
Assert.Contains("error", modelStateError["[0].Address"] as IEnumerable<string>);
|
||||
Assert.True(modelStateError.ContainsKey("[2].Name"));
|
||||
Assert.Contains("OH NO", modelStateError["[2].Name"] as IEnumerable<string>);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionConstructorWithoutDetail_AddsCorrectDictionaryItems()
|
||||
{
|
||||
HttpError error = new HttpError(new ArgumentException("error", new Exception()), false);
|
||||
|
||||
Assert.Contains(new KeyValuePair<string, object>("Message", "An error has occurred."), error);
|
||||
Assert.False(error.ContainsKey("ExceptionMessage"));
|
||||
Assert.False(error.ContainsKey("ExceptionType"));
|
||||
Assert.False(error.ContainsKey("StackTrace"));
|
||||
Assert.False(error.ContainsKey("InnerException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModelStateConstructorWithoutDetail_AddsCorrectDictionaryItems()
|
||||
{
|
||||
ModelStateDictionary modelState = new ModelStateDictionary();
|
||||
modelState.AddModelError("[0].Name", "error1");
|
||||
modelState.AddModelError("[0].Name", "error2");
|
||||
modelState.AddModelError("[0].Address", "error");
|
||||
modelState.AddModelError("[2].Name", new Exception("OH NO"));
|
||||
|
||||
HttpError error = new HttpError(modelState, false);
|
||||
HttpError modelStateError = error["ModelState"] as HttpError;
|
||||
|
||||
Assert.Contains(new KeyValuePair<string, object>("Message", "The request is invalid."), error);
|
||||
Assert.Contains("error1", modelStateError["[0].Name"] as IEnumerable<string>);
|
||||
Assert.Contains("error2", modelStateError["[0].Name"] as IEnumerable<string>);
|
||||
Assert.Contains("error", modelStateError["[0].Address"] as IEnumerable<string>);
|
||||
Assert.True(modelStateError.ContainsKey("[2].Name"));
|
||||
Assert.DoesNotContain("OH NO", modelStateError["[2].Name"] as IEnumerable<string>);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HttpError_Roundtrips_WithJsonFormatter()
|
||||
{
|
||||
HttpError error = new HttpError("error") { { "ErrorCode", 42 }, { "Data", new[] { "a", "b", "c" } } };
|
||||
MediaTypeFormatter formatter = new JsonMediaTypeFormatter();
|
||||
MemoryStream stream = new MemoryStream();
|
||||
|
||||
formatter.WriteToStreamAsync(typeof(HttpError), error, stream, content: null, transportContext: null).Wait();
|
||||
stream.Position = 0;
|
||||
HttpError roundtrippedError = formatter.ReadFromStreamAsync(typeof(HttpError), stream, content: null, formatterLogger: null).Result as HttpError;
|
||||
|
||||
Assert.NotNull(roundtrippedError);
|
||||
Assert.Equal("error", roundtrippedError.Message);
|
||||
Assert.Equal(42L, roundtrippedError["ErrorCode"]);
|
||||
JArray data = roundtrippedError["Data"] as JArray;
|
||||
Assert.Equal(3, data.Count);
|
||||
Assert.Contains("a", data);
|
||||
Assert.Contains("b", data);
|
||||
Assert.Contains("c", data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HttpError_Roundtrips_WithXmlFormatter()
|
||||
{
|
||||
HttpError error = new HttpError("error") { { "ErrorCode", 42 }, { "Data", new[] { "a", "b", "c" } } };
|
||||
MediaTypeFormatter formatter = new XmlMediaTypeFormatter();
|
||||
MemoryStream stream = new MemoryStream();
|
||||
|
||||
formatter.WriteToStreamAsync(typeof(HttpError), error, stream, content: null, transportContext: null).Wait();
|
||||
stream.Position = 0;
|
||||
HttpError roundtrippedError = formatter.ReadFromStreamAsync(typeof(HttpError), stream, content: null, formatterLogger: null).Result as HttpError;
|
||||
|
||||
Assert.NotNull(roundtrippedError);
|
||||
Assert.Equal("error", roundtrippedError.Message);
|
||||
Assert.Equal("42", roundtrippedError["ErrorCode"]);
|
||||
Assert.Equal("a b c", roundtrippedError["Data"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HttpErrorWithWhitespace_Roundtrips_WithXmlFormatter()
|
||||
{
|
||||
string message = " foo\n bar \n ";
|
||||
HttpError error = new HttpError(message);
|
||||
MediaTypeFormatter formatter = new XmlMediaTypeFormatter();
|
||||
MemoryStream stream = new MemoryStream();
|
||||
|
||||
formatter.WriteToStreamAsync(typeof(HttpError), error, stream, content: null, transportContext: null).Wait();
|
||||
stream.Position = 0;
|
||||
HttpError roundtrippedError = formatter.ReadFromStreamAsync(typeof(HttpError), stream, content: null, formatterLogger: null).Result as HttpError;
|
||||
|
||||
Assert.NotNull(roundtrippedError);
|
||||
Assert.Equal(message, roundtrippedError.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HttpError_Roundtrips_WithXmlSerializer()
|
||||
{
|
||||
HttpError error = new HttpError("error") { { "ErrorCode", 42 }, { "Data", new[] { "a", "b", "c" } } };
|
||||
MediaTypeFormatter formatter = new XmlMediaTypeFormatter() { UseXmlSerializer = true };
|
||||
MemoryStream stream = new MemoryStream();
|
||||
|
||||
formatter.WriteToStreamAsync(typeof(HttpError), error, stream, content: null, transportContext: null).Wait();
|
||||
stream.Position = 0;
|
||||
HttpError roundtrippedError = formatter.ReadFromStreamAsync(typeof(HttpError), stream, content: null, formatterLogger: null).Result as HttpError;
|
||||
|
||||
Assert.NotNull(roundtrippedError);
|
||||
Assert.Equal("error", roundtrippedError.Message);
|
||||
Assert.Equal("42", roundtrippedError["ErrorCode"]);
|
||||
Assert.Equal("a b c", roundtrippedError["Data"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HttpErrorForInnerException_Serializes_WithXmlSerializer()
|
||||
{
|
||||
HttpError error = new HttpError(new ArgumentException("error", new Exception("innerError")), includeErrorDetail: true);
|
||||
MediaTypeFormatter formatter = new XmlMediaTypeFormatter() { UseXmlSerializer = true };
|
||||
MemoryStream stream = new MemoryStream();
|
||||
|
||||
formatter.WriteToStreamAsync(typeof(HttpError), error, stream, content: null, transportContext: null).Wait();
|
||||
stream.Position = 0;
|
||||
string serializedError = new StreamReader(stream).ReadToEnd();
|
||||
|
||||
Assert.NotNull(serializedError);
|
||||
Assert.Equal(
|
||||
"<Error><Message>An error has occurred.</Message><ExceptionMessage>error</ExceptionMessage><ExceptionType>System.ArgumentException</ExceptionType><StackTrace /><InnerException><Message>An error has occurred.</Message><ExceptionMessage>innerError</ExceptionMessage><ExceptionType>System.Exception</ExceptionType><StackTrace /></InnerException></Error>",
|
||||
serializedError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPropertyValue_GetsValue_IfTypeMatches()
|
||||
{
|
||||
HttpError error = new HttpError();
|
||||
error["key"] = "x";
|
||||
|
||||
Assert.Equal("x", error.GetPropertyValue<string>("key"));
|
||||
Assert.Equal("x", error.GetPropertyValue<object>("key"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPropertyValue_GetsDefault_IfTypeDoesNotMatch()
|
||||
{
|
||||
HttpError error = new HttpError();
|
||||
error["key"] = "x";
|
||||
|
||||
Assert.Null(error.GetPropertyValue<Uri>("key"));
|
||||
Assert.Equal(0, error.GetPropertyValue<int>("key"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPropertyValue_GetsDefault_IfPropertyMissing()
|
||||
{
|
||||
HttpError error = new HttpError();
|
||||
|
||||
Assert.Null(error.GetPropertyValue<string>("key"));
|
||||
Assert.Equal(0, error.GetPropertyValue<int>("key"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("ErrorKeyValue")]
|
||||
public void HttpErrorStringProperties_UseCorrectHttpErrorKey(HttpError httpError, Func<string> productUnderTest, string key, string actualValue)
|
||||
{
|
||||
// Arrange
|
||||
httpError[key] = actualValue;
|
||||
|
||||
// Act
|
||||
string expectedValue = productUnderTest.Invoke();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedValue, actualValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HttpErrorProperty_InnerException_UsesCorrectHttpErrorKey()
|
||||
{
|
||||
// Arrange
|
||||
HttpError error = new HttpError(new ArgumentException("error", new Exception()), true);
|
||||
|
||||
// Act
|
||||
HttpError innerException = error.InnerException;
|
||||
|
||||
// Assert
|
||||
Assert.Same(error["InnerException"], innerException);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HttpErrorProperty_ModelState_UsesCorrectHttpErrorKey()
|
||||
{
|
||||
// Arrange
|
||||
ModelStateDictionary modelState = new ModelStateDictionary();
|
||||
modelState.AddModelError("[0].Name", "error1");
|
||||
HttpError error = new HttpError(modelState, true);
|
||||
|
||||
// Act
|
||||
HttpError actualModelStateError = error.ModelState;
|
||||
|
||||
// Assert
|
||||
Assert.Same(error["ModelState"], actualModelStateError);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("HttpErrors")]
|
||||
public void HttpErrors_UseCaseInsensitiveComparer(HttpError httpError)
|
||||
{
|
||||
// Arrange
|
||||
var lowercaseKey = "abcd";
|
||||
var uppercaseKey = "ABCD";
|
||||
|
||||
httpError[lowercaseKey] = "error";
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(httpError.ContainsKey(lowercaseKey));
|
||||
Assert.True(httpError.ContainsKey(uppercaseKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,347 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace System.Net.Http
|
||||
{
|
||||
public class HttpRequestMessageExtensionsTest
|
||||
{
|
||||
[Fact]
|
||||
public void CreateResponse_DoingConneg_OnlyContent_RetrievesContentNegotiatorFromServices()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
|
||||
var services = new Mock<IServiceProvider>();
|
||||
services
|
||||
.Setup(s => s.GetService(typeof(IContentNegotiator)))
|
||||
.Returns(Mock.Of<IContentNegotiator>())
|
||||
.Verifiable();
|
||||
|
||||
var options = new WebApiCompatShimOptions();
|
||||
options.Formatters.AddRange(new MediaTypeFormatterCollection());
|
||||
|
||||
var optionsAccessor = new Mock<IOptionsAccessor<WebApiCompatShimOptions>>();
|
||||
optionsAccessor.SetupGet(o => o.Options).Returns(options);
|
||||
|
||||
services
|
||||
.Setup(s => s.GetService(typeof(IOptionsAccessor<WebApiCompatShimOptions>)))
|
||||
.Returns(optionsAccessor.Object);
|
||||
|
||||
context.RequestServices = services.Object;
|
||||
|
||||
var request = CreateRequest(context);
|
||||
|
||||
// Act
|
||||
request.CreateResponse(CreateValue());
|
||||
|
||||
// Assert
|
||||
services.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_DoingConneg_RetrievesContentNegotiatorFromServices()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
|
||||
var services = new Mock<IServiceProvider>();
|
||||
services
|
||||
.Setup(s => s.GetService(typeof(IContentNegotiator)))
|
||||
.Returns(Mock.Of<IContentNegotiator>())
|
||||
.Verifiable();
|
||||
|
||||
var options = new WebApiCompatShimOptions();
|
||||
options.Formatters.AddRange(new MediaTypeFormatterCollection());
|
||||
|
||||
var optionsAccessor = new Mock<IOptionsAccessor<WebApiCompatShimOptions>>();
|
||||
optionsAccessor.SetupGet(o => o.Options).Returns(options);
|
||||
|
||||
services
|
||||
.Setup(s => s.GetService(typeof(IOptionsAccessor<WebApiCompatShimOptions>)))
|
||||
.Returns(optionsAccessor.Object);
|
||||
|
||||
context.RequestServices = services.Object;
|
||||
|
||||
var request = CreateRequest(context);
|
||||
|
||||
// Act
|
||||
request.CreateResponse(HttpStatusCode.OK, CreateValue());
|
||||
|
||||
// Assert
|
||||
services.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_DoingConneg_PerformsContentNegotiationAndCreatesContentUsingResults()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
|
||||
var formatter = new XmlMediaTypeFormatter();
|
||||
|
||||
var contentNegotiator = new Mock<IContentNegotiator>();
|
||||
contentNegotiator
|
||||
.Setup(c => c.Negotiate(It.IsAny<Type>(), It.IsAny<HttpRequestMessage>(), It.IsAny<IEnumerable<MediaTypeFormatter>>()))
|
||||
.Returns(new ContentNegotiationResult(formatter, mediaType: null));
|
||||
|
||||
context.RequestServices = CreateServices(contentNegotiator.Object, formatter);
|
||||
|
||||
var request = CreateRequest(context);
|
||||
|
||||
// Act
|
||||
var response = request.CreateResponse<string>(HttpStatusCode.NoContent, "42");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
Assert.Same(request, response.RequestMessage);
|
||||
|
||||
var objectContent = Assert.IsType<ObjectContent<string>>(response.Content);
|
||||
Assert.Equal("42", objectContent.Value);
|
||||
Assert.Same(formatter, objectContent.Formatter);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_MatchingMediaType_WhenMediaTypeStringIsInvalidFormat_Throws()
|
||||
{
|
||||
HttpRequestMessage request = CreateRequest(new DefaultHttpContext());
|
||||
|
||||
var ex = Assert.Throws<FormatException>(
|
||||
() => request.CreateResponse(HttpStatusCode.OK, CreateValue(), "foo/bar; param=value"));
|
||||
|
||||
Assert.Equal("The format of value 'foo/bar; param=value' is invalid.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_MatchingMediaType_WhenRequestDoesNotHaveHttpContextThrows()
|
||||
{
|
||||
HttpRequestMessage request = CreateRequest(null);
|
||||
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => request.CreateResponse(HttpStatusCode.OK, CreateValue(), mediaType: "foo/bar"));
|
||||
|
||||
Assert.Equal(
|
||||
"The HttpRequestMessage instance is not properly initialized. " +
|
||||
"Use HttpRequestMessageHttpContextExtensions.GetHttpRequestMessage to create an HttpRequestMessage " +
|
||||
"for the current request.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_MatchingMediaType_WhenMediaTypeDoesNotMatch_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
context.RequestServices = CreateServices(new DefaultContentNegotiator());
|
||||
|
||||
var request = CreateRequest(context);
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => request.CreateResponse(HttpStatusCode.OK, CreateValue(), mediaType: "foo/bar"));
|
||||
Assert.Equal(
|
||||
"Could not find a formatter matching the media type 'foo/bar' that can write an instance of 'System.Object'.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_MatchingMediaType_FindsMatchingFormatterAndCreatesResponse()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
|
||||
var formatter = new Mock<MediaTypeFormatter> { CallBase = true };
|
||||
formatter.Setup(f => f.CanWriteType(typeof(object))).Returns(true).Verifiable();
|
||||
formatter.Object.SupportedMediaTypes.Add(new MediaTypeHeaderValue("foo/bar"));
|
||||
|
||||
context.RequestServices = CreateServices(new DefaultContentNegotiator(), formatter.Object);
|
||||
|
||||
var expectedValue = CreateValue();
|
||||
|
||||
var request = CreateRequest(context);
|
||||
|
||||
// Act
|
||||
var response = request.CreateResponse(HttpStatusCode.Gone, expectedValue, mediaType: "foo/bar");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.Gone, response.StatusCode);
|
||||
var content = Assert.IsType<ObjectContent<object>>(response.Content);
|
||||
Assert.Same(expectedValue, content.Value);
|
||||
Assert.Same(formatter.Object, content.Formatter);
|
||||
Assert.Equal("foo/bar", content.Headers.ContentType.MediaType);
|
||||
formatter.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_AcceptingFormatter_CreatesResponseWithDefaultMediaType()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
|
||||
var formatter = new Mock<MediaTypeFormatter>() { CallBase = true };
|
||||
formatter
|
||||
.Setup(f => f.CanWriteType(typeof(object)))
|
||||
.Returns(true)
|
||||
.Verifiable();
|
||||
formatter
|
||||
.Setup(f => f.SetDefaultContentHeaders(typeof(object), It.IsAny<HttpContentHeaders>(), It.IsAny<MediaTypeHeaderValue>()))
|
||||
.Callback<Type, HttpContentHeaders, MediaTypeHeaderValue>(SetMediaType)
|
||||
.Verifiable();
|
||||
|
||||
formatter.Object.SupportedMediaTypes.Add(new MediaTypeHeaderValue("foo/bar"));
|
||||
|
||||
var expectedValue = CreateValue();
|
||||
|
||||
var request = CreateRequest(context);
|
||||
|
||||
// Act
|
||||
var response = request.CreateResponse(
|
||||
HttpStatusCode.MultipleChoices,
|
||||
expectedValue,
|
||||
formatter.Object,
|
||||
mediaType: (string)null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.MultipleChoices, response.StatusCode);
|
||||
var content = Assert.IsType<ObjectContent<object>>(response.Content);
|
||||
Assert.Same(expectedValue, content.Value);
|
||||
Assert.Same(formatter.Object, content.Formatter);
|
||||
Assert.Equal("foo/bar", content.Headers.ContentType.MediaType);
|
||||
|
||||
formatter.Verify();
|
||||
}
|
||||
|
||||
private static void SetMediaType(Type type, HttpContentHeaders headers, MediaTypeHeaderValue value)
|
||||
{
|
||||
headers.ContentType = new MediaTypeHeaderValue("foo/bar");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_AcceptingFormatter_WithOverridenMediaTypeString_CreatesResponse()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
|
||||
var formatter = new Mock<MediaTypeFormatter> { CallBase = true };
|
||||
formatter.Setup(f => f.CanWriteType(typeof(object))).Returns(true).Verifiable();
|
||||
formatter.Object.SupportedMediaTypes.Add(new MediaTypeHeaderValue("foo/bar"));
|
||||
|
||||
var expectedValue = CreateValue();
|
||||
|
||||
var request = CreateRequest(context);
|
||||
|
||||
// Act
|
||||
var response = request.CreateResponse(
|
||||
HttpStatusCode.MultipleChoices,
|
||||
CreateValue(),
|
||||
formatter.Object,
|
||||
mediaType: "bin/baz");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("bin/baz", response.Content.Headers.ContentType.MediaType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_AcceptingFormatter_WithOverridenMediaTypeHeader_CreatesResponse()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
|
||||
var formatter = new Mock<MediaTypeFormatter> { CallBase = true };
|
||||
formatter.Setup(f => f.CanWriteType(typeof(object))).Returns(true).Verifiable();
|
||||
formatter.Object.SupportedMediaTypes.Add(new MediaTypeHeaderValue("foo/bar"));
|
||||
|
||||
var expectedValue = CreateValue();
|
||||
|
||||
var request = CreateRequest(context);
|
||||
|
||||
// Act
|
||||
var response = request.CreateResponse(
|
||||
HttpStatusCode.MultipleChoices,
|
||||
CreateValue(),
|
||||
formatter.Object,
|
||||
mediaType: new MediaTypeHeaderValue("bin/baz"));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("bin/baz", response.Content.Headers.ContentType.MediaType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateErrorResponseRangeNotSatisfiable_SetsCorrectStatusCodeAndContentRangeHeader()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
context.RequestServices = CreateServices(new DefaultContentNegotiator());
|
||||
|
||||
var request = CreateRequest(context);
|
||||
|
||||
var expectedContentRange = new ContentRangeHeaderValue(length: 128);
|
||||
var invalidByteRangeException = new InvalidByteRangeException(expectedContentRange);
|
||||
|
||||
// Act
|
||||
var response = request.CreateErrorResponse(invalidByteRangeException);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.RequestedRangeNotSatisfiable, response.StatusCode);
|
||||
Assert.Same(expectedContentRange, response.Content.Headers.ContentRange);
|
||||
}
|
||||
|
||||
private static HttpRequestMessage CreateRequest(HttpContext context)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Properties.Add(nameof(HttpContext), context);
|
||||
return request;
|
||||
}
|
||||
|
||||
private static object CreateValue()
|
||||
{
|
||||
return new object();
|
||||
}
|
||||
|
||||
private static IServiceProvider CreateServices(
|
||||
IContentNegotiator contentNegotiator = null,
|
||||
MediaTypeFormatter formatter = null)
|
||||
{
|
||||
var options = new WebApiCompatShimOptions();
|
||||
|
||||
if (formatter == null)
|
||||
{
|
||||
options.Formatters.AddRange(new MediaTypeFormatterCollection());
|
||||
}
|
||||
else
|
||||
{
|
||||
options.Formatters.Add(formatter);
|
||||
}
|
||||
|
||||
var optionsAccessor = new Mock<IOptionsAccessor<WebApiCompatShimOptions>>();
|
||||
optionsAccessor.SetupGet(o => o.Options).Returns(options);
|
||||
|
||||
var services = new Mock<IServiceProvider>(MockBehavior.Strict);
|
||||
services
|
||||
.Setup(s => s.GetService(typeof(IOptionsAccessor<WebApiCompatShimOptions>)))
|
||||
.Returns(optionsAccessor.Object);
|
||||
|
||||
if (contentNegotiator != null)
|
||||
{
|
||||
services
|
||||
.Setup(s => s.GetService(typeof(IContentNegotiator)))
|
||||
.Returns(contentNegotiator);
|
||||
}
|
||||
|
||||
return services.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ using System.Web.Http;
|
|||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using System.Net;
|
||||
using System.Net.Http.Formatting;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
|
|
@ -16,7 +17,7 @@ namespace WebApiCompatShimWebSite
|
|||
public async Task<IActionResult> EchoProperty()
|
||||
{
|
||||
await Echo(Request);
|
||||
return new EmptyResult();
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> EchoParameter(HttpRequestMessage request)
|
||||
|
|
@ -33,8 +34,8 @@ namespace WebApiCompatShimWebSite
|
|||
public async Task<HttpResponseMessage> EchoWithResponseMessage(HttpRequestMessage request)
|
||||
{
|
||||
var message = string.Format(
|
||||
"{0} {1}",
|
||||
request.Method.ToString(),
|
||||
"{0} {1}",
|
||||
request.Method.ToString(),
|
||||
await request.Content.ReadAsStringAsync());
|
||||
|
||||
var response = request.CreateResponse(HttpStatusCode.OK);
|
||||
|
|
@ -57,6 +58,42 @@ namespace WebApiCompatShimWebSite
|
|||
return response;
|
||||
}
|
||||
|
||||
public HttpResponseMessage GetUser(string mediaType = null)
|
||||
{
|
||||
var user = new User()
|
||||
{
|
||||
Name = "Test User",
|
||||
};
|
||||
|
||||
if (mediaType == null)
|
||||
{
|
||||
// This will perform content negotation
|
||||
return Request.CreateResponse<User>(HttpStatusCode.OK, user);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This will use the provided media type
|
||||
return Request.CreateResponse<User>(HttpStatusCode.OK, user, mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
public HttpResponseMessage GetUserJson()
|
||||
{
|
||||
var user = new User()
|
||||
{
|
||||
Name = "Test User",
|
||||
};
|
||||
|
||||
return Request.CreateResponse<User>(HttpStatusCode.OK, user, new JsonMediaTypeFormatter(), "text/json");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public HttpResponseMessage Fail()
|
||||
{
|
||||
// This will perform content negotation
|
||||
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "It failed.");
|
||||
}
|
||||
|
||||
private async Task Echo(HttpRequestMessage request)
|
||||
{
|
||||
var message = string.Format(
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@ namespace WebApiCompatShimWebSite
|
|||
{
|
||||
public class User
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue