Correct polarity of MediaTypeHeaderValue.IsSubsetOf()` checks and remove one conneg fallback
- aspnet/Mvc#3138 part 2/2 - request's Content-Type header must be a subset of what an `IInputFormatter` can consume - `[Consumes]` is similar - what an `IOutputFormatter` produces must be a subset of the request's Accept header - `FormatFilter` and `ObjectResult` are similar - `ObjectResult` no longer falls back to `Content-Type` header if no `Accept` value is acceptable - left `WebApiCompatShim` code alone for consistency with down-level `System.Net.Http.Formatting` - correct tests to match new behaviour - do not test `Accept` values containing a `charset` parameter; that case is not valid WIP: - four test failures; something about comparing media types w/ charset included - why do some localization tests fail in VS? nits: - add `InputFormatterTests` - add / update comments and doc comments - correct xUnit attributes in `ActionResultTest`; odd it doesn't show up in command-line runs
This commit is contained in:
parent
2e2043f427
commit
03625c38af
|
|
@ -56,10 +56,10 @@ namespace Microsoft.AspNet.Mvc
|
||||||
MediaTypeHeaderValue requestContentType = null;
|
MediaTypeHeaderValue requestContentType = null;
|
||||||
MediaTypeHeaderValue.TryParse(context.HttpContext.Request.ContentType, out requestContentType);
|
MediaTypeHeaderValue.TryParse(context.HttpContext.Request.ContentType, out requestContentType);
|
||||||
|
|
||||||
// Only execute if this is the last filter before calling the action.
|
// Confirm the request's content type is more specific than a media type this action supports e.g. OK
|
||||||
// This ensures that we only run the filter which is closest to the action.
|
// if client sent "text/plain" data and this action supports "text/*".
|
||||||
if (requestContentType != null &&
|
if (requestContentType != null &&
|
||||||
!ContentTypes.Any(contentType => contentType.IsSubsetOf(requestContentType)))
|
!ContentTypes.Any(contentType => requestContentType.IsSubsetOf(contentType)))
|
||||||
{
|
{
|
||||||
context.Result = new UnsupportedMediaTypeResult();
|
context.Result = new UnsupportedMediaTypeResult();
|
||||||
}
|
}
|
||||||
|
|
@ -102,7 +102,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return !isActionWithoutConsumeConstraintPresent;
|
return !isActionWithoutConsumeConstraintPresent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ContentTypes.Any(c => c.IsSubsetOf(requestContentType)))
|
// Confirm the request's content type is more specific than a media type this action supports e.g. OK
|
||||||
|
// if client sent "text/plain" data and this action supports "text/*".
|
||||||
|
if (ContentTypes.Any(contentType => requestContentType.IsSubsetOf(contentType)))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
_cursor = new FilterCursor(_filters);
|
_cursor = new FilterCursor(_filters);
|
||||||
|
|
||||||
ActionContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
|
ActionContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
|
||||||
|
|
||||||
await InvokeAllAuthorizationFiltersAsync();
|
await InvokeAllAuthorizationFiltersAsync();
|
||||||
|
|
||||||
// If Authorization Filters return a result, it's a short circuit because
|
// If Authorization Filters return a result, it's a short circuit because
|
||||||
|
|
@ -663,7 +663,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
"Microsoft.AspNet.Mvc.BeforeActionMethod",
|
"Microsoft.AspNet.Mvc.BeforeActionMethod",
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
actionContext = ActionContext,
|
actionContext = ActionContext,
|
||||||
arguments = _actionExecutingContext.ActionArguments,
|
arguments = _actionExecutingContext.ActionArguments,
|
||||||
controller = _actionExecutingContext.Controller
|
controller = _actionExecutingContext.Controller
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Microsoft.Net.Http.Headers;
|
||||||
namespace Microsoft.AspNet.Mvc.Formatters
|
namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A filter which will use the format value in the route data or query string to set the content type on an
|
/// A filter which will use the format value in the route data or query string to set the content type on an
|
||||||
/// <see cref="ObjectResult" /> returned from an action.
|
/// <see cref="ObjectResult" /> returned from an action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FormatFilter : IFormatFilter, IResourceFilter, IResultFilter
|
public class FormatFilter : IFormatFilter, IResourceFilter, IResultFilter
|
||||||
|
|
@ -48,13 +48,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
public MediaTypeHeaderValue ContentType { get; }
|
public MediaTypeHeaderValue ContentType { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <c>true</c> if the current <see cref="FormatFilter"/> is active and will execute.
|
/// <c>true</c> if the current <see cref="FormatFilter"/> is active and will execute.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsActive { get; }
|
public bool IsActive { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// As a <see cref="IResourceFilter"/>, this filter looks at the request and rejects it before going ahead if
|
/// As a <see cref="IResourceFilter"/>, this filter looks at the request and rejects it before going ahead if
|
||||||
/// 1. The format in the request doesnt match any format in the map.
|
/// 1. The format in the request does not match any format in the map.
|
||||||
/// 2. If there is a conflicting producesFilter.
|
/// 2. If there is a conflicting producesFilter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The <see cref="ResourceExecutingContext"/>.</param>
|
/// <param name="context">The <see cref="ResourceExecutingContext"/>.</param>
|
||||||
|
|
@ -67,7 +67,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
|
|
||||||
if (!IsActive)
|
if (!IsActive)
|
||||||
{
|
{
|
||||||
return; // no format specified by user, so the filter is muted
|
// no format specified by user, so the filter is muted
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ContentType == null)
|
if (ContentType == null)
|
||||||
|
|
@ -77,19 +78,22 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine media types this action supports.
|
||||||
var responseTypeFilters = context.Filters.OfType<IApiResponseMetadataProvider>();
|
var responseTypeFilters = context.Filters.OfType<IApiResponseMetadataProvider>();
|
||||||
var contentTypes = new List<MediaTypeHeaderValue>();
|
var supportedMediaTypes = new List<MediaTypeHeaderValue>();
|
||||||
|
|
||||||
foreach (var filter in responseTypeFilters)
|
foreach (var filter in responseTypeFilters)
|
||||||
{
|
{
|
||||||
filter.SetContentTypes(contentTypes);
|
filter.SetContentTypes(supportedMediaTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentTypes.Count != 0)
|
// Check if support is adequate for requested media type.
|
||||||
|
if (supportedMediaTypes.Count != 0)
|
||||||
{
|
{
|
||||||
// We need to check if the action can generate the content type the user asked for. If it cannot, exit
|
// We need to check if the action can generate the content type the user asked for. That is, treat the
|
||||||
// here with not found result.
|
// request's format and IApiResponseMetadataProvider-provided content types similarly to an Accept
|
||||||
if (!contentTypes.Any(c => ContentType.IsSubsetOf(c)))
|
// header and an output formatter's SupportedMediaTypes: Confirm action supports a more specific media
|
||||||
|
// type than requested e.g. OK if "text/*" requested and action supports "text/plain".
|
||||||
|
if (!supportedMediaTypes.Any(contentType => contentType.IsSubsetOf(ContentType)))
|
||||||
{
|
{
|
||||||
context.Result = new HttpNotFoundResult();
|
context.Result = new HttpNotFoundResult();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,12 +62,17 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
|
|
||||||
var contentType = context.HttpContext.Request.ContentType;
|
var contentType = context.HttpContext.Request.ContentType;
|
||||||
MediaTypeHeaderValue requestContentType;
|
MediaTypeHeaderValue requestContentType;
|
||||||
if (!MediaTypeHeaderValue.TryParse(contentType, out requestContentType))
|
if (!MediaTypeHeaderValue.TryParse(contentType, out requestContentType) || requestContentType == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SupportedMediaTypes.Any(supportedMediaType => supportedMediaType.IsSubsetOf(requestContentType));
|
// Confirm the request's content type is more specific than a media type this formatter supports e.g. OK if
|
||||||
|
// client sent "text/plain" data and this formatter supports "text/*".
|
||||||
|
return SupportedMediaTypes.Any(supportedMediaType =>
|
||||||
|
{
|
||||||
|
return requestContentType.IsSubsetOf(supportedMediaType);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
{
|
{
|
||||||
List<MediaTypeHeaderValue> mediaTypes = null;
|
List<MediaTypeHeaderValue> mediaTypes = null;
|
||||||
|
|
||||||
|
// Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*"
|
||||||
|
// requested and formatter supports "text/plain". Treat contentType like it came from an Accept header.
|
||||||
foreach (var mediaType in _supportedMediaTypes)
|
foreach (var mediaType in _supportedMediaTypes)
|
||||||
{
|
{
|
||||||
if (mediaType.IsSubsetOf(contentType))
|
if (mediaType.IsSubsetOf(contentType))
|
||||||
|
|
@ -119,8 +121,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
{
|
{
|
||||||
var requestCharset = requestContentType.Charset;
|
var requestCharset = requestContentType.Charset;
|
||||||
encoding = SupportedEncodings.FirstOrDefault(
|
encoding = SupportedEncodings.FirstOrDefault(
|
||||||
supportedEncoding =>
|
supportedEncoding => requestCharset.Equals(supportedEncoding.WebName));
|
||||||
requestCharset.Equals(supportedEncoding.WebName));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,10 +152,11 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Since supportedMedia Type is going to be more specific check if supportedMediaType is a subset
|
// Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*"
|
||||||
// of the content type which is typically what we get on acceptHeader.
|
// requested and formatter supports "text/plain". contentType is typically what we got in an Accept
|
||||||
mediaType = SupportedMediaTypes
|
// header.
|
||||||
.FirstOrDefault(supportedMediaType => supportedMediaType.IsSubsetOf(contentType));
|
mediaType = SupportedMediaTypes.FirstOrDefault(
|
||||||
|
supportedMediaType => supportedMediaType.IsSubsetOf(contentType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType != null)
|
if (mediaType != null)
|
||||||
|
|
@ -201,11 +203,11 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
// Copy the media type as we don't want it to affect the next request
|
// Copy the media type as we don't want it to affect the next request
|
||||||
selectedMediaType = selectedMediaType.Copy();
|
selectedMediaType = selectedMediaType.Copy();
|
||||||
|
|
||||||
// Not text-based media types will use an encoding/charset - binary formats just ignore it. We want to
|
// Note text-based media types will use an encoding/charset - binary formats just ignore it. We want to
|
||||||
// make this class work with media types that use encodings, and those that don't.
|
// make this class work with media types that use encodings, and those that don't.
|
||||||
//
|
//
|
||||||
// The default implementation of SelectCharacterEncoding will read from the list of SupportedEncodings
|
// The default implementation of SelectCharacterEncoding will read from the list of SupportedEncodings
|
||||||
// and will always choose a default encoding if any are supported. So, the only cases where the
|
// and will always choose a default encoding if any are supported. So, the only cases where the
|
||||||
// selectedEncoding can be null are:
|
// selectedEncoding can be null are:
|
||||||
//
|
//
|
||||||
// 1). No supported encodings - we assume this is a non-text format
|
// 1). No supported encodings - we assume this is a non-text format
|
||||||
|
|
@ -237,21 +239,17 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
if (acceptCharsetHeaders != null && acceptCharsetHeaders.Count > 0)
|
if (acceptCharsetHeaders != null && acceptCharsetHeaders.Count > 0)
|
||||||
{
|
{
|
||||||
var sortedAcceptCharsetHeaders = acceptCharsetHeaders
|
var sortedAcceptCharsetHeaders = acceptCharsetHeaders
|
||||||
.Where(acceptCharset =>
|
.Where(acceptCharset => acceptCharset.Quality != HeaderQuality.NoMatch)
|
||||||
acceptCharset.Quality != HeaderQuality.NoMatch)
|
.OrderByDescending(m => m, StringWithQualityHeaderValueComparer.QualityComparer);
|
||||||
.OrderByDescending(
|
|
||||||
m => m, StringWithQualityHeaderValueComparer.QualityComparer);
|
|
||||||
|
|
||||||
foreach (var acceptCharset in sortedAcceptCharsetHeaders)
|
foreach (var acceptCharset in sortedAcceptCharsetHeaders)
|
||||||
{
|
{
|
||||||
var charset = acceptCharset.Value;
|
var charset = acceptCharset.Value;
|
||||||
if (!string.IsNullOrWhiteSpace(charset))
|
if (!string.IsNullOrWhiteSpace(charset))
|
||||||
{
|
{
|
||||||
var encoding = SupportedEncodings.FirstOrDefault(
|
var encoding = SupportedEncodings.FirstOrDefault(supportedEncoding =>
|
||||||
supportedEncoding =>
|
charset.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase) ||
|
||||||
charset.Equals(supportedEncoding.WebName,
|
charset.Equals("*", StringComparison.Ordinal));
|
||||||
StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
charset.Equals("*", StringComparison.Ordinal));
|
|
||||||
if (encoding != null)
|
if (encoding != null)
|
||||||
{
|
{
|
||||||
return encoding;
|
return encoding;
|
||||||
|
|
|
||||||
|
|
@ -86,17 +86,15 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
var logger = formatterContext.HttpContext.RequestServices.GetRequiredService<ILogger<ObjectResult>>();
|
var logger = formatterContext.HttpContext.RequestServices.GetRequiredService<ILogger<ObjectResult>>();
|
||||||
|
|
||||||
// Check if any content-type was explicitly set (for example, via ProducesAttribute
|
// Check if any content-type was explicitly set (for example, via ProducesAttribute
|
||||||
// or Url path extension mapping). If yes, then ignore content-negotiation and use this content-type.
|
// or URL path extension mapping). If yes, then ignore content-negotiation and use this content-type.
|
||||||
if (ContentTypes.Count == 1)
|
if (ContentTypes.Count == 1)
|
||||||
{
|
{
|
||||||
logger.LogVerbose(
|
logger.LogVerbose(
|
||||||
"Skipped content negotiation as content type '{ContentType}' is explicitly set for the response.",
|
"Skipped content negotiation as content type '{ContentType}' is explicitly set for the response.",
|
||||||
ContentTypes[0]);
|
ContentTypes[0]);
|
||||||
|
|
||||||
return SelectFormatterUsingAnyAcceptableContentType(formatterContext,
|
return SelectFormatterUsingAnyAcceptableContentType(formatterContext, formatters, ContentTypes);
|
||||||
formatters,
|
|
||||||
ContentTypes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sortedAcceptHeaderMediaTypes = GetSortedAcceptHeaderMediaTypes(formatterContext);
|
var sortedAcceptHeaderMediaTypes = GetSortedAcceptHeaderMediaTypes(formatterContext);
|
||||||
|
|
@ -106,11 +104,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Check if we have enough information to do content-negotiation, otherwise get the first formatter
|
// Check if we have enough information to do content-negotiation, otherwise get the first formatter
|
||||||
// which can write the type.
|
// which can write the type.
|
||||||
MediaTypeHeaderValue requestContentType = null;
|
if (!sortedAcceptHeaderMediaTypes.Any())
|
||||||
MediaTypeHeaderValue.TryParse(
|
|
||||||
formatterContext.HttpContext.Request.ContentType,
|
|
||||||
out requestContentType);
|
|
||||||
if (!sortedAcceptHeaderMediaTypes.Any() && requestContentType == null)
|
|
||||||
{
|
{
|
||||||
logger.LogVerbose("No information found on request to perform content negotiation.");
|
logger.LogVerbose("No information found on request to perform content negotiation.");
|
||||||
|
|
||||||
|
|
@ -122,25 +116,12 @@ namespace Microsoft.AspNet.Mvc
|
||||||
//
|
//
|
||||||
|
|
||||||
// 1. Select based on sorted accept headers.
|
// 1. Select based on sorted accept headers.
|
||||||
if (sortedAcceptHeaderMediaTypes.Any())
|
selectedFormatter = SelectFormatterUsingSortedAcceptHeaders(
|
||||||
{
|
formatterContext,
|
||||||
selectedFormatter = SelectFormatterUsingSortedAcceptHeaders(
|
formatters,
|
||||||
formatterContext,
|
sortedAcceptHeaderMediaTypes);
|
||||||
formatters,
|
|
||||||
sortedAcceptHeaderMediaTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. No formatter was found based on accept headers, fall back on request Content-Type header.
|
// 2. No formatter was found based on Accept header. Fallback to type-based match.
|
||||||
if (selectedFormatter == null && requestContentType != null)
|
|
||||||
{
|
|
||||||
selectedFormatter = SelectFormatterUsingAnyAcceptableContentType(
|
|
||||||
formatterContext,
|
|
||||||
formatters,
|
|
||||||
new[] { requestContentType });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. No formatter was found based on Accept and request Content-Type headers, so
|
|
||||||
// fallback on type based match.
|
|
||||||
if (selectedFormatter == null)
|
if (selectedFormatter == null)
|
||||||
{
|
{
|
||||||
logger.LogVerbose("Could not find an output formatter based on content negotiation.");
|
logger.LogVerbose("Could not find an output formatter based on content negotiation.");
|
||||||
|
|
@ -157,15 +138,15 @@ namespace Microsoft.AspNet.Mvc
|
||||||
if (sortedAcceptHeaderMediaTypes.Any())
|
if (sortedAcceptHeaderMediaTypes.Any())
|
||||||
{
|
{
|
||||||
// Filter and remove accept headers which cannot support any of the user specified content types.
|
// Filter and remove accept headers which cannot support any of the user specified content types.
|
||||||
|
// That is, confirm this result supports a more specific media type than requested e.g. OK if
|
||||||
|
// "text/*" requested and result supports "text/plain".
|
||||||
var filteredAndSortedAcceptHeaders = sortedAcceptHeaderMediaTypes
|
var filteredAndSortedAcceptHeaders = sortedAcceptHeaderMediaTypes
|
||||||
.Where(acceptHeader =>
|
.Where(acceptHeader => ContentTypes.Any(contentType => contentType.IsSubsetOf(acceptHeader)));
|
||||||
ContentTypes.Any(contentType =>
|
|
||||||
contentType.IsSubsetOf(acceptHeader)));
|
|
||||||
|
|
||||||
selectedFormatter = SelectFormatterUsingSortedAcceptHeaders(
|
selectedFormatter = SelectFormatterUsingSortedAcceptHeaders(
|
||||||
formatterContext,
|
formatterContext,
|
||||||
formatters,
|
formatters,
|
||||||
filteredAndSortedAcceptHeaders);
|
filteredAndSortedAcceptHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedFormatter == null)
|
if (selectedFormatter == null)
|
||||||
|
|
@ -177,9 +158,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
// In any of these cases, if the user has specified content types,
|
// In any of these cases, if the user has specified content types,
|
||||||
// do a last effort to find a formatter which can write any of the user specified content type.
|
// do a last effort to find a formatter which can write any of the user specified content type.
|
||||||
selectedFormatter = SelectFormatterUsingAnyAcceptableContentType(
|
selectedFormatter = SelectFormatterUsingAnyAcceptableContentType(
|
||||||
formatterContext,
|
formatterContext,
|
||||||
formatters,
|
formatters,
|
||||||
ContentTypes);
|
ContentTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,9 +183,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IOutputFormatter SelectFormatterUsingSortedAcceptHeaders(
|
public virtual IOutputFormatter SelectFormatterUsingSortedAcceptHeaders(
|
||||||
OutputFormatterContext formatterContext,
|
OutputFormatterContext formatterContext,
|
||||||
IEnumerable<IOutputFormatter> formatters,
|
IEnumerable<IOutputFormatter> formatters,
|
||||||
IEnumerable<MediaTypeHeaderValue> sortedAcceptHeaders)
|
IEnumerable<MediaTypeHeaderValue> sortedAcceptHeaders)
|
||||||
{
|
{
|
||||||
IOutputFormatter selectedFormatter = null;
|
IOutputFormatter selectedFormatter = null;
|
||||||
foreach (var contentType in sortedAcceptHeaders)
|
foreach (var contentType in sortedAcceptHeaders)
|
||||||
|
|
@ -212,8 +193,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
// Loop through each of the formatters and see if any one will support this
|
// Loop through each of the formatters and see if any one will support this
|
||||||
// mediaType Value.
|
// mediaType Value.
|
||||||
selectedFormatter = formatters.FirstOrDefault(
|
selectedFormatter = formatters.FirstOrDefault(
|
||||||
formatter =>
|
formatter => formatter.CanWriteResult(formatterContext, contentType));
|
||||||
formatter.CanWriteResult(formatterContext, contentType));
|
|
||||||
if (selectedFormatter != null)
|
if (selectedFormatter != null)
|
||||||
{
|
{
|
||||||
// we found our match.
|
// we found our match.
|
||||||
|
|
@ -225,15 +205,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IOutputFormatter SelectFormatterUsingAnyAcceptableContentType(
|
public virtual IOutputFormatter SelectFormatterUsingAnyAcceptableContentType(
|
||||||
OutputFormatterContext formatterContext,
|
OutputFormatterContext formatterContext,
|
||||||
IEnumerable<IOutputFormatter> formatters,
|
IEnumerable<IOutputFormatter> formatters,
|
||||||
IEnumerable<MediaTypeHeaderValue> acceptableContentTypes)
|
IEnumerable<MediaTypeHeaderValue> acceptableContentTypes)
|
||||||
{
|
{
|
||||||
var selectedFormatter = formatters.FirstOrDefault(
|
var selectedFormatter = formatters.FirstOrDefault(
|
||||||
formatter =>
|
formatter => acceptableContentTypes.Any(
|
||||||
acceptableContentTypes
|
contentType => formatter.CanWriteResult(formatterContext, contentType)));
|
||||||
.Any(contentType =>
|
|
||||||
formatter.CanWriteResult(formatterContext, contentType)));
|
|
||||||
return selectedFormatter;
|
return selectedFormatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,7 +243,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
if (respectAcceptHeader)
|
if (respectAcceptHeader)
|
||||||
{
|
{
|
||||||
sortedAcceptHeaderMediaTypes = SortMediaTypeHeaderValues(incomingAcceptHeaderMediaTypes)
|
sortedAcceptHeaderMediaTypes = SortMediaTypeHeaderValues(incomingAcceptHeaderMediaTypes)
|
||||||
.Where(header => header.Quality != HeaderQuality.NoMatch);
|
.Where(header => header.Quality != HeaderQuality.NoMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortedAcceptHeaderMediaTypes;
|
return sortedAcceptHeaderMediaTypes;
|
||||||
|
|
@ -286,9 +265,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Use OrderBy() instead of Array.Sort() as it performs fewer comparisons. In this case the comparisons
|
// Use OrderBy() instead of Array.Sort() as it performs fewer comparisons. In this case the comparisons
|
||||||
// are quite expensive so OrderBy() performs better.
|
// are quite expensive so OrderBy() performs better.
|
||||||
return headerValues.OrderByDescending(headerValue =>
|
return headerValues.OrderByDescending(
|
||||||
headerValue,
|
headerValue => headerValue,
|
||||||
MediaTypeHeaderValueComparer.QualityComparer);
|
MediaTypeHeaderValueComparer.QualityComparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<IOutputFormatter> GetDefaultFormatters(ActionContext context)
|
private IEnumerable<IOutputFormatter> GetDefaultFormatters(ActionContext context)
|
||||||
|
|
@ -302,7 +281,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
.GetRequiredService<IActionBindingContextAccessor>()
|
.GetRequiredService<IActionBindingContextAccessor>()
|
||||||
.ActionBindingContext;
|
.ActionBindingContext;
|
||||||
|
|
||||||
// In scenarios where there is a resource filter which directly shortcircuits using an ObjectResult.
|
// In scenarios where there is a resource filter which directly short-circuits using an ObjectResult.
|
||||||
// actionBindingContext is not setup yet and is null.
|
// actionBindingContext is not setup yet and is null.
|
||||||
if (actionBindingContext == null)
|
if (actionBindingContext == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,292 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Internal;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
|
{
|
||||||
|
public class InputFormatterTest
|
||||||
|
{
|
||||||
|
private class CatchAllFormatter : TestFormatter
|
||||||
|
{
|
||||||
|
public CatchAllFormatter()
|
||||||
|
{
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("*/*"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/mathml-content+xml")]
|
||||||
|
[InlineData("application/mathml-presentation+xml")]
|
||||||
|
[InlineData("application/mathml+xml; undefined=ignored")]
|
||||||
|
[InlineData("application/octet-stream; padding=3")]
|
||||||
|
[InlineData("application/xml")]
|
||||||
|
[InlineData("application/xml-dtd; undefined=ignored")]
|
||||||
|
[InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p")]
|
||||||
|
[InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p; undefined=ignored")]
|
||||||
|
[InlineData("text/html")]
|
||||||
|
public void CatchAll_CanRead_ReturnsTrueForSupportedMediaTypes(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new CatchAllFormatter();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = requestContentType;
|
||||||
|
var context = new InputFormatterContext(
|
||||||
|
httpContext,
|
||||||
|
modelName: string.Empty,
|
||||||
|
modelState: new ModelStateDictionary(),
|
||||||
|
modelType: typeof(void));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MultipartFormatter : TestFormatter
|
||||||
|
{
|
||||||
|
public MultipartFormatter()
|
||||||
|
{
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("multipart/*"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p")]
|
||||||
|
[InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p; undefined=ignored")]
|
||||||
|
public void MultipartFormatter_CanRead_ReturnsTrueForSupportedMediaTypes(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new MultipartFormatter();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = requestContentType;
|
||||||
|
var context = new InputFormatterContext(
|
||||||
|
httpContext,
|
||||||
|
modelName: string.Empty,
|
||||||
|
modelState: new ModelStateDictionary(),
|
||||||
|
modelType: typeof(void));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/mathml-content+xml")]
|
||||||
|
[InlineData("application/mathml-presentation+xml")]
|
||||||
|
[InlineData("application/mathml+xml; undefined=ignored")]
|
||||||
|
[InlineData("application/octet-stream; padding=3")]
|
||||||
|
[InlineData("application/xml")]
|
||||||
|
[InlineData("application/xml-dtd; undefined=ignored")]
|
||||||
|
[InlineData("text/html")]
|
||||||
|
public void MultipartFormatter_CanRead_ReturnsFalseForUnsupportedMediaTypes(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new MultipartFormatter();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = requestContentType;
|
||||||
|
var context = new InputFormatterContext(
|
||||||
|
httpContext,
|
||||||
|
modelName: string.Empty,
|
||||||
|
modelState: new ModelStateDictionary(),
|
||||||
|
modelType: typeof(void));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MultipartMixedFormatter : TestFormatter
|
||||||
|
{
|
||||||
|
public MultipartMixedFormatter()
|
||||||
|
{
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("multipart/mixed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p")]
|
||||||
|
[InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p; undefined=ignored")]
|
||||||
|
public void MultipartMixedFormatter_CanRead_ReturnsTrueForSupportedMediaTypes(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new MultipartMixedFormatter();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = requestContentType;
|
||||||
|
var context = new InputFormatterContext(
|
||||||
|
httpContext,
|
||||||
|
modelName: string.Empty,
|
||||||
|
modelState: new ModelStateDictionary(),
|
||||||
|
modelType: typeof(void));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/mathml-content+xml")]
|
||||||
|
[InlineData("application/mathml-presentation+xml")]
|
||||||
|
[InlineData("application/mathml+xml; undefined=ignored")]
|
||||||
|
[InlineData("application/octet-stream; padding=3")]
|
||||||
|
[InlineData("application/xml")]
|
||||||
|
[InlineData("application/xml-dtd; undefined=ignored")]
|
||||||
|
[InlineData("text/html")]
|
||||||
|
public void MultipartMixedFormatter_CanRead_ReturnsFalseForUnsupportedMediaTypes(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new MultipartMixedFormatter();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = requestContentType;
|
||||||
|
var context = new InputFormatterContext(
|
||||||
|
httpContext,
|
||||||
|
modelName: string.Empty,
|
||||||
|
modelState: new ModelStateDictionary(),
|
||||||
|
modelType: typeof(void));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MathMLFormatter : TestFormatter
|
||||||
|
{
|
||||||
|
public MathMLFormatter()
|
||||||
|
{
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/mathml-content+xml"));
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/mathml-presentation+xml"));
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/mathml+xml"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/mathml-content+xml")]
|
||||||
|
[InlineData("application/mathml-presentation+xml")]
|
||||||
|
[InlineData("application/mathml+xml; undefined=ignored")]
|
||||||
|
public void MathMLFormatter_CanRead_ReturnsTrueForSupportedMediaTypes(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new MathMLFormatter();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = requestContentType;
|
||||||
|
var context = new InputFormatterContext(
|
||||||
|
httpContext,
|
||||||
|
modelName: string.Empty,
|
||||||
|
modelState: new ModelStateDictionary(),
|
||||||
|
modelType: typeof(void));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/octet-stream; padding=3")]
|
||||||
|
[InlineData("application/xml")]
|
||||||
|
[InlineData("application/xml-dtd; undefined=ignored")]
|
||||||
|
[InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p")]
|
||||||
|
[InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p; undefined=ignored")]
|
||||||
|
[InlineData("text/html")]
|
||||||
|
public void MathMLFormatter_CanRead_ReturnsFalseForUnsupportedMediaTypes(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new MathMLFormatter();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = requestContentType;
|
||||||
|
var context = new InputFormatterContext(
|
||||||
|
httpContext,
|
||||||
|
modelName: string.Empty,
|
||||||
|
modelState: new ModelStateDictionary(),
|
||||||
|
modelType: typeof(void));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSubsetOf does not follow XML media type conventions. This formatter does not support "application/*+xml".
|
||||||
|
private class XmlFormatter : TestFormatter
|
||||||
|
{
|
||||||
|
public XmlFormatter()
|
||||||
|
{
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml"));
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/xml")]
|
||||||
|
public void XMLFormatter_CanRead_ReturnsTrueForSupportedMediaTypes(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new XmlFormatter();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = requestContentType;
|
||||||
|
var context = new InputFormatterContext(
|
||||||
|
httpContext,
|
||||||
|
modelName: string.Empty,
|
||||||
|
modelState: new ModelStateDictionary(),
|
||||||
|
modelType: typeof(void));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/mathml-content+xml")]
|
||||||
|
[InlineData("application/mathml-presentation+xml")]
|
||||||
|
[InlineData("application/mathml+xml; undefined=ignored")]
|
||||||
|
[InlineData("application/octet-stream; padding=3")]
|
||||||
|
[InlineData("application/xml-dtd; undefined=ignored")]
|
||||||
|
[InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p")]
|
||||||
|
[InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p; undefined=ignored")]
|
||||||
|
[InlineData("text/html")]
|
||||||
|
public void XMLFormatter_CanRead_ReturnsFalseForUnsupportedMediaTypes(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new XmlFormatter();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = requestContentType;
|
||||||
|
var context = new InputFormatterContext(
|
||||||
|
httpContext,
|
||||||
|
modelName: string.Empty,
|
||||||
|
modelState: new ModelStateDictionary(),
|
||||||
|
modelType: typeof(void));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = formatter.CanRead(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestFormatter : InputFormatter
|
||||||
|
{
|
||||||
|
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,6 @@ using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Abstractions;
|
using Microsoft.AspNet.Mvc.Abstractions;
|
||||||
using Microsoft.AspNet.Mvc.Formatters;
|
using Microsoft.AspNet.Mvc.Formatters;
|
||||||
using Microsoft.AspNet.Mvc.Formatters.Xml;
|
|
||||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.AspNet.Testing.xunit;
|
using Microsoft.AspNet.Testing.xunit;
|
||||||
|
|
@ -133,7 +132,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
var result = new ObjectResult(input);
|
var result = new ObjectResult(input);
|
||||||
result.ContentTypes = new List<MediaTypeHeaderValue>();
|
result.ContentTypes = new List<MediaTypeHeaderValue>();
|
||||||
result.ContentTypes.Add(MediaTypeHeaderValue.Parse(expectedContentType));
|
result.ContentTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await result.ExecuteResultAsync(actionContext);
|
await result.ExecuteResultAsync(actionContext);
|
||||||
|
|
@ -157,7 +156,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
// Set the content type property explicitly to a single value.
|
// Set the content type property explicitly to a single value.
|
||||||
var result = new ObjectResult(input);
|
var result = new ObjectResult(input);
|
||||||
result.ContentTypes = new List<MediaTypeHeaderValue>();
|
result.ContentTypes = new List<MediaTypeHeaderValue>();
|
||||||
result.ContentTypes.Add(MediaTypeHeaderValue.Parse(expectedContentType));
|
result.ContentTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await result.ExecuteResultAsync(actionContext);
|
await result.ExecuteResultAsync(actionContext);
|
||||||
|
|
@ -338,23 +337,23 @@ namespace Microsoft.AspNet.Mvc
|
||||||
[InlineData("application/xml")]
|
[InlineData("application/xml")]
|
||||||
[InlineData("application/custom")]
|
[InlineData("application/custom")]
|
||||||
[InlineData("application/xml;q=1, application/custom;q=0.8")]
|
[InlineData("application/xml;q=1, application/custom;q=0.8")]
|
||||||
public void SelectFormatter_WithNoMatchingAcceptHeadersAndRequestContentType_PicksFormatterBasedOnObjectType
|
public void SelectFormatter_WithNoMatchingAcceptHeader_PicksFormatterBasedOnObjectType(string acceptHeader)
|
||||||
(string acceptHeader)
|
|
||||||
{
|
{
|
||||||
// For no accept headers,
|
// For no Accept headers, CanWriteResult is called once for the type match pass. For each additional Accept
|
||||||
// can write is called twice once for the request Content-Type and once for the type match pass.
|
// header, it is called once.
|
||||||
// For each additional accept header, it is called once.
|
|
||||||
// Arrange
|
// Arrange
|
||||||
var acceptHeaderCollection = string.IsNullOrEmpty(acceptHeader) ?
|
var acceptHeaderCollection = string.IsNullOrEmpty(acceptHeader) ?
|
||||||
null : MediaTypeHeaderValue.ParseList(new[] { acceptHeader }).ToArray();
|
null :
|
||||||
|
MediaTypeHeaderValue.ParseList(new[] { acceptHeader }).ToArray();
|
||||||
var stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
var httpResponse = new Mock<HttpResponse>();
|
var httpResponse = new Mock<HttpResponse>();
|
||||||
httpResponse.SetupProperty<string>(o => o.ContentType);
|
httpResponse.SetupProperty<string>(o => o.ContentType);
|
||||||
httpResponse.SetupGet(r => r.Body).Returns(stream);
|
httpResponse.SetupGet(r => r.Body).Returns(stream);
|
||||||
|
|
||||||
var actionContext = CreateMockActionContext(httpResponse.Object,
|
var actionContext = CreateMockActionContext(
|
||||||
requestAcceptHeader: acceptHeader,
|
httpResponse.Object,
|
||||||
requestContentType: "application/xml");
|
requestAcceptHeader: acceptHeader,
|
||||||
|
requestContentType: "application/text");
|
||||||
var input = "testInput";
|
var input = "testInput";
|
||||||
var result = new ObjectResult(input);
|
var result = new ObjectResult(input);
|
||||||
var mockCountingFormatter = new Mock<IOutputFormatter>();
|
var mockCountingFormatter = new Mock<IOutputFormatter>();
|
||||||
|
|
@ -365,35 +364,40 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Object = input,
|
Object = input,
|
||||||
DeclaredType = typeof(string)
|
DeclaredType = typeof(string)
|
||||||
};
|
};
|
||||||
var mockCountingSupportedContentType = MediaTypeHeaderValue.Parse("application/text");
|
var requestContentType = MediaTypeHeaderValue.Parse("application/text");
|
||||||
mockCountingFormatter.Setup(o => o.CanWriteResult(context,
|
mockCountingFormatter
|
||||||
It.Is<MediaTypeHeaderValue>(mth => mth == null)))
|
.Setup(o => o.CanWriteResult(context, It.Is<MediaTypeHeaderValue>(mth => mth == null)))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
mockCountingFormatter.Setup(o => o.CanWriteResult(context, mockCountingSupportedContentType))
|
mockCountingFormatter
|
||||||
.Returns(true);
|
.Setup(o => o.CanWriteResult(context, requestContentType))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
// Set more than one formatters. The test output formatter throws on write.
|
// Set more than one formatters. The test output formatter throws on write.
|
||||||
result.Formatters = new List<IOutputFormatter>
|
result.Formatters = new List<IOutputFormatter>
|
||||||
{
|
{
|
||||||
new CannotWriteFormatter(),
|
new CannotWriteFormatter(),
|
||||||
mockCountingFormatter.Object,
|
mockCountingFormatter.Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var formatter = result.SelectFormatter(context, result.Formatters);
|
var formatter = result.SelectFormatter(context, result.Formatters);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(mockCountingFormatter.Object, formatter);
|
Assert.Equal(mockCountingFormatter.Object, formatter);
|
||||||
|
|
||||||
|
// CanWriteResult is called once for the type-based match.
|
||||||
mockCountingFormatter.Verify(v => v.CanWriteResult(context, null), Times.Once());
|
mockCountingFormatter.Verify(v => v.CanWriteResult(context, null), Times.Once());
|
||||||
|
|
||||||
// CanWriteResult is invoked for the following cases:
|
// CanWriteResult is never called for the request's Content-Type.
|
||||||
// 1. For each accept header present
|
mockCountingFormatter.Verify(v => v.CanWriteResult(context, requestContentType), Times.Never());
|
||||||
// 2. Request Content-Type
|
|
||||||
// 3. Type based match
|
// In total, CanWriteResult is invoked for the following cases:
|
||||||
var callCount = (acceptHeaderCollection == null ? 0 : acceptHeaderCollection.Count()) + 2;
|
// 1. For each Accept header present
|
||||||
mockCountingFormatter.Verify(v => v.CanWriteResult(context,
|
// 2. Type-based match
|
||||||
It.IsNotIn<MediaTypeHeaderValue>(mockCountingSupportedContentType)),
|
var callCount = (acceptHeaderCollection == null ? 0 : acceptHeaderCollection.Count()) + 1;
|
||||||
Times.Exactly(callCount));
|
mockCountingFormatter.Verify(
|
||||||
|
v => v.CanWriteResult(It.IsAny< OutputFormatterContext>(), It.IsAny<MediaTypeHeaderValue>()),
|
||||||
|
Times.Exactly(callCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("application/json", true)]
|
[InlineData("application/json", true)]
|
||||||
[InlineData("application/*", true)]
|
[InlineData("application/*", false)]
|
||||||
[InlineData("*/*", true)]
|
[InlineData("*/*", false)]
|
||||||
[InlineData("text/json", true)]
|
[InlineData("text/json", true)]
|
||||||
[InlineData("text/*", true)]
|
[InlineData("text/*", false)]
|
||||||
[InlineData("text/xml", false)]
|
[InlineData("text/xml", false)]
|
||||||
[InlineData("application/xml", false)]
|
[InlineData("application/xml", false)]
|
||||||
[InlineData("", false)]
|
[InlineData("", false)]
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("application/json-patch+json", true)]
|
[InlineData("application/json-patch+json", true)]
|
||||||
[InlineData("application/json", false)]
|
[InlineData("application/json", false)]
|
||||||
[InlineData("application/*", true)]
|
[InlineData("application/*", false)]
|
||||||
[InlineData("*/*", true)]
|
[InlineData("*/*", false)]
|
||||||
public void CanRead_ReturnsTrueOnlyForJsonPatchContentType(string requestContentType, bool expectedCanRead)
|
public void CanRead_ReturnsTrueOnlyForJsonPatchContentType(string requestContentType, bool expectedCanRead)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,10 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
|
||||||
// Mono issue - https://github.com/aspnet/External/issues/18
|
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||||
[InlineData("application/xml", true)]
|
[InlineData("application/xml", true)]
|
||||||
[InlineData("application/*", true)]
|
[InlineData("application/*", false)]
|
||||||
[InlineData("*/*", true)]
|
[InlineData("*/*", false)]
|
||||||
[InlineData("text/xml", true)]
|
[InlineData("text/xml", true)]
|
||||||
[InlineData("text/*", true)]
|
[InlineData("text/*", false)]
|
||||||
[InlineData("text/json", false)]
|
[InlineData("text/json", false)]
|
||||||
[InlineData("application/json", false)]
|
[InlineData("application/json", false)]
|
||||||
[InlineData("", false)]
|
[InlineData("", false)]
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,10 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("application/xml", true)]
|
[InlineData("application/xml", true)]
|
||||||
[InlineData("application/*", true)]
|
[InlineData("application/*", false)]
|
||||||
[InlineData("*/*", true)]
|
[InlineData("*/*", false)]
|
||||||
[InlineData("text/xml", true)]
|
[InlineData("text/xml", true)]
|
||||||
[InlineData("text/*", true)]
|
[InlineData("text/*", false)]
|
||||||
[InlineData("text/json", false)]
|
[InlineData("text/json", false)]
|
||||||
[InlineData("application/json", false)]
|
[InlineData("application/json", false)]
|
||||||
[InlineData("", false)]
|
[InlineData("", false)]
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
"<DummyClass xmlns=\"http://schemas.datacontract.org/2004/07/ActionResultsWebSite\">" +
|
"<DummyClass xmlns=\"http://schemas.datacontract.org/2004/07/ActionResultsWebSite\">" +
|
||||||
"<SampleInt>2</SampleInt><SampleString>foo</SampleString></DummyClass>";
|
"<SampleInt>2</SampleInt><SampleString>foo</SampleString></DummyClass>";
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, "Home/GetCustomErrorObject");
|
var request = new HttpRequestMessage(HttpMethod.Post, "Home/GetCustomErrorObject");
|
||||||
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;charset=utf-8"));
|
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
||||||
request.Content = new StringContent(input, Encoding.UTF8, "application/xml");
|
request.Content = new StringContent(input, Encoding.UTF8, "application/xml");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -218,7 +218,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal("content", await response.Content.ReadAsStringAsync());
|
Assert.Equal("content", await response.Content.ReadAsStringAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
public async Task ContentResult_WritesContent_SetsContentTypeAndEncoding()
|
public async Task ContentResult_WritesContent_SetsContentTypeAndEncoding()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("application/json")]
|
[InlineData("application/json")]
|
||||||
[InlineData("application/*")]
|
|
||||||
[InlineData("*/*")]
|
|
||||||
[InlineData("text/json")]
|
[InlineData("text/json")]
|
||||||
[InlineData("text/*")]
|
|
||||||
public async Task JsonInputFormatter_IsSelectedForJsonRequest(string requestContentType)
|
public async Task JsonInputFormatter_IsSelectedForJsonRequest(string requestContentType)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Home/ReturnUser");
|
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Home/ReturnUser");
|
||||||
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml;charset=utf-8"));
|
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.SendAsync(request);
|
var response = await Client.SendAsync(request);
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
public HttpClient Client { get; }
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("application/xml,*/*;0.2")]
|
[InlineData("application/xml,*/*;q=0.2")]
|
||||||
[InlineData("application/xml,*/*")]
|
[InlineData("application/xml,*/*")]
|
||||||
public async Task AllMediaRangeAcceptHeader_FirstFormatterInListWritesResponse(string acceptHeader)
|
public async Task AllMediaRangeAcceptHeader_FirstFormatterInListWritesResponse(string acceptHeader)
|
||||||
{
|
{
|
||||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
[ConditionalTheory]
|
[ConditionalTheory]
|
||||||
// Mono issue - https://github.com/aspnet/External/issues/18
|
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||||
[InlineData("application/xml,*/*;0.2")]
|
[InlineData("application/xml,*/*;q=0.2")]
|
||||||
[InlineData("application/xml,*/*")]
|
[InlineData("application/xml,*/*")]
|
||||||
public async Task AllMediaRangeAcceptHeader_ProducesAttributeIsHonored(string acceptHeader)
|
public async Task AllMediaRangeAcceptHeader_ProducesAttributeIsHonored(string acceptHeader)
|
||||||
{
|
{
|
||||||
|
|
@ -71,9 +71,41 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
[ConditionalTheory]
|
[ConditionalTheory]
|
||||||
// Mono issue - https://github.com/aspnet/External/issues/18
|
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||||
[InlineData("application/xml,*/*;0.2")]
|
[InlineData("application/xml,*/*;q=0.2")]
|
||||||
[InlineData("application/xml,*/*")]
|
[InlineData("application/xml,*/*")]
|
||||||
public async Task AllMediaRangeAcceptHeader_WithContentTypeHeader_ContentTypeIsHonored(string acceptHeader)
|
public async Task AllMediaRangeAcceptHeader_WithContentTypeHeader_ContentTypeIsIgnored(string acceptHeader)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var requestData =
|
||||||
|
"<RespectBrowserAcceptHeaderController.Employee xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"" +
|
||||||
|
" xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite.Controllers\"><Id>35</Id><Name>Jimmy" +
|
||||||
|
"</Name></RespectBrowserAcceptHeaderController.Employee>";
|
||||||
|
var expectedResponseData = @"{""Id"":35,""Name"":""Jimmy""}";
|
||||||
|
var request = RequestWithAccept("http://localhost/RespectBrowserAcceptHeader/CreateEmployee", acceptHeader);
|
||||||
|
request.Content = new StringContent(requestData, Encoding.UTF8, "application/xml");
|
||||||
|
request.Method = HttpMethod.Post;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
Assert.NotNull(response.Content.Headers.ContentType);
|
||||||
|
|
||||||
|
// Site uses default output formatter (ignores Accept header) because that header contained a wildcard match.
|
||||||
|
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||||
|
|
||||||
|
var responseData = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(expectedResponseData, responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||||
|
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||||
|
[InlineData("application/xml,application/json;q=0.2")]
|
||||||
|
[InlineData("application/xml,application/json")]
|
||||||
|
public async Task AllMediaRangeAcceptHeader_WithExactMatch_ReturnsExpectedContent(string acceptHeader)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var requestData =
|
var requestData =
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var request = new HttpRequestMessage(
|
var request = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
"http://localhost/Home/GetDummyClass?sampleInput=10");
|
"http://localhost/Home/GetDummyClass?sampleInput=10");
|
||||||
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml;charset=utf-8"));
|
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.SendAsync(request);
|
var response = await Client.SendAsync(request);
|
||||||
|
|
@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var request = new HttpRequestMessage(
|
var request = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
"http://localhost/XmlSerializer/GetDummyClass?sampleInput=10");
|
"http://localhost/XmlSerializer/GetDummyClass?sampleInput=10");
|
||||||
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml;charset=utf-8"));
|
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.SendAsync(request);
|
var response = await Client.SendAsync(request);
|
||||||
|
|
@ -72,7 +72,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var request = new HttpRequestMessage(
|
var request = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
"http://localhost/DataContractSerializer/GetPerson?name=HelloWorld");
|
"http://localhost/DataContractSerializer/GetPerson?name=HelloWorld");
|
||||||
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml;charset=utf-8"));
|
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.SendAsync(request);
|
var response = await Client.SendAsync(request);
|
||||||
|
|
@ -93,7 +93,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var request = new HttpRequestMessage(
|
var request = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
"http://localhost/XmlSerializer/GetDerivedDummyClass?sampleInput=10");
|
"http://localhost/XmlSerializer/GetDerivedDummyClass?sampleInput=10");
|
||||||
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml;charset=utf-8"));
|
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.SendAsync(request);
|
var response = await Client.SendAsync(request);
|
||||||
|
|
@ -116,7 +116,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var request = new HttpRequestMessage(
|
var request = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
"http://localhost/Home/GetDerivedDummyClass?sampleInput=10");
|
"http://localhost/Home/GetDerivedDummyClass?sampleInput=10");
|
||||||
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml;charset=utf-8"));
|
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.SendAsync(request);
|
var response = await Client.SendAsync(request);
|
||||||
|
|
@ -137,7 +137,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var request = new HttpRequestMessage(
|
var request = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
"http://localhost/XmlSerializer/GetDictionary");
|
"http://localhost/XmlSerializer/GetDictionary");
|
||||||
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml;charset=utf-8"));
|
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.SendAsync(request);
|
var response = await Client.SendAsync(request);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue