diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpError.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpError.cs
new file mode 100644
index 0000000000..438facb22b
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpError.cs
@@ -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
+{
+ ///
+ /// 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 type.
+ ///
+ [XmlRoot("Error")]
+ public sealed class HttpError : Dictionary, IXmlSerializable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public HttpError()
+ : base(StringComparer.OrdinalIgnoreCase)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class containing error message .
+ ///
+ /// The error message to associate with this instance.
+ public HttpError([NotNull] string message)
+ : this()
+ {
+ Message = message;
+ }
+
+ ///
+ /// Initializes a new instance of the class for .
+ ///
+ /// The exception to use for error information.
+ /// true to include the exception information in the error; false otherwise
+ 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));
+ }
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class for .
+ ///
+ /// The invalid model state to use for error information.
+ /// true to include exception messages in the error; false otherwise
+ 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 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);
+ }
+
+ ///
+ /// 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.
+ ///
+ public string Message
+ {
+ get { return GetPropertyValue(HttpErrorKeys.MessageKey); }
+ set { this[HttpErrorKeys.MessageKey] = value; }
+ }
+
+ ///
+ /// The containing information about the errors that occurred during model binding.
+ ///
+ ///
+ /// The inclusion of information carried in the is
+ /// controlled by the error detail policy. All other information in the
+ /// 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.
+ ///
+ public HttpError ModelState
+ {
+ get { return GetPropertyValue(HttpErrorKeys.ModelStateKey); }
+ }
+
+ ///
+ /// A detailed description of the error intended for the developer to understand exactly what failed.
+ ///
+ ///
+ /// 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.
+ ///
+ public string MessageDetail
+ {
+ get { return GetPropertyValue(HttpErrorKeys.MessageDetailKey); }
+ set { this[HttpErrorKeys.MessageDetailKey] = value; }
+ }
+
+ ///
+ /// The message of the if available.
+ ///
+ ///
+ /// 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.
+ ///
+ public string ExceptionMessage
+ {
+ get { return GetPropertyValue(HttpErrorKeys.ExceptionMessageKey); }
+ set { this[HttpErrorKeys.ExceptionMessageKey] = value; }
+ }
+
+ ///
+ /// The type of the if available.
+ ///
+ ///
+ /// 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.
+ ///
+ public string ExceptionType
+ {
+ get { return GetPropertyValue(HttpErrorKeys.ExceptionTypeKey); }
+ set { this[HttpErrorKeys.ExceptionTypeKey] = value; }
+ }
+
+ ///
+ /// The stack trace information associated with this instance if available.
+ ///
+ ///
+ /// 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.
+ ///
+ public string StackTrace
+ {
+ get { return GetPropertyValue(HttpErrorKeys.StackTraceKey); }
+ set { this[HttpErrorKeys.StackTraceKey] = value; }
+ }
+
+ ///
+ /// The inner associated with this instance if available.
+ ///
+ ///
+ /// 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.
+ ///
+ public HttpError InnerException
+ {
+ get { return GetPropertyValue(HttpErrorKeys.InnerExceptionKey); }
+ }
+
+ ///
+ /// Gets a particular property value from this error instance.
+ ///
+ /// The type of the property.
+ /// The name of the error property.
+ /// The value of the error property.
+ public TValue GetPropertyValue(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();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpErrorKeys.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpErrorKeys.cs
new file mode 100644
index 0000000000..7d079b94ba
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpErrorKeys.cs
@@ -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
+{
+ ///
+ /// Provides keys to look up error information stored in the dictionary.
+ ///
+ public static class HttpErrorKeys
+ {
+ ///
+ /// Provides a key for the Message.
+ ///
+ public static readonly string MessageKey = "Message";
+
+ ///
+ /// Provides a key for the MessageDetail.
+ ///
+ public static readonly string MessageDetailKey = "MessageDetail";
+
+ ///
+ /// Provides a key for the ModelState.
+ ///
+ public static readonly string ModelStateKey = "ModelState";
+
+ ///
+ /// Provides a key for the ExceptionMessage.
+ ///
+ public static readonly string ExceptionMessageKey = "ExceptionMessage";
+
+ ///
+ /// Provides a key for the ExceptionType.
+ ///
+ public static readonly string ExceptionTypeKey = "ExceptionType";
+
+ ///
+ /// Provides a key for the StackTrace.
+ ///
+ public static readonly string StackTraceKey = "StackTrace";
+
+ ///
+ /// Provides a key for the InnerException.
+ ///
+ public static readonly string InnerExceptionKey = "InnerException";
+
+ ///
+ /// Provides a key for the MessageLanguage.
+ ///
+ public static readonly string MessageLanguageKey = "MessageLanguage";
+
+ ///
+ /// Provides a key for the ErrorCode.
+ ///
+ public static readonly string ErrorCodeKey = "ErrorCode";
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs
new file mode 100644
index 0000000000..a2555f9c75
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs
@@ -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
+{
+ ///
+ /// Provides extension methods for the class.
+ ///
+ public static class HttpRequestMessageExtensions
+ {
+ ///
+ /// 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(
+ [NotNull] this HttpRequestMessage request,
+ [NotNull] InvalidByteRangeException invalidByteRangeException)
+ {
+ var rangeNotSatisfiableResponse = request.CreateErrorResponse(
+ HttpStatusCode.RequestedRangeNotSatisfiable,
+ invalidByteRangeException);
+ rangeNotSatisfiableResponse.Content.Headers.ContentRange = invalidByteRangeException.ContentRange;
+ return rangeNotSatisfiableResponse;
+ }
+
+ ///
+ /// 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(
+ [NotNull] this HttpRequestMessage request,
+ HttpStatusCode statusCode,
+ [NotNull] string 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(
+ [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);
+ }
+
+ ///
+ /// 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(
+ [NotNull] this HttpRequestMessage request,
+ HttpStatusCode statusCode,
+ [NotNull] Exception 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(
+ [NotNull] this HttpRequestMessage request,
+ HttpStatusCode statusCode,
+ [NotNull] ModelStateDictionary 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(
+ [NotNull] this HttpRequestMessage request,
+ HttpStatusCode statusCode,
+ [NotNull] HttpError 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([NotNull] this HttpRequestMessage request, T 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 use the provided or it 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 configuration to use. Can be null.
+ /// A response wrapping with .
+ public static HttpResponseMessage CreateResponse(
+ [NotNull] this HttpRequestMessage request,
+ HttpStatusCode statusCode,
+ T value,
+ IEnumerable formatters)
+ {
+ var context = GetHttpContext(request);
+
+ if (formatters == null)
+ {
+ // Get the default formatters from options
+ var options = context.RequestServices.GetService>();
+ formatters = options.Options.Formatters;
+ }
+
+ var contentNegotiator = context.RequestServices.GetService();
+
+ 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);
+ }
+ }
+
+ ///
+ /// 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(
+ [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>();
+ 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);
+ }
+
+ ///
+ /// 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(
+ [NotNull] this HttpRequestMessage request,
+ HttpStatusCode statusCode,
+ [NotNull] T value,
+ [NotNull] MediaTypeFormatter 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(
+ [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);
+ }
+
+ ///
+ /// 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(
+ [NotNull] this HttpRequestMessage request,
+ HttpStatusCode statusCode,
+ T value,
+ [NotNull] MediaTypeFormatter formatter,
+ MediaTypeHeaderValue mediaType)
+ {
+ 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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs
index cb10994d90..480fda8e65 100644
--- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs
@@ -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);
+ ///
+ /// The request is invalid.
+ ///
+ internal static string HttpError_BadRequest
+ {
+ get { return GetString("HttpError_BadRequest"); }
+ }
+
+ ///
+ /// The request is invalid.
+ ///
+ internal static string FormatHttpError_BadRequest()
+ {
+ return GetString("HttpError_BadRequest");
+ }
+
+ ///
+ /// An error has occurred.
+ ///
+ internal static string HttpError_GenericError
+ {
+ get { return GetString("HttpError_GenericError"); }
+ }
+
+ ///
+ /// An error has occurred.
+ ///
+ internal static string FormatHttpError_GenericError()
+ {
+ return GetString("HttpError_GenericError");
+ }
+
+ ///
+ /// The model state is valid.
+ ///
+ internal static string HttpError_ValidModelState
+ {
+ get { return GetString("HttpError_ValidModelState"); }
+ }
+
+ ///
+ /// The model state is valid.
+ ///
+ internal static string FormatHttpError_ValidModelState()
+ {
+ return GetString("HttpError_ValidModelState");
+ }
+
+ ///
+ /// Could not find a formatter matching the media type '{0}' that can write an instance of '{1}'.
+ ///
+ internal static string HttpRequestMessage_CouldNotFindMatchingFormatter
+ {
+ get { return GetString("HttpRequestMessage_CouldNotFindMatchingFormatter"); }
+ }
+
+ ///
+ /// Could not find a formatter matching the media type '{0}' that can write an instance of '{1}'.
+ ///
+ internal static string FormatHttpRequestMessage_CouldNotFindMatchingFormatter(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("HttpRequestMessage_CouldNotFindMatchingFormatter"), p0, p1);
+ }
+
+ ///
+ /// The {0} instance is not properly initialized. Use {1} to create an {0} for the current request.
+ ///
+ internal static string HttpRequestMessage_MustHaveHttpContext
+ {
+ get { return GetString("HttpRequestMessage_MustHaveHttpContext"); }
+ }
+
+ ///
+ /// The {0} instance is not properly initialized. Use {1} to create an {0} for the current request.
+ ///
+ internal static string FormatHttpRequestMessage_MustHaveHttpContext(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("HttpRequestMessage_MustHaveHttpContext"), p0, p1);
+ }
+
///
/// The {0} only supports writing objects of type {1}.
///
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Resources.resx b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Resources.resx
index a86ebf7b9a..e62964b509 100644
--- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Resources.resx
@@ -117,6 +117,21 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ The request is invalid.
+
+
+ An error has occurred.
+
+
+ The model state is valid.
+
+
+ Could not find a formatter matching the media type '{0}' that can write an instance of '{1}'.
+
+
+ The {0} instance is not properly initialized. Use {1} to create an {0} for the current request.
+
The {0} only supports writing objects of type {1}.
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/WebApiCompatShimServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/WebApiCompatShimServiceCollectionExtensions.cs
index 8ff52fb81a..c821107215 100644
--- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/WebApiCompatShimServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/WebApiCompatShimServiceCollectionExtensions.cs
@@ -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();
+
+ // The constructors on DefaultContentNegotiator aren't DI friendly, so just
+ // new it up.
+ services.AddInstance(new DefaultContentNegotiator());
+
return services;
}
}
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs
index b37a95c9c1..c9e2ff955b 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs
@@ -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();
+
+ // 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();
+
+ // 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();
+
+ // 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();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("Test User", user.Name);
+ Assert.Equal("text/json", response.Content.Headers.ContentType.MediaType);
+ }
}
}
#endif
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpErrorTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpErrorTest.cs
new file mode 100644
index 0000000000..3a30640193
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpErrorTest.cs
@@ -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