parent
c47825944d
commit
4f351bd37c
|
|
@ -56,6 +56,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// </remarks>
|
||||
public virtual StringSegment ContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value to indicate whether the content type was specified by server-side code.
|
||||
/// This allows <see cref="IOutputFormatter.CanWriteResult(OutputFormatterCanWriteContext)"/> to
|
||||
/// implement stricter filtering on content types that, for example, are being considered purely
|
||||
/// of an incoming Accept header.
|
||||
/// </summary>
|
||||
public virtual bool ContentTypeIsServerDefined { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the object to write to the response.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
{
|
||||
Type = new StringSegment();
|
||||
SubType = new StringSegment();
|
||||
SubTypeWithoutSuffix = new StringSegment();
|
||||
SubTypeSuffix = new StringSegment();
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
|
@ -88,11 +90,24 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
if (subTypeLength == 0)
|
||||
{
|
||||
SubType = new StringSegment();
|
||||
SubTypeWithoutSuffix = new StringSegment();
|
||||
SubTypeSuffix = new StringSegment();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
SubType = subType;
|
||||
|
||||
if (TryGetSuffixLength(subType, out var subtypeSuffixLength))
|
||||
{
|
||||
SubTypeWithoutSuffix = subType.Subsegment(0, subType.Length - subtypeSuffixLength - 1);
|
||||
SubTypeSuffix = subType.Subsegment(subType.Length - subtypeSuffixLength, subtypeSuffixLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubTypeWithoutSuffix = SubType;
|
||||
SubTypeSuffix = new StringSegment();
|
||||
}
|
||||
}
|
||||
|
||||
_parameterParser = new MediaTypeParameterParser(mediaType, offset + typeLength + subTypeLength, length);
|
||||
|
|
@ -158,9 +173,29 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
return current - offset;
|
||||
}
|
||||
|
||||
private static bool TryGetSuffixLength(StringSegment subType, out int suffixLength)
|
||||
{
|
||||
// Find the last instance of '+', if there is one
|
||||
var startPos = subType.Offset + subType.Length - 1;
|
||||
for (var currentPos = startPos; currentPos >= subType.Offset; currentPos--)
|
||||
{
|
||||
if (subType.Buffer[currentPos] == '+')
|
||||
{
|
||||
suffixLength = startPos - currentPos;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
suffixLength = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the <see cref="MediaType"/>.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// For the media type <c>"application/json"</c>, the property gives the value <c>"application"</c>.
|
||||
/// </example>
|
||||
public StringSegment Type { get; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -171,13 +206,52 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// <summary>
|
||||
/// Gets the subtype of the <see cref="MediaType"/>.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// For the media type <c>"application/vnd.example+json"</c>, the property gives the value
|
||||
/// <c>"vnd.example+json"</c>.
|
||||
/// </example>
|
||||
public StringSegment SubType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subtype of the <see cref="MediaType"/>, excluding any structured syntax suffix.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// For the media type <c>"application/vnd.example+json"</c>, the property gives the value
|
||||
/// <c>"vnd.example"</c>.
|
||||
/// </example>
|
||||
public StringSegment SubTypeWithoutSuffix { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the structured syntax suffix of the <see cref="MediaType"/> if it has one.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// For the media type <c>"application/vnd.example+json"</c>, the property gives the value
|
||||
/// <c>"json"</c>.
|
||||
/// </example>
|
||||
public StringSegment SubTypeSuffix { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this <see cref="MediaType"/> matches all subtypes.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// For the media type <c>"application/*"</c>, this property is <c>true</c>.
|
||||
/// </example>
|
||||
/// <example>
|
||||
/// For the media type <c>"application/json"</c>, this property is <c>false</c>.
|
||||
/// </example>
|
||||
public bool MatchesAllSubTypes => SubType.Equals("*", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this <see cref="MediaType"/> matches all subtypes, ignoring any structured syntax suffix.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// For the media type <c>"application/*+json"</c>, this property is <c>true</c>.
|
||||
/// </example>
|
||||
/// <example>
|
||||
/// For the media type <c>"application/vnd.example+json"</c>, this property is <c>false</c>.
|
||||
/// </example>
|
||||
public bool MatchesAllSubTypesWithoutSuffix => SubTypeWithoutSuffix.Equals("*", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="System.Text.Encoding"/> of the <see cref="MediaType"/> if it has one.
|
||||
/// </summary>
|
||||
|
|
@ -188,6 +262,22 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// </summary>
|
||||
public StringSegment Charset => GetParameter("charset");
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current <see cref="MediaType"/> contains a wildcard.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if this <see cref="MediaType"/> contains a wildcard; otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
public bool HasWildcard
|
||||
{
|
||||
get
|
||||
{
|
||||
return MatchesAllTypes ||
|
||||
MatchesAllSubTypesWithoutSuffix ||
|
||||
GetParameter("*").Equals("*", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current <see cref="MediaType"/> is a subset of the <paramref name="set"/>
|
||||
/// <see cref="MediaType"/>.
|
||||
|
|
@ -198,8 +288,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// </returns>
|
||||
public bool IsSubsetOf(MediaType set)
|
||||
{
|
||||
return (set.MatchesAllTypes || set.Type.Equals(Type, StringComparison.OrdinalIgnoreCase)) &&
|
||||
(set.MatchesAllSubTypes || set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase)) &&
|
||||
return MatchesType(set) &&
|
||||
MatchesSubtype(set) &&
|
||||
ContainsAllParameters(set._parameterParser);
|
||||
}
|
||||
|
||||
|
|
@ -372,6 +462,53 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
return $"{mediaType.Value}; charset={encoding.WebName}";
|
||||
}
|
||||
|
||||
private bool MatchesType(MediaType set)
|
||||
{
|
||||
return set.MatchesAllTypes ||
|
||||
set.Type.Equals(Type, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private bool MatchesSubtype(MediaType set)
|
||||
{
|
||||
if (set.MatchesAllSubTypes)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (set.SubTypeSuffix.HasValue)
|
||||
{
|
||||
if (SubTypeSuffix.HasValue)
|
||||
{
|
||||
// Both the set and the media type being checked have suffixes, so both parts must match.
|
||||
return MatchesSubtypeWithoutSuffix(set) && MatchesSubtypeSuffix(set);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The set has a suffix, but the media type being checked doesn't. We never consider this to match.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The set has no suffix, so we're just looking for an exact match (which means that if 'this'
|
||||
// has a suffix, it won't match).
|
||||
return set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchesSubtypeWithoutSuffix(MediaType set)
|
||||
{
|
||||
return set.MatchesAllSubTypesWithoutSuffix ||
|
||||
set.SubTypeWithoutSuffix.Equals(SubTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private bool MatchesSubtypeSuffix(MediaType set)
|
||||
{
|
||||
// We don't have support for wildcards on suffixes alone (e.g., "application/entity+*")
|
||||
// because there's no clear use case for it.
|
||||
return set.SubTypeSuffix.Equals(SubTypeSuffix, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private bool ContainsAllParameters(MediaTypeParameterParser setParameters)
|
||||
{
|
||||
var parameterFound = true;
|
||||
|
|
@ -385,6 +522,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
break;
|
||||
}
|
||||
|
||||
if (setParameter.HasName("*"))
|
||||
{
|
||||
// A parameter named "*" has no effect on media type matching, as it is only used as an indication
|
||||
// that the entire media type string should be treated as a wildcard.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Copy the parser as we need to iterate multiple times over it.
|
||||
// We can do this because it's a struct
|
||||
var subSetParameters = _parameterParser;
|
||||
|
|
@ -452,8 +596,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
if (nameLength == 0 || OffsetIsOutOfRange(current, input.Length) || input[current] != '=')
|
||||
{
|
||||
parsedValue = default(MediaTypeParameter);
|
||||
return 0;
|
||||
if (current == input.Length && name.Equals("*", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// As a special case, we allow a trailing ";*" to indicate a wildcard
|
||||
// string allowing any other parameters. It's the same as ";*=*".
|
||||
var asterisk = new StringSegment("*");
|
||||
parsedValue = new MediaTypeParameter(asterisk, asterisk);
|
||||
return current - startIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
parsedValue = default(MediaTypeParameter);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
StringSegment value;
|
||||
|
|
|
|||
|
|
@ -49,24 +49,35 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<string> mediaTypes = null;
|
||||
|
||||
if (contentType == null)
|
||||
var parsedContentType = contentType != null ? new MediaType(contentType) : default(MediaType);
|
||||
|
||||
foreach (var mediaType in SupportedMediaTypes)
|
||||
{
|
||||
// If contentType is null, then any type we support is valid.
|
||||
return SupportedMediaTypes;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<string> mediaTypes = null;
|
||||
|
||||
var parsedContentType = new MediaType(contentType);
|
||||
|
||||
// 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)
|
||||
var parsedMediaType = new MediaType(mediaType);
|
||||
if (parsedMediaType.HasWildcard)
|
||||
{
|
||||
var parsedMediaType = new MediaType(mediaType);
|
||||
if (parsedMediaType.IsSubsetOf(parsedContentType))
|
||||
// For supported media types that are wildcard patterns, confirm that the requested
|
||||
// media type satisfies the wildcard pattern (e.g., if "text/entity+json;v=2" requested
|
||||
// and formatter supports "text/*+json").
|
||||
// Treat contentType like it came from a [Produces] attribute.
|
||||
if (contentType != null && parsedContentType.IsSubsetOf(parsedMediaType))
|
||||
{
|
||||
if (mediaTypes == null)
|
||||
{
|
||||
mediaTypes = new List<string>();
|
||||
}
|
||||
|
||||
mediaTypes.Add(contentType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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.
|
||||
if (contentType == null || parsedMediaType.IsSubsetOf(parsedContentType))
|
||||
{
|
||||
if (mediaTypes == null)
|
||||
{
|
||||
|
|
@ -76,9 +87,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
mediaTypes.Add(mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
return mediaTypes;
|
||||
}
|
||||
|
||||
return mediaTypes;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -112,17 +123,36 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
else
|
||||
{
|
||||
// Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*"
|
||||
// requested and formatter supports "text/plain". contentType is typically what we got in an Accept
|
||||
// header.
|
||||
// Confirm this formatter
|
||||
var parsedContentType = new MediaType(context.ContentType);
|
||||
for (var i = 0; i < SupportedMediaTypes.Count; i++)
|
||||
{
|
||||
var supportedMediaType = new MediaType(SupportedMediaTypes[i]);
|
||||
if (supportedMediaType.IsSubsetOf(parsedContentType))
|
||||
if (supportedMediaType.HasWildcard)
|
||||
{
|
||||
context.ContentType = new StringSegment(SupportedMediaTypes[i]);
|
||||
return true;
|
||||
// For supported media types that are wildcard patterns, confirm that the requested
|
||||
// media type satisfies the wildcard pattern (e.g., if "text/entity+json;v=2" requested
|
||||
// and formatter supports "text/*+json").
|
||||
// We only do this when comparing against server-defined content types (e.g., those
|
||||
// from [Produces] or Response.ContentType), otherwise we'd potentially be reflecting
|
||||
// back arbitrary Accept header values.
|
||||
if (context.ContentTypeIsServerDefined
|
||||
&& parsedContentType.IsSubsetOf(supportedMediaType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For supported media types that are not wildcard patterns, confirm that this formatter
|
||||
// supports a more specific media type than requested e.g. OK if "text/*" requested and
|
||||
// formatter supports "text/plain".
|
||||
// contentType is typically what we got in an Accept header.
|
||||
if (supportedMediaType.IsSubsetOf(parsedContentType))
|
||||
{
|
||||
context.ContentType = new StringSegment(SupportedMediaTypes[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
foreach (var formatter in formatters)
|
||||
{
|
||||
formatterContext.ContentType = new StringSegment();
|
||||
formatterContext.ContentTypeIsServerDefined = false;
|
||||
if (formatter.CanWriteResult(formatterContext))
|
||||
{
|
||||
return formatter;
|
||||
|
|
@ -349,6 +350,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
var mediaType = sortedAcceptHeaders[i];
|
||||
formatterContext.ContentType = mediaType.MediaType;
|
||||
formatterContext.ContentTypeIsServerDefined = false;
|
||||
for (var j = 0; j < formatters.Count; j++)
|
||||
{
|
||||
var formatter = formatters[j];
|
||||
|
|
@ -401,6 +403,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
foreach (var contentType in acceptableContentTypes)
|
||||
{
|
||||
formatterContext.ContentType = new StringSegment(contentType);
|
||||
formatterContext.ContentTypeIsServerDefined = true;
|
||||
if (formatter.CanWriteResult(formatterContext))
|
||||
{
|
||||
return formatter;
|
||||
|
|
@ -446,6 +449,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
var formatter = formatters[k];
|
||||
formatterContext.ContentType = new StringSegment(possibleOutputContentTypes[j]);
|
||||
formatterContext.ContentTypeIsServerDefined = true;
|
||||
if (formatter.CanWriteResult(formatterContext))
|
||||
{
|
||||
return formatter;
|
||||
|
|
@ -469,8 +473,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
var contentType = contentTypes[i];
|
||||
var parsedContentType = new MediaType(contentType);
|
||||
if (parsedContentType.MatchesAllTypes ||
|
||||
parsedContentType.MatchesAllSubTypes)
|
||||
if (parsedContentType.HasWildcard)
|
||||
{
|
||||
var message = Resources.FormatObjectResult_MatchAllContentType(
|
||||
contentType,
|
||||
|
|
|
|||
|
|
@ -126,8 +126,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
foreach (var arg in completeArgs)
|
||||
{
|
||||
var contentType = new MediaType(arg);
|
||||
if (contentType.MatchesAllTypes ||
|
||||
contentType.MatchesAllSubTypes)
|
||||
if (contentType.HasWildcard)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatMatchAllContentTypeIsNotAllowed(arg));
|
||||
|
|
|
|||
|
|
@ -15,5 +15,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
|
|||
|
||||
public static readonly MediaTypeHeaderValue ApplicationJsonPatch
|
||||
= MediaTypeHeaderValue.Parse("application/json-patch+json").CopyAsReadOnly();
|
||||
|
||||
public static readonly MediaTypeHeaderValue ApplicationAnyJsonSyntax
|
||||
= MediaTypeHeaderValue.Parse("application/*+json").CopyAsReadOnly();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,16 +59,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
|
|||
{
|
||||
options.OutputFormatters.Add(new JsonOutputFormatter(_jsonSerializerSettings, _charPool));
|
||||
|
||||
var jsonInputLogger = _loggerFactory.CreateLogger<JsonInputFormatter>();
|
||||
options.InputFormatters.Add(new JsonInputFormatter(
|
||||
jsonInputLogger,
|
||||
// Register JsonPatchInputFormatter before JsonInputFormatter, otherwise
|
||||
// JsonInputFormatter would consume "application/json-patch+json" requests
|
||||
// before JsonPatchInputFormatter gets to see them.
|
||||
var jsonInputPatchLogger = _loggerFactory.CreateLogger<JsonPatchInputFormatter>();
|
||||
options.InputFormatters.Add(new JsonPatchInputFormatter(
|
||||
jsonInputPatchLogger,
|
||||
_jsonSerializerSettings,
|
||||
_charPool,
|
||||
_objectPoolProvider));
|
||||
|
||||
var jsonInputPatchLogger = _loggerFactory.CreateLogger<JsonPatchInputFormatter>();
|
||||
options.InputFormatters.Add(new JsonPatchInputFormatter(
|
||||
jsonInputPatchLogger,
|
||||
var jsonInputLogger = _loggerFactory.CreateLogger<JsonInputFormatter>();
|
||||
options.InputFormatters.Add(new JsonInputFormatter(
|
||||
jsonInputLogger,
|
||||
_jsonSerializerSettings,
|
||||
_charPool,
|
||||
_objectPoolProvider));
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
SupportedEncodings.Add(Encoding.Unicode);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -12,5 +12,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
|
|||
|
||||
public static readonly MediaTypeHeaderValue TextXml
|
||||
= MediaTypeHeaderValue.Parse("text/xml").CopyAsReadOnly();
|
||||
|
||||
public static readonly MediaTypeHeaderValue ApplicationAnyXmlSyntax
|
||||
= MediaTypeHeaderValue.Parse("application/*+xml").CopyAsReadOnly();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax);
|
||||
|
||||
_serializerSettings = new DataContractSerializerSettings();
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax);
|
||||
|
||||
WriterSettings = writerSettings;
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax);
|
||||
|
||||
WrapperProviderFactories = new List<IWrapperProviderFactory>();
|
||||
WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory());
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax);
|
||||
|
||||
WriterSettings = writerSettings;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
|
@ -14,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
[InlineData("application/json")]
|
||||
[InlineData("application /json")]
|
||||
[InlineData(" application / json ")]
|
||||
public void Constructor_CanParseParameterlessMediaTypes(string mediaType)
|
||||
public void Constructor_CanParseParameterlessSuffixlessMediaTypes(string mediaType)
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = new MediaType(mediaType, 0, mediaType.Length);
|
||||
|
|
@ -24,23 +25,54 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.Equal(new StringSegment("json"), result.SubType);
|
||||
}
|
||||
|
||||
public static IEnumerable<string[]> MediaTypesWithSuffixes
|
||||
{
|
||||
get
|
||||
{
|
||||
return new List<string[]>
|
||||
{
|
||||
// See https://tools.ietf.org/html/rfc6838#section-4.2 for allowed names spec
|
||||
new[] { "application/json", "json", null },
|
||||
new[] { "application/json+", "json", "" },
|
||||
new[] { "application/+json", "", "json" },
|
||||
new[] { "application/entitytype+json", "entitytype", "json" },
|
||||
new[] { " application / vnd.com-pany.some+entity!.v2+js.#$&^_n ; q=\"0.3+1\"", "vnd.com-pany.some+entity!.v2", "js.#$&^_n" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof (MediaTypesWithSuffixes))]
|
||||
public void Constructor_CanParseSuffixedMediaTypes(
|
||||
string mediaType,
|
||||
string expectedSubTypeWithoutSuffix,
|
||||
string expectedSubtypeSuffix)
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = new MediaType(mediaType);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new StringSegment(expectedSubTypeWithoutSuffix), result.SubTypeWithoutSuffix);
|
||||
Assert.Equal(new StringSegment(expectedSubtypeSuffix), result.SubTypeSuffix);
|
||||
}
|
||||
|
||||
public static TheoryData<string> MediaTypesWithParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string>
|
||||
{
|
||||
"application/json;format=pretty;charset=utf-8;q=0.8",
|
||||
"application/json;format=pretty;charset=\"utf-8\";q=0.8",
|
||||
"application/json;format=pretty;charset=utf-8; q=0.8 ",
|
||||
"application/json;format=pretty;charset=utf-8 ; q=0.8 ",
|
||||
"application/json;format=pretty; charset=utf-8 ; q=0.8 ",
|
||||
"application/json;format=pretty ; charset=utf-8 ; q=0.8 ",
|
||||
"application/json; format=pretty ; charset=utf-8 ; q=0.8 ",
|
||||
"application/json; format=pretty ; charset=utf-8 ; q= 0.8 ",
|
||||
"application/json; format=pretty ; charset=utf-8 ; q = 0.8 ",
|
||||
" application / json; format = pretty ; charset = utf-8 ; q = 0.8 ",
|
||||
" application / json; format = \"pretty\" ; charset = \"utf-8\" ; q = \"0.8\" ",
|
||||
"application/json+bson;format=pretty;charset=utf-8;q=0.8",
|
||||
"application/json+bson;format=pretty;charset=\"utf-8\";q=0.8",
|
||||
"application/json+bson;format=pretty;charset=utf-8; q=0.8 ",
|
||||
"application/json+bson;format=pretty;charset=utf-8 ; q=0.8 ",
|
||||
"application/json+bson;format=pretty; charset=utf-8 ; q=0.8 ",
|
||||
"application/json+bson;format=pretty ; charset=utf-8 ; q=0.8 ",
|
||||
"application/json+bson; format=pretty ; charset=utf-8 ; q=0.8 ",
|
||||
"application/json+bson; format=pretty ; charset=utf-8 ; q= 0.8 ",
|
||||
"application/json+bson; format=pretty ; charset=utf-8 ; q = 0.8 ",
|
||||
" application / json+bson; format = pretty ; charset = utf-8 ; q = 0.8 ",
|
||||
" application / json+bson; format = \"pretty\" ; charset = \"utf-8\" ; q = \"0.8\" ",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +86,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
// Assert
|
||||
Assert.Equal(new StringSegment("application"), result.Type);
|
||||
Assert.Equal(new StringSegment("json"), result.SubType);
|
||||
Assert.Equal(new StringSegment("json+bson"), result.SubType);
|
||||
Assert.Equal(new StringSegment("json"), result.SubTypeWithoutSuffix);
|
||||
Assert.Equal(new StringSegment("bson"), result.SubTypeSuffix);
|
||||
Assert.Equal(new StringSegment("pretty"), result.GetParameter("format"));
|
||||
Assert.Equal(new StringSegment("0.8"), result.GetParameter("q"));
|
||||
Assert.Equal(new StringSegment("utf-8"), result.GetParameter("charset"));
|
||||
|
|
@ -171,6 +205,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
[InlineData("application/json", "application/json;format=indent;charset=utf-8")]
|
||||
[InlineData("application/json;format=indent;charset=utf-8", "application/json;format=indent;charset=utf-8")]
|
||||
[InlineData("application/json;charset=utf-8;format=indent", "application/json;format=indent;charset=utf-8")]
|
||||
[InlineData("application/*", "application/json")]
|
||||
[InlineData("application/*", "application/entitytype+json;v=2")]
|
||||
[InlineData("application/*;v=2", "application/entitytype+json;v=2")]
|
||||
[InlineData("application/json;*", "application/json;v=2")]
|
||||
[InlineData("application/json;v=2;*", "application/json;v=2;charset=utf-8")]
|
||||
[InlineData("*/*", "application/json")]
|
||||
[InlineData("application/entity+json", "application/entity+json")]
|
||||
[InlineData("application/*+json", "application/entity+json")]
|
||||
[InlineData("application/*", "application/entity+json")]
|
||||
public void IsSubsetOf_ReturnsTrueWhenExpected(string set, string subset)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -188,6 +231,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
[InlineData("application/json;charset=utf-8", "application/json")]
|
||||
[InlineData("application/json;format=indent;charset=utf-8", "application/json")]
|
||||
[InlineData("application/json;format=indent;charset=utf-8", "application/json;charset=utf-8")]
|
||||
[InlineData("application/*", "text/json")]
|
||||
[InlineData("application/*;v=2", "application/json")]
|
||||
[InlineData("application/*;v=2", "application/json;v=1")]
|
||||
[InlineData("application/json;v=2;*", "application/json;v=1")]
|
||||
[InlineData("application/entity+json", "application/entity+txt")]
|
||||
[InlineData("application/entity+json", "application/entity.v2+json")]
|
||||
[InlineData("application/*+json", "application/entity+txt")]
|
||||
[InlineData("application/entity+*", "application/entity.v2+json")]
|
||||
[InlineData("application/*+*", "application/json")]
|
||||
[InlineData("application/entity+*", "application/entity+json")] // We don't allow suffixes to be wildcards
|
||||
[InlineData("application/*+*", "application/entity+json")] // We don't allow suffixes to be wildcards
|
||||
public void IsSubsetOf_ReturnsFalseWhenExpected(string set, string subset)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -257,6 +311,48 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("*/*", true)]
|
||||
[InlineData("text/*", true)]
|
||||
[InlineData("text/*+suffix", true)]
|
||||
[InlineData("text/*+", true)]
|
||||
[InlineData("text/*+*", true)]
|
||||
[InlineData("text/json+suffix", false)]
|
||||
[InlineData("*/json+*", false)]
|
||||
public void MatchesAllSubTypesWithoutSuffix_ReturnsExpectedResult(string value, bool expectedReturnValue)
|
||||
{
|
||||
// Arrange
|
||||
var mediaType = new MediaType(value);
|
||||
|
||||
// Act
|
||||
var result = mediaType.MatchesAllSubTypesWithoutSuffix;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedReturnValue, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("*/*", true)]
|
||||
[InlineData("text/*", true)]
|
||||
[InlineData("text/entity+*", false)] // We don't support wildcards on suffixes
|
||||
[InlineData("text/*+json", true)]
|
||||
[InlineData("text/entity+json;*", true)]
|
||||
[InlineData("text/entity+json;v=3;*", true)]
|
||||
[InlineData("text/entity+json;v=3;q=0.8", false)]
|
||||
[InlineData("text/json", false)]
|
||||
[InlineData("text/json;param=*", false)] // * is the literal value of the param
|
||||
public void HasWildcard_ReturnsTrueWhenExpected(string value, bool expectedReturnValue)
|
||||
{
|
||||
// Arrange
|
||||
var mediaType = new MediaType(value);
|
||||
|
||||
// Act
|
||||
var result = mediaType.HasWildcard;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedReturnValue, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MediaTypesWithParameters))]
|
||||
[InlineData("application/json;format=pretty;q=0.9;charset=utf-8;q=0.8")]
|
||||
|
|
|
|||
|
|
@ -75,13 +75,47 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, true)]
|
||||
[InlineData(false, false)]
|
||||
public void CanWriteResult_MatchesWildcardsOnlyWhenContentTypeProvidedByServer(
|
||||
bool contentTypeProvidedByServer, bool shouldMatchWildcards)
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TypeSpecificFormatter();
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/*+xml"));
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/*+json"));
|
||||
formatter.SupportedTypes.Add(typeof(string));
|
||||
|
||||
var requestedContentType = "application/vnd.test.entity+json;v=2";
|
||||
var context = new OutputFormatterWriteContext(
|
||||
new DefaultHttpContext(),
|
||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||
typeof(string),
|
||||
"Hello, world!")
|
||||
{
|
||||
ContentType = new StringSegment(requestedContentType),
|
||||
ContentTypeIsServerDefined = contentTypeProvidedByServer,
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = formatter.CanWriteResult(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(shouldMatchWildcards, result);
|
||||
Assert.Equal(requestedContentType, context.ContentType.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSupportedContentTypes_ReturnsAllContentTypes_WithContentTypeNull()
|
||||
public void GetSupportedContentTypes_ReturnsAllNonWildcardContentTypes_WithContentTypeNull()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TestOutputFormatter();
|
||||
formatter.SupportedMediaTypes.Clear();
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("*/*"));
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/*"));
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/plain;*"));
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml"));
|
||||
|
||||
// Act
|
||||
|
|
@ -96,7 +130,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSupportedContentTypes_ReturnsMatchingContentTypes_WithContentType()
|
||||
public void GetSupportedContentTypes_ReturnsMoreSpecificMatchingContentTypes_WithContentType()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TestOutputFormatter();
|
||||
|
|
@ -116,18 +150,39 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSupportedContentTypes_ReturnsMatchingContentTypes_NoMatches()
|
||||
public void GetSupportedContentTypes_ReturnsMatchingWildcardContentTypes_WithContentType()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TestOutputFormatter();
|
||||
|
||||
formatter.SupportedMediaTypes.Clear();
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/*+json"));
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml"));
|
||||
|
||||
// Act
|
||||
var contentTypes = formatter.GetSupportedContentTypes(
|
||||
"application/xml",
|
||||
"application/vnd.test+json;v=2",
|
||||
typeof(int));
|
||||
|
||||
// Assert
|
||||
var contentType = Assert.Single(contentTypes);
|
||||
Assert.Equal("application/vnd.test+json;v=2", contentType.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSupportedContentTypes_ReturnsMatchingContentTypes_NoMatches()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TestOutputFormatter();
|
||||
|
||||
formatter.SupportedMediaTypes.Clear();
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/*+xml"));
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml"));
|
||||
|
||||
// Act
|
||||
var contentTypes = formatter.GetSupportedContentTypes(
|
||||
"application/vnd.test+bson",
|
||||
typeof(int));
|
||||
|
||||
// Assert
|
||||
|
|
|
|||
|
|
@ -342,6 +342,100 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.Null(formatter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectFormatter_WithAcceptHeaderOnly_SetsContentTypeIsServerDefinedToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var executor = CreateExecutor();
|
||||
|
||||
var formatters = new List<IOutputFormatter>
|
||||
{
|
||||
new ServerContentTypeOnlyFormatter()
|
||||
};
|
||||
|
||||
var context = new OutputFormatterWriteContext(
|
||||
new DefaultHttpContext(),
|
||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||
objectType: null,
|
||||
@object: null);
|
||||
|
||||
context.HttpContext.Request.Headers[HeaderNames.Accept] = "text/custom";
|
||||
|
||||
// Act
|
||||
var formatter = executor.SelectFormatter(
|
||||
context,
|
||||
new MediaTypeCollection { },
|
||||
formatters);
|
||||
|
||||
// Assert
|
||||
Assert.Null(formatter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectFormatter_WithAcceptHeaderAndContentTypes_SetsContentTypeIsServerDefinedWhenExpected()
|
||||
{
|
||||
// Arrange
|
||||
var executor = CreateExecutor();
|
||||
|
||||
var formatters = new List<IOutputFormatter>
|
||||
{
|
||||
new ServerContentTypeOnlyFormatter()
|
||||
};
|
||||
|
||||
var context = new OutputFormatterWriteContext(
|
||||
new DefaultHttpContext(),
|
||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||
objectType: null,
|
||||
@object: null);
|
||||
|
||||
context.HttpContext.Request.Headers[HeaderNames.Accept] = "text/custom, text/custom2";
|
||||
|
||||
var serverDefinedContentTypes = new MediaTypeCollection();
|
||||
serverDefinedContentTypes.Add("text/other");
|
||||
serverDefinedContentTypes.Add("text/custom2");
|
||||
|
||||
// Act
|
||||
var formatter = executor.SelectFormatter(
|
||||
context,
|
||||
serverDefinedContentTypes,
|
||||
formatters);
|
||||
|
||||
// Assert
|
||||
Assert.Same(formatters[0], formatter);
|
||||
Assert.Equal(new StringSegment("text/custom2"), context.ContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectFormatter_WithContentTypesOnly_SetsContentTypeIsServerDefinedToTrue()
|
||||
{
|
||||
// Arrange
|
||||
var executor = CreateExecutor();
|
||||
|
||||
var formatters = new List<IOutputFormatter>
|
||||
{
|
||||
new ServerContentTypeOnlyFormatter()
|
||||
};
|
||||
|
||||
var context = new OutputFormatterWriteContext(
|
||||
new DefaultHttpContext(),
|
||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||
objectType: null,
|
||||
@object: null);
|
||||
|
||||
var serverDefinedContentTypes = new MediaTypeCollection();
|
||||
serverDefinedContentTypes.Add("text/custom");
|
||||
|
||||
// Act
|
||||
var formatter = executor.SelectFormatter(
|
||||
context,
|
||||
serverDefinedContentTypes,
|
||||
formatters);
|
||||
|
||||
// Assert
|
||||
Assert.Same(formatters[0], formatter);
|
||||
Assert.Equal(new StringSegment("text/custom"), context.ContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_NoFormatterFound_Returns406()
|
||||
{
|
||||
|
|
@ -419,6 +513,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
[InlineData(new[] { "*/*" }, "*/*")]
|
||||
[InlineData(new[] { "application/xml", "*/*", "application/json" }, "*/*")]
|
||||
[InlineData(new[] { "*/*", "application/json" }, "*/*")]
|
||||
[InlineData(new[] { "application/json", "application/*+json" }, "application/*+json")]
|
||||
[InlineData(new[] { "application/entiy+json;*", "application/json" }, "application/entiy+json;*")]
|
||||
public async Task ExecuteAsync_MatchAllContentType_Throws(string[] contentTypes, string invalidContentType)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -656,5 +752,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
return SelectedOutputFormatter;
|
||||
}
|
||||
}
|
||||
|
||||
private class ServerContentTypeOnlyFormatter : OutputFormatter
|
||||
{
|
||||
public override bool CanWriteResult(OutputFormatterCanWriteContext context)
|
||||
{
|
||||
// This test formatter matches if and only if the content type is specified
|
||||
// as "server defined". This lets tests identify what value the ObjectResultExecutor
|
||||
// passed for that flag.
|
||||
return context.ContentTypeIsServerDefined;
|
||||
}
|
||||
|
||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ namespace Microsoft.AspNetCore.Mvc.Test
|
|||
[InlineData("*/*", "*/*")]
|
||||
[InlineData("application/xml, */*, application/json", "*/*")]
|
||||
[InlineData("*/*, application/json", "*/*")]
|
||||
[InlineData("application/*+json", "application/*+json")]
|
||||
[InlineData("application/json;v=1;*", "application/json;v=1;*")]
|
||||
public void ProducesAttribute_InvalidContentType_Throws(string content, string invalidContentType)
|
||||
{
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
[InlineData("text/*", false)]
|
||||
[InlineData("text/xml", false)]
|
||||
[InlineData("application/xml", false)]
|
||||
[InlineData("application/some.entity+json", true)]
|
||||
[InlineData("application/some.entity+json;v=2", true)]
|
||||
[InlineData("application/some.entity+xml", false)]
|
||||
[InlineData("application/some.entity+*", false)]
|
||||
[InlineData("text/some.entity+json", false)]
|
||||
[InlineData("", false)]
|
||||
[InlineData(null, false)]
|
||||
[InlineData("invalid", false)]
|
||||
|
|
|
|||
|
|
@ -401,6 +401,49 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
Assert.Equal(expectedOutput, content);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("application/json", false, "application/json")]
|
||||
[InlineData("application/json", true, "application/json")]
|
||||
[InlineData("application/xml", false, null)]
|
||||
[InlineData("application/xml", true, null)]
|
||||
[InlineData("application/*", false, "application/json")]
|
||||
[InlineData("text/*", false, "text/json")]
|
||||
[InlineData("custom/*", false, null)]
|
||||
[InlineData("application/json;v=2", false, null)]
|
||||
[InlineData("application/json;v=2", true, null)]
|
||||
[InlineData("application/some.entity+json", false, null)]
|
||||
[InlineData("application/some.entity+json", true, "application/some.entity+json")]
|
||||
[InlineData("application/some.entity+json;v=2", true, "application/some.entity+json;v=2")]
|
||||
[InlineData("application/some.entity+xml", true, null)]
|
||||
public void CanWriteResult_ReturnsExpectedValueForMediaType(
|
||||
string mediaType,
|
||||
bool isServerDefined,
|
||||
string expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonOutputFormatter(new JsonSerializerSettings(), ArrayPool<char>.Shared);
|
||||
|
||||
var body = new MemoryStream();
|
||||
var actionContext = GetActionContext(MediaTypeHeaderValue.Parse(mediaType), body);
|
||||
var outputFormatterContext = new OutputFormatterWriteContext(
|
||||
actionContext.HttpContext,
|
||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||
typeof(string),
|
||||
new object())
|
||||
{
|
||||
ContentType = new StringSegment(mediaType),
|
||||
ContentTypeIsServerDefined = isServerDefined,
|
||||
};
|
||||
|
||||
// Act
|
||||
var actualCanWriteValue = formatter.CanWriteResult(outputFormatterContext);
|
||||
|
||||
// Assert
|
||||
var expectedContentType = expectedResult ?? mediaType;
|
||||
Assert.Equal(expectedResult != null, actualCanWriteValue);
|
||||
Assert.Equal(new StringSegment(expectedContentType), outputFormatterContext.ContentType);
|
||||
}
|
||||
|
||||
private static Encoding CreateOrGetSupportedEncoding(
|
||||
JsonOutputFormatter formatter,
|
||||
string encodingAsString,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
[InlineData("text/*", false)]
|
||||
[InlineData("text/json", false)]
|
||||
[InlineData("application/json", false)]
|
||||
[InlineData("application/some.entity+xml", true)]
|
||||
[InlineData("application/some.entity+xml;v=2", true)]
|
||||
[InlineData("application/some.entity+json", false)]
|
||||
[InlineData("application/some.entity+*", false)]
|
||||
[InlineData("text/some.entity+json", false)]
|
||||
[InlineData("", false)]
|
||||
[InlineData(null, false)]
|
||||
[InlineData("invalid", false)]
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||
[MemberData(nameof(TypesForCanWriteResult))]
|
||||
public void CanWriteResult_ReturnsExpectedOutput(object input, Type declaredType, bool expectedOutput)
|
||||
public void CanWriteResult_ReturnsExpectedValueForObjectType(object input, Type declaredType, bool expectedOutput)
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new XmlDataContractSerializerOutputFormatter();
|
||||
|
|
@ -391,6 +391,42 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
Assert.Equal(expectedOutput, result);
|
||||
}
|
||||
|
||||
[ConditionalTheory]
|
||||
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||
[InlineData("application/xml", false, "application/xml")]
|
||||
[InlineData("application/xml", true, "application/xml")]
|
||||
[InlineData("application/other", false, null)]
|
||||
[InlineData("application/other", true, null)]
|
||||
[InlineData("application/*", false, "application/xml")]
|
||||
[InlineData("text/*", false, "text/xml")]
|
||||
[InlineData("custom/*", false, null)]
|
||||
[InlineData("application/xml;v=2", false, null)]
|
||||
[InlineData("application/xml;v=2", true, null)]
|
||||
[InlineData("application/some.entity+xml", false, null)]
|
||||
[InlineData("application/some.entity+xml", true, "application/some.entity+xml")]
|
||||
[InlineData("application/some.entity+xml;v=2", true, "application/some.entity+xml;v=2")]
|
||||
[InlineData("application/some.entity+other", true, null)]
|
||||
public void CanWriteResult_ReturnsExpectedValueForMediaType(
|
||||
string mediaType,
|
||||
bool isServerDefined,
|
||||
string expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new XmlDataContractSerializerOutputFormatter();
|
||||
var outputFormatterContext = GetOutputFormatterContext(new object(), typeof(object));
|
||||
outputFormatterContext.ContentType = new StringSegment(mediaType);
|
||||
outputFormatterContext.ContentTypeIsServerDefined = isServerDefined;
|
||||
|
||||
// Act
|
||||
var actualCanWriteValue = formatter.CanWriteResult(outputFormatterContext);
|
||||
|
||||
// Assert
|
||||
var expectedContentType = expectedResult ?? mediaType;
|
||||
Assert.Equal(expectedResult != null, actualCanWriteValue);
|
||||
Assert.Equal(new StringSegment(expectedContentType), outputFormatterContext.ContentType);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> TypesForGetSupportedContentTypes
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -48,6 +48,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
[InlineData("text/*", false)]
|
||||
[InlineData("text/json", false)]
|
||||
[InlineData("application/json", false)]
|
||||
[InlineData("application/some.entity+xml", true)]
|
||||
[InlineData("application/some.entity+xml;v=2", true)]
|
||||
[InlineData("application/some.entity+json", false)]
|
||||
[InlineData("application/some.entity+*", false)]
|
||||
[InlineData("text/some.entity+json", false)]
|
||||
[InlineData("", false)]
|
||||
[InlineData("invalid", false)]
|
||||
[InlineData(null, false)]
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(TypesForCanWriteResult))]
|
||||
public void XmlSerializer_CanWriteResult(object input, Type declaredType, bool expectedOutput)
|
||||
public void CanWriteResult_ReturnsExpectedValueForObjectType(object input, Type declaredType, bool expectedOutput)
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new XmlSerializerOutputFormatter();
|
||||
|
|
@ -309,6 +309,40 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
Assert.Equal(expectedOutput, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("application/xml", false, "application/xml")]
|
||||
[InlineData("application/xml", true, "application/xml")]
|
||||
[InlineData("application/other", false, null)]
|
||||
[InlineData("application/other", true, null)]
|
||||
[InlineData("application/*", false, "application/xml")]
|
||||
[InlineData("text/*", false, "text/xml")]
|
||||
[InlineData("custom/*", false, null)]
|
||||
[InlineData("application/xml;v=2", false, null)]
|
||||
[InlineData("application/xml;v=2", true, null)]
|
||||
[InlineData("application/some.entity+xml", false, null)]
|
||||
[InlineData("application/some.entity+xml", true, "application/some.entity+xml")]
|
||||
[InlineData("application/some.entity+xml;v=2", true, "application/some.entity+xml;v=2")]
|
||||
[InlineData("application/some.entity+other", true, null)]
|
||||
public void CanWriteResult_ReturnsExpectedValueForMediaType(
|
||||
string mediaType,
|
||||
bool isServerDefined,
|
||||
string expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new XmlSerializerOutputFormatter();
|
||||
var outputFormatterContext = GetOutputFormatterContext(new object(), typeof (object));
|
||||
outputFormatterContext.ContentType = new StringSegment(mediaType);
|
||||
outputFormatterContext.ContentTypeIsServerDefined = isServerDefined;
|
||||
|
||||
// Act
|
||||
var actualCanWriteValue = formatter.CanWriteResult(outputFormatterContext);
|
||||
|
||||
// Assert
|
||||
var expectedContentType = expectedResult ?? mediaType;
|
||||
Assert.Equal(expectedResult != null, actualCanWriteValue);
|
||||
Assert.Equal(new StringSegment(expectedContentType), outputFormatterContext.ContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task XmlSerializerOutputFormatterDoesntFlushOutputStream()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -817,6 +817,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal(typeof(JsonOutputFormatter).FullName, textJson.FormatterType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiExplorer_ResponseContentType_WildcardMatch()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("http://localhost/ApiExplorerResponseContentType/WildcardMatch");
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||
|
||||
// Assert
|
||||
var description = Assert.Single(result);
|
||||
var responseType = Assert.Single(description.SupportedResponseTypes);
|
||||
Assert.Equal(1, responseType.ResponseFormats.Count);
|
||||
|
||||
var responseFormat = responseType.ResponseFormats[0];
|
||||
Assert.Equal("application/hal+json", responseFormat.MediaType);
|
||||
Assert.Equal(typeof(JsonOutputFormatter).FullName, responseFormat.FormatterType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiExplorer_ResponseContentType_NoMatch()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -123,5 +123,47 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expectedString, product.SampleString);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task JsonSyntaxSuffix_SelectsActionConsumingJson()
|
||||
{
|
||||
// Arrange
|
||||
var input = "{SampleString:\"some input\"}";
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Post,
|
||||
"http://localhost/ConsumesAttribute_MediaTypeSuffix/CreateProduct");
|
||||
request.Content = new StringContent(input, Encoding.UTF8, "application/vnd.example+json");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(request);
|
||||
var product = JsonConvert.DeserializeObject<Product>(await response.Content.ReadAsStringAsync());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("Read from JSON: some input", product.SampleString);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
// Mono issue - https://github.com/aspnet/External/issues/18
|
||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
||||
public async Task XmlSyntaxSuffix_SelectsActionConsumingXml()
|
||||
{
|
||||
// Arrange
|
||||
var input = "<Product xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" " +
|
||||
"xmlns=\"http://schemas.datacontract.org/2004/07/BasicWebSite.Models\">" +
|
||||
"<SampleString>some input</SampleString></Product>";
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Post,
|
||||
"http://localhost/ConsumesAttribute_MediaTypeSuffix/CreateProduct");
|
||||
request.Content = new StringContent(input, Encoding.UTF8, "application/vnd.example+xml");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(request);
|
||||
var product = JsonConvert.DeserializeObject<Product>(await response.Content.ReadAsStringAsync());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("Read from XML: some input", product.SampleString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,13 @@ using System;
|
|||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BasicWebSite.Models;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
|
|
@ -468,5 +471,42 @@ END:VCARD
|
|||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal(body, "MethodWithFormatFilter");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProducesAttribute_CustomMediaTypeWithJsonSuffix_RunsConnegAndSelectsJsonFormatter()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMediaType = MediaTypeHeaderValue.Parse("application/vnd.example.contact+json; v=2; charset=utf-8");
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/ProducesWithMediaTypeSuffixesController/ContactInfo");
|
||||
request.Headers.Add("Accept", "application/vnd.example.contact+json; v=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var contact = JsonConvert.DeserializeObject<Contact>(body);
|
||||
Assert.Equal("Jason Ecsemelle", contact.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProducesAttribute_CustomMediaTypeWithXmlSuffix_RunsConnegAndSelectsXmlFormatter()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMediaType = MediaTypeHeaderValue.Parse("application/vnd.example.contact+xml; v=2; charset=utf-8");
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/ProducesWithMediaTypeSuffixesController/ContactInfo");
|
||||
request.Headers.Add("Accept", "application/vnd.example.contact+xml; v=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);
|
||||
var bodyStream = await response.Content.ReadAsStreamAsync();
|
||||
var xmlDeserializer = new DataContractSerializer(typeof(Contact));
|
||||
var contact = xmlDeserializer.ReadObject(bodyStream) as Contact;
|
||||
Assert.Equal("Jason Ecsemelle", contact.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -106,8 +106,8 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
|
||||
// Assert
|
||||
Assert.Collection(options.InputFormatters,
|
||||
formatter => Assert.IsType<JsonInputFormatter>(formatter),
|
||||
formatter => Assert.IsType<JsonPatchInputFormatter>(formatter));
|
||||
formatter => Assert.IsType<JsonPatchInputFormatter>(formatter),
|
||||
formatter => Assert.IsType<JsonInputFormatter>(formatter));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -22,7 +22,14 @@ namespace ApiExplorerWebSite
|
|||
}
|
||||
|
||||
[HttpGet]
|
||||
[Produces("application/hal+json", "text/hal+json")]
|
||||
[Produces("application/hal+custom", "application/hal+json")]
|
||||
public Product WildcardMatch()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Produces("application/custom", "text/hal+bson")]
|
||||
public Product NoMatch()
|
||||
{
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
// 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 BasicWebSite.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BasicWebSite.Controllers.ActionConstraints
|
||||
{
|
||||
[Route("ConsumesAttribute_MediaTypeSuffix/[action]")]
|
||||
public class ConsumesAttribute_MediaTypeSuffix : Controller
|
||||
{
|
||||
[Consumes("application/vnd.example+json")]
|
||||
public Product CreateProduct([FromBody] Product_Json jsonInput)
|
||||
{
|
||||
// To show that we selected the correct method (and not just the
|
||||
// correct input formatter), produce method-specific output.
|
||||
jsonInput.SampleString = "Read from JSON: " + jsonInput.SampleString;
|
||||
return jsonInput;
|
||||
}
|
||||
|
||||
[Consumes("application/vnd.example+xml")]
|
||||
public Product CreateProduct([FromBody] Product_Xml xmlInput)
|
||||
{
|
||||
// To show that we selected the correct method (and not just the
|
||||
// correct input formatter), produce method-specific output.
|
||||
xmlInput.SampleString = "Read from XML: " + xmlInput.SampleString;
|
||||
return xmlInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// 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 BasicWebSite.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BasicWebSite.Controllers.ContentNegotiation
|
||||
{
|
||||
[Route("ProducesWithMediaTypeSuffixesController/[action]")]
|
||||
public class ProducesWithMediaTypeSuffixesController : Controller
|
||||
{
|
||||
[Produces("application/vnd.example.contact+json; v=2", "application/vnd.example.contact+xml; v=2")]
|
||||
public Contact ContactInfo()
|
||||
{
|
||||
return new Contact()
|
||||
{
|
||||
Name = "Jason Ecsemelle"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue