Remove runtime dependency usage of MediaTypeHeaderValue.

Removes usage of MediaTypeHeaderValue in ApiExplorer, InputFormatters and
OutputFormatters

Public interface changes with stub implementation.
This commit is contained in:
jacalvar 2015-12-10 11:15:33 -08:00
parent 32d15186a0
commit 830fd410f5
76 changed files with 1061 additions and 496 deletions

View File

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.Net.Http.Headers; using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Mvc.Formatters namespace Microsoft.AspNet.Mvc.Formatters
{ {
@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
/// and expect to see the same value provided in /// and expect to see the same value provided in
/// <see cref="IOutputFormatter.WriteAsync(OutputFormatterWriteContext)"/> /// <see cref="IOutputFormatter.WriteAsync(OutputFormatterWriteContext)"/>
/// </remarks> /// </remarks>
public virtual MediaTypeHeaderValue ContentType { get; set; } public virtual StringSegment ContentType { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating that content-negotiation could not find a formatter based on the /// Gets or sets a value indicating that content-negotiation could not find a formatter based on the

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.ApiExplorer namespace Microsoft.AspNet.Mvc.ApiExplorer
{ {
@ -19,6 +18,6 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer
/// <summary> /// <summary>
/// The media type of the response. /// The media type of the response.
/// </summary> /// </summary>
public MediaTypeHeaderValue MediaType { get; set; } public string MediaType { get; set; }
} }
} }

View File

@ -15,7 +15,6 @@ using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Template; using Microsoft.AspNet.Routing.Template;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.ApiExplorer namespace Microsoft.AspNet.Mvc.ApiExplorer
{ {
@ -311,7 +310,7 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer
// Walk through all 'filter' attributes in order, and allow each one to see or override // Walk through all 'filter' attributes in order, and allow each one to see or override
// the results of the previous ones. This is similar to the execution path for content-negotiation. // the results of the previous ones. This is similar to the execution path for content-negotiation.
var contentTypes = new List<MediaTypeHeaderValue>(); var contentTypes = new MediaTypeCollection();
if (responseMetadataAttributes != null) if (responseMetadataAttributes != null)
{ {
foreach (var metadataAttribute in responseMetadataAttributes) foreach (var metadataAttribute in responseMetadataAttributes)
@ -322,7 +321,7 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer
if (contentTypes.Count == 0) if (contentTypes.Count == 0)
{ {
contentTypes.Add(null); contentTypes.Add((string)null);
} }
foreach (var contentType in contentTypes) foreach (var contentType in contentTypes)

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.ApiExplorer namespace Microsoft.AspNet.Mvc.ApiExplorer
{ {
@ -28,8 +27,8 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer
/// The <see cref="Type"/> for which the supported content types are desired. /// The <see cref="Type"/> for which the supported content types are desired.
/// </param> /// </param>
/// <returns>Content types which are supported by the <see cref="Formatters.IOutputFormatter"/>.</returns> /// <returns>Content types which are supported by the <see cref="Formatters.IOutputFormatter"/>.</returns>
IReadOnlyList<MediaTypeHeaderValue> GetSupportedContentTypes( IReadOnlyList<string> GetSupportedContentTypes(
MediaTypeHeaderValue contentType, string contentType,
Type objectType); Type objectType);
} }
} }

View File

@ -2,8 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic; using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.ApiExplorer namespace Microsoft.AspNet.Mvc.ApiExplorer
{ {
@ -20,6 +19,6 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer
/// <summary> /// <summary>
/// Configures a collection of allowed content types which can be produced by the action. /// Configures a collection of allowed content types which can be produced by the action.
/// </summary> /// </summary>
void SetContentTypes(IList<MediaTypeHeaderValue> contentTypes); void SetContentTypes(MediaTypeCollection contentTypes);
} }
} }

View File

@ -8,6 +8,8 @@ using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.ActionConstraints; using Microsoft.AspNet.Mvc.ActionConstraints;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
@ -30,6 +32,15 @@ namespace Microsoft.AspNet.Mvc
throw new ArgumentNullException(nameof(contentType)); throw new ArgumentNullException(nameof(contentType));
} }
// We want to ensure that the given provided content types are valid values, so
// we validate them using the semantics of MediaTypeHeaderValue.
MediaTypeHeaderValue.Parse(contentType);
for (var i = 0; i < otherContentTypes.Length; i++)
{
MediaTypeHeaderValue.Parse(otherContentTypes[i]);
}
ContentTypes = GetContentTypes(contentType, otherContentTypes); ContentTypes = GetContentTypes(contentType, otherContentTypes);
} }
@ -39,7 +50,7 @@ namespace Microsoft.AspNet.Mvc
int IActionConstraint.Order { get; } = ConsumesActionConstraintOrder; int IActionConstraint.Order { get; } = ConsumesActionConstraintOrder;
/// <inheritdoc /> /// <inheritdoc />
public IList<MediaTypeHeaderValue> ContentTypes { get; set; } public MediaTypeCollection ContentTypes { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public void OnResourceExecuting(ResourceExecutingContext context) public void OnResourceExecuting(ResourceExecutingContext context)
@ -53,13 +64,12 @@ namespace Microsoft.AspNet.Mvc
// Ignore all other filters. This is to ensure we have a overriding behavior. // Ignore all other filters. This is to ensure we have a overriding behavior.
if (IsApplicable(context.ActionDescriptor)) if (IsApplicable(context.ActionDescriptor))
{ {
MediaTypeHeaderValue requestContentType = null; var requestContentType = context.HttpContext.Request.ContentType;
MediaTypeHeaderValue.TryParse(context.HttpContext.Request.ContentType, out requestContentType);
// Confirm the request's content type is more specific than a media type this action supports e.g. OK // 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 client sent "text/plain" data and this action supports "text/*".
if (requestContentType != null && if (requestContentType != null &&
!ContentTypes.Any(contentType => requestContentType.IsSubsetOf(contentType))) !ContentTypes.Any(contentType => MediaTypeComparisons.IsSubsetOf(contentType, requestContentType)))
{ {
context.Result = new UnsupportedMediaTypeResult(); context.Result = new UnsupportedMediaTypeResult();
} }
@ -86,8 +96,7 @@ namespace Microsoft.AspNet.Mvc
return true; return true;
} }
MediaTypeHeaderValue requestContentType = null; var requestContentType = context.RouteContext.HttpContext.Request.ContentType;
MediaTypeHeaderValue.TryParse(context.RouteContext.HttpContext.Request.ContentType, out requestContentType);
// If the request content type is null we need to act like pass through. // If the request content type is null we need to act like pass through.
// In case there is a single candidate with a constraint it should be selected. // In case there is a single candidate with a constraint it should be selected.
@ -104,7 +113,7 @@ namespace Microsoft.AspNet.Mvc
// Confirm the request's content type is more specific than a media type this action supports e.g. OK // 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 client sent "text/plain" data and this action supports "text/*".
if (ContentTypes.Any(contentType => requestContentType.IsSubsetOf(contentType))) if (ContentTypes.Any(contentType => MediaTypeComparisons.IsSubsetOf(contentType, requestContentType)))
{ {
return true; return true;
} }
@ -164,22 +173,22 @@ namespace Microsoft.AspNet.Mvc
} }
private List<MediaTypeHeaderValue> GetContentTypes(string firstArg, string[] args) private MediaTypeCollection GetContentTypes(string firstArg, string[] args)
{ {
var completeArgs = new List<string>(); var completeArgs = new List<string>();
completeArgs.Add(firstArg); completeArgs.Add(firstArg);
completeArgs.AddRange(args); completeArgs.AddRange(args);
var contentTypes = new List<MediaTypeHeaderValue>(); var contentTypes = new MediaTypeCollection();
foreach (var arg in completeArgs) foreach (var arg in completeArgs)
{ {
var contentType = MediaTypeHeaderValue.Parse(arg); if (MediaTypeComparisons.MatchesAllSubtypes(arg) ||
if (contentType.MatchesAllSubTypes || contentType.MatchesAllTypes) MediaTypeComparisons.MatchesAllTypes(arg))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
Resources.FormatMatchAllContentTypeIsNotAllowed(arg)); Resources.FormatMatchAllContentTypeIsNotAllowed(arg));
} }
contentTypes.Add(contentType); contentTypes.Add(arg);
} }
return contentTypes; return contentTypes;

View File

@ -16,10 +16,10 @@ namespace Microsoft.AspNet.Mvc
{ {
public class ContentResult : ActionResult public class ContentResult : ActionResult
{ {
private readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("text/plain") private readonly string DefaultContentType = new MediaTypeHeaderValue("text/plain")
{ {
Encoding = Encoding.UTF8 Encoding = Encoding.UTF8
}; }.ToString();
/// <summary> /// <summary>
/// Gets or set the content representing the body of the response. /// Gets or set the content representing the body of the response.
@ -27,9 +27,9 @@ namespace Microsoft.AspNet.Mvc
public string Content { get; set; } public string Content { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response. /// Gets or sets the Content-Type header for the response.
/// </summary> /// </summary>
public MediaTypeHeaderValue ContentType { get; set; } public string ContentType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the HTTP status code. /// Gets or sets the HTTP status code.

View File

@ -271,7 +271,7 @@ namespace Microsoft.AspNet.Mvc
var result = new ContentResult var result = new ContentResult
{ {
Content = content, Content = content,
ContentType = contentType ContentType = contentType?.ToString()
}; };
return result; return result;

View File

@ -31,10 +31,6 @@ namespace Microsoft.AspNet.Mvc
{ {
throw new ArgumentNullException(nameof(fileContents)); throw new ArgumentNullException(nameof(fileContents));
} }
if (contentType == null)
{
throw new ArgumentNullException(nameof(contentType));
}
} }
/// <summary> /// <summary>
@ -45,18 +41,13 @@ namespace Microsoft.AspNet.Mvc
/// <param name="fileContents">The bytes that represent the file contents.</param> /// <param name="fileContents">The bytes that represent the file contents.</param>
/// <param name="contentType">The Content-Type header of the response.</param> /// <param name="contentType">The Content-Type header of the response.</param>
public FileContentResult(byte[] fileContents, MediaTypeHeaderValue contentType) public FileContentResult(byte[] fileContents, MediaTypeHeaderValue contentType)
: base(contentType) : base(contentType?.ToString())
{ {
if (fileContents == null) if (fileContents == null)
{ {
throw new ArgumentNullException(nameof(fileContents)); throw new ArgumentNullException(nameof(fileContents));
} }
if (contentType == null)
{
throw new ArgumentNullException(nameof(contentType));
}
FileContents = fileContents; FileContents = fileContents;
} }

View File

@ -7,6 +7,7 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.Logging;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
@ -24,7 +25,7 @@ namespace Microsoft.AspNet.Mvc
/// the provided <paramref name="contentType"/>. /// the provided <paramref name="contentType"/>.
/// </summary> /// </summary>
/// <param name="contentType">The Content-Type header of the response.</param> /// <param name="contentType">The Content-Type header of the response.</param>
protected FileResult(MediaTypeHeaderValue contentType) protected FileResult(string contentType)
{ {
if (contentType == null) if (contentType == null)
{ {
@ -35,9 +36,9 @@ namespace Microsoft.AspNet.Mvc
} }
/// <summary> /// <summary>
/// Gets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response. /// Gets the Content-Type header for the response.
/// </summary> /// </summary>
public MediaTypeHeaderValue ContentType { get; } public string ContentType { get; }
/// <summary> /// <summary>
/// Gets the file name that will be used in the Content-Disposition header of the response. /// Gets the file name that will be used in the Content-Disposition header of the response.

View File

@ -42,18 +42,13 @@ namespace Microsoft.AspNet.Mvc
/// <param name="fileStream">The stream with the file.</param> /// <param name="fileStream">The stream with the file.</param>
/// <param name="contentType">The Content-Type header of the response.</param> /// <param name="contentType">The Content-Type header of the response.</param>
public FileStreamResult(Stream fileStream, MediaTypeHeaderValue contentType) public FileStreamResult(Stream fileStream, MediaTypeHeaderValue contentType)
: base(contentType) : base(contentType?.ToString())
{ {
if (fileStream == null) if (fileStream == null)
{ {
throw new ArgumentNullException(nameof(fileStream)); throw new ArgumentNullException(nameof(fileStream));
} }
if (contentType == null)
{
throw new ArgumentNullException(nameof(contentType));
}
FileStream = fileStream; FileStream = fileStream;
} }

View File

@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNet.Mvc.ApiExplorer; using Microsoft.AspNet.Mvc.ApiExplorer;
using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Filters;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters namespace Microsoft.AspNet.Mvc.Formatters
{ {
@ -78,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// Determine media types this action supports. // Determine media types this action supports.
var responseTypeFilters = context.Filters.OfType<IApiResponseMetadataProvider>(); var responseTypeFilters = context.Filters.OfType<IApiResponseMetadataProvider>();
var supportedMediaTypes = new List<MediaTypeHeaderValue>(); var supportedMediaTypes = new MediaTypeCollection();
foreach (var filter in responseTypeFilters) foreach (var filter in responseTypeFilters)
{ {
filter.SetContentTypes(supportedMediaTypes); filter.SetContentTypes(supportedMediaTypes);
@ -91,7 +89,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// request's format and IApiResponseMetadataProvider-provided content types similarly to an Accept // request's format and IApiResponseMetadataProvider-provided content types similarly to an Accept
// header and an output formatter's SupportedMediaTypes: Confirm action supports a more specific media // 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". // type than requested e.g. OK if "text/*" requested and action supports "text/plain".
if (!supportedMediaTypes.Any(c => c.IsSubsetOf(contentType))) if (!supportedMediaTypes.Any(c => MediaTypeComparisons.IsSubsetOf(contentType, c)))
{ {
context.Result = new HttpNotFoundResult(); context.Result = new HttpNotFoundResult();
} }

View File

@ -4,24 +4,46 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters namespace Microsoft.AspNet.Mvc.Formatters
{ {
/// <summary> /// <summary>
/// Used to specify mapping between the URL Format and corresponding <see cref="MediaTypeHeaderValue"/>. /// Used to specify mapping between the URL Format and corresponding media type.
/// </summary> /// </summary>
public class FormatterMappings public class FormatterMappings
{ {
private readonly Dictionary<string, MediaTypeHeaderValue> _map = private readonly Dictionary<string, string> _map =
new Dictionary<string, MediaTypeHeaderValue>(StringComparer.OrdinalIgnoreCase); new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// Sets mapping for the format to specified <see cref="MediaTypeHeaderValue"/>. /// Sets mapping for the format to specified media type.
/// If the format already exists, the <see cref="MediaTypeHeaderValue"/> will be overwritten with the new value. /// If the format already exists, the media type will be overwritten with the new value.
/// </summary> /// </summary>
/// <param name="format">The format value.</param> /// <param name="format">The format value.</param>
/// <param name="contentType">The <see cref="MediaTypeHeaderValue"/> for the format value.</param> /// <param name="contentType">The media type for the format value.</param>
public void SetMediaTypeMappingForFormat(string format, string contentType)
{
if (format == null)
{
throw new ArgumentNullException(nameof(format));
}
if (contentType == null)
{
throw new ArgumentNullException(nameof(contentType));
}
SetMediaTypeMappingForFormat(format, MediaTypeHeaderValue.Parse(contentType));
}
/// <summary>
/// Sets mapping for the format to specified media type.
/// If the format already exists, the media type will be overwritten with the new value.
/// </summary>
/// <param name="format">The format value.</param>
/// <param name="contentType">The media type for the format value.</param>
public void SetMediaTypeMappingForFormat(string format, MediaTypeHeaderValue contentType) public void SetMediaTypeMappingForFormat(string format, MediaTypeHeaderValue contentType)
{ {
if (format == null) if (format == null)
@ -36,31 +58,34 @@ namespace Microsoft.AspNet.Mvc.Formatters
ValidateContentType(contentType); ValidateContentType(contentType);
format = RemovePeriodIfPresent(format); format = RemovePeriodIfPresent(format);
_map[format] = contentType.CopyAsReadOnly(); _map[format] = contentType.ToString();
} }
/// <summary> /// <summary>
/// Gets <see cref="MediaTypeHeaderValue"/> for the specified format. /// Gets the media type for the specified format.
/// </summary> /// </summary>
/// <param name="format">The format value.</param> /// <param name="format">The format value.</param>
/// <returns>The <see cref="MediaTypeHeaderValue"/> for input format.</returns> /// <returns>The media type for input format.</returns>
public MediaTypeHeaderValue GetMediaTypeMappingForFormat(string format) public string GetMediaTypeMappingForFormat(string format)
{ {
if (format == null) if (string.IsNullOrEmpty(format))
{ {
throw new ArgumentNullException(nameof(format)); var message = Resources.FormatFormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat(
nameof(format));
throw new ArgumentException(message, nameof(format));
} }
format = RemovePeriodIfPresent(format); format = RemovePeriodIfPresent(format);
MediaTypeHeaderValue value = null; string value = null;
_map.TryGetValue(format, out value); _map.TryGetValue(format, out value);
return value; return value;
} }
/// <summary> /// <summary>
/// Clears the <see cref="MediaTypeHeaderValue"/> mapping for the format. /// Clears the media type mapping for the format.
/// </summary> /// </summary>
/// <param name="format">The format value.</param> /// <param name="format">The format value.</param>
/// <returns><c>true</c> if the format is successfully found and cleared; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the format is successfully found and cleared; otherwise, <c>false</c>.</returns>

View File

@ -8,6 +8,7 @@ using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters namespace Microsoft.AspNet.Mvc.Formatters
@ -37,10 +38,10 @@ namespace Microsoft.AspNet.Mvc.Formatters
public IList<Encoding> SupportedEncodings { get; } = new List<Encoding>(); public IList<Encoding> SupportedEncodings { get; } = new List<Encoding>();
/// <summary> /// <summary>
/// Gets the mutable collection of <see cref="MediaTypeHeaderValue"/> elements supported by /// Gets the mutable collection of media type elements supported by
/// this <see cref="InputFormatter"/>. /// this <see cref="InputFormatter"/>.
/// </summary> /// </summary>
public IList<MediaTypeHeaderValue> SupportedMediaTypes { get; } = new List<MediaTypeHeaderValue>(); public MediaTypeCollection SupportedMediaTypes { get; } = new MediaTypeCollection();
protected object GetDefaultValueForType(Type modelType) protected object GetDefaultValueForType(Type modelType)
{ {
@ -61,8 +62,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
} }
var contentType = context.HttpContext.Request.ContentType; var contentType = context.HttpContext.Request.ContentType;
MediaTypeHeaderValue requestContentType; if (string.IsNullOrEmpty(contentType))
if (!MediaTypeHeaderValue.TryParse(contentType, out requestContentType))
{ {
return false; return false;
} }
@ -71,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// client sent "text/plain" data and this formatter supports "text/*". // client sent "text/plain" data and this formatter supports "text/*".
return SupportedMediaTypes.Any(supportedMediaType => return SupportedMediaTypes.Any(supportedMediaType =>
{ {
return requestContentType.IsSubsetOf(supportedMediaType); return MediaTypeComparisons.IsSubsetOf(supportedMediaType, contentType);
}); });
} }
@ -107,27 +107,28 @@ namespace Microsoft.AspNet.Mvc.Formatters
/// <summary> /// <summary>
/// Returns an <see cref="Encoding"/> based on <paramref name="context"/>'s /// Returns an <see cref="Encoding"/> based on <paramref name="context"/>'s
/// <see cref="MediaTypeHeaderValue.Charset"/>. /// character set.
/// </summary> /// </summary>
/// <param name="context">The <see cref="InputFormatterContext"/>.</param> /// <param name="context">The <see cref="InputFormatterContext"/>.</param>
/// <returns> /// <returns>
/// An <see cref="Encoding"/> based on <paramref name="context"/>'s /// An <see cref="Encoding"/> based on <paramref name="context"/>'s
/// <see cref="MediaTypeHeaderValue.Charset"/>. <c>null</c> if no supported encoding was found. /// character set. <c>null</c> if no supported encoding was found.
/// </returns> /// </returns>
protected Encoding SelectCharacterEncoding(InputFormatterContext context) protected Encoding SelectCharacterEncoding(InputFormatterContext context)
{ {
var request = context.HttpContext.Request; var request = context.HttpContext.Request;
MediaTypeHeaderValue contentType; if (request.ContentType != null)
MediaTypeHeaderValue.TryParse(request.ContentType, out contentType);
if (contentType != null)
{ {
var charset = contentType.Charset; var encoding = MediaTypeEncoding.GetEncoding(request.ContentType);
if (!string.IsNullOrEmpty(charset)) if (encoding != null)
{ {
foreach (var supportedEncoding in SupportedEncodings) foreach (var supportedEncoding in SupportedEncodings)
{ {
if (string.Equals(charset, supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase)) if (string.Equals(
encoding.WebName,
supportedEncoding.WebName,
StringComparison.OrdinalIgnoreCase))
{ {
return supportedEncoding; return supportedEncoding;
} }

View File

@ -0,0 +1,73 @@
// 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.Collections.ObjectModel;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters
{
/// <summary>
/// A collection of media types.
/// </summary>
public class MediaTypeCollection : Collection<string>
{
/// <summary>
/// Initializes a new instance of <see cref="MediaTypeCollection"/>.
/// </summary>
public MediaTypeCollection()
{
}
/// <summary>
/// Adds an object to the end of the <see cref="MediaTypeCollection"/>.
/// </summary>
/// <param name="item">The media type to be added to the end of the <see cref="MediaTypeCollection"/>.</param>
public void Add(MediaTypeHeaderValue item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
Add(item.ToString());
}
/// <summary>
/// Inserts an element into the <see cref="MediaTypeCollection"/> at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
/// <param name="item">The media type to insert.</param>
public void Insert(int index, MediaTypeHeaderValue item)
{
if (index < 0 || index > Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
Insert(index, item.ToString());
}
/// <summary>
/// Removes the first occurrence of a specific media type from the <see cref="MediaTypeCollection"/>.
/// </summary>
/// <param name="item"></param>
/// <returns><code>true</code> if <paramref name="item"/> is successfully removed; otherwise, <code>false</code>.
/// This method also returns <code>false</code> if <paramref name="item"/> was not found in the original
/// <see cref="MediaTypeCollection"/>.</returns>
public bool Remove(MediaTypeHeaderValue item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
return Remove(item.ToString());
}
}
}

View File

@ -0,0 +1,120 @@
// 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 Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters
{
/// <summary>
/// Different types of tests against media type values.
/// </summary>
public static class MediaTypeComparisons
{
/// <summary>
/// Determines if the <paramref name="subset" /> media type is a subset of the <paramref name="set" /> media type
/// without taking into account the quality parameter.
/// </summary>
/// <param name="set">The more general media type.</param>
/// <param name="subset">The more specific media type.</param>
/// <returns><code>true</code> if <paramref name="set" /> is a more general media type than <paramref name="subset"/>;
/// otherwise <code>false</code>.</returns>
public static bool IsSubsetOf(StringSegment set, string subset)
{
return IsSubsetOf(set, new StringSegment(subset));
}
/// <summary>
/// Determines if the <paramref name="subset" /> media type is a subset of the <paramref name="set" /> media type
/// without taking into account the quality parameter.
/// </summary>
/// <param name="set">The more general media type.</param>
/// <param name="subset">The more specific media type.</param>
/// <returns><code>true</code> if <paramref name="set" /> is a more general media type than <paramref name="subset"/>;
/// otherwise <code>false</code>.</returns>
public static bool IsSubsetOf(string set, string subset)
{
return IsSubsetOf(new StringSegment(set), new StringSegment(subset));
}
/// <summary>
/// Determines if the <paramref name="subset" /> media type is a subset of the <paramref name="set" /> media type.
/// Two media types are compatible if one is a subset of the other ignoring any charset
/// parameter.
/// </summary>
/// <param name="set">The more general media type.</param>
/// <param name="subset">The more specific media type.</param>
/// <param name="ignoreQuality">Whether or not we should skip checking the quality parameter.</param>
/// <returns><code>true</code> if <paramref name="set" /> is a more general media type than <paramref name="subset"/>;
/// otherwise <code>false</code>.</returns>
public static bool IsSubsetOf(StringSegment set, StringSegment subset)
{
if (!set.HasValue || !subset.HasValue)
{
return false;
}
MediaTypeHeaderValue setMediaType;
MediaTypeHeaderValue subSetMediaType;
return MediaTypeHeaderValue.TryParse(set.Value, out setMediaType) &&
MediaTypeHeaderValue.TryParse(subset.Value, out subSetMediaType) &&
subSetMediaType.IsSubsetOf(setMediaType);
}
/// <summary>
/// Determines if the type of a given <paramref name="mediaType" /> matches all types, E.g, */*.
/// </summary>
/// <param name="mediaType">The media type to check</param>
/// <returns><code>true</code> if the <paramref name="mediaType" /> matches all subtypes; otherwise <code>false</code>.</returns>
public static bool MatchesAllTypes(string mediaType)
{
return MatchesAllTypes(new StringSegment(mediaType));
}
/// <summary>
/// Determines if the type of a given <paramref name="mediaType" /> matches all types, E.g, */*.
/// </summary>
/// <param name="mediaType">The media type to check</param>
/// <returns><code>true</code> if the <paramref name="mediaType" /> matches all subtypes; otherwise <code>false</code>.</returns>
public static bool MatchesAllTypes(StringSegment mediaType)
{
if (!mediaType.HasValue)
{
return false;
}
MediaTypeHeaderValue parsedMediaType;
return MediaTypeHeaderValue.TryParse(mediaType.Value, out parsedMediaType) &&
parsedMediaType.MatchesAllTypes;
}
/// <summary>
/// Determines if the given <paramref name="mediaType" /> matches all subtypes, E.g, text/*.
/// </summary>
/// <param name="mediaType">The media type to check</param>
/// <returns><code>true</code> if the <paramref name="mediaType" /> matches all subtypes; otherwise <code>false</code>.</returns>
public static bool MatchesAllSubtypes(string mediaType)
{
return MatchesAllSubtypes(new StringSegment(mediaType));
}
/// <summary>
/// Determines if the given <paramref name="mediaType" /> matches all subtypes, E.g, text/*.
/// </summary>
/// <param name="mediaType">The media type to check</param>
/// <returns><code>true</code> if the <paramref name="mediaType" /> matches all subtypes; otherwise <code>false</code>.</returns>
public static bool MatchesAllSubtypes(StringSegment mediaType)
{
if (!mediaType.HasValue)
{
return false;
}
MediaTypeHeaderValue parsedMediaType;
return MediaTypeHeaderValue.TryParse(mediaType.Value, out parsedMediaType) &&
parsedMediaType.MatchesAllSubTypes;
}
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Text;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters
{
/// <summary>
/// A set of operations to manipulate the encoding of a media type value.
/// </summary>
public class MediaTypeEncoding
{
/// <summary>
/// Gets the <see cref="Encoding"/> for the given <see cref="mediaType"/> if it exists.
/// </summary>
/// <param name="mediaType">The media type from which to get the charset parameter.</param>
/// <returns>The <see cref="Encoding"/> of the media type if it exists; otherwise <code>null</code>.</returns>
public static Encoding GetEncoding(StringSegment mediaType)
{
var charset = GetCharsetParameter(mediaType);
return GetEncodingFromCharset(charset);
}
/// <summary>
/// Gets the <see cref="Encoding"/> for the given <see cref="mediaType"/> if it exists.
/// </summary>
/// <param name="mediaType">The media type from which to get the charset parameter.</param>
/// <returns>The <see cref="Encoding"/> of the media type if it exists or a <see cref="StringSegment"/> without value if not.</returns>
public static Encoding GetEncoding(string mediaType)
{
var charset = GetCharsetParameter(new StringSegment(mediaType));
return GetEncodingFromCharset(charset);
}
/// <summary>
/// Gets the charset parameter of the given <paramref name="mediaType"/> if it exists.
/// </summary>
/// <param name="mediaType">The media type from which to get the charset parameter.</param>
/// <returns>The charset of the media type if it exists or a <see cref="StringSegment"/> without value if not.</returns>
public static StringSegment GetCharsetParameter(StringSegment mediaType)
{
MediaTypeHeaderValue parsedMediaType;
if (MediaTypeHeaderValue.TryParse(mediaType.Value, out parsedMediaType))
{
return new StringSegment(parsedMediaType.Charset);
}
return new StringSegment();
}
/// <summary>
/// Replaces the encoding of the given <paramref name="mediaType"/> with the provided
/// <paramref name="encoding"/>.
/// </summary>
/// <param name="mediaType">The media type whose encoding will be replaced.</param>
/// <param name="encoding">The encoding that will replace the encoding in the <paramref name="mediaType"/></param>
/// <returns>A media type with the replaced encoding.</returns>
public static string ReplaceEncoding(string mediaType, Encoding encoding)
{
return ReplaceEncoding(new StringSegment(mediaType), encoding);
}
/// <summary>
/// Replaces the encoding of the given <paramref name="mediaType"/> with the provided
/// <paramref name="encoding"/>.
/// </summary>
/// <param name="mediaType">The media type whose encoding will be replaced.</param>
/// <param name="encoding">The encoding that will replace the encoding in the <paramref name="mediaType"/></param>
/// <returns>A media type with the replaced encoding.</returns>
public static string ReplaceEncoding(StringSegment mediaType, Encoding encoding)
{
var parsedMediaType = MediaTypeHeaderValue.Parse(mediaType.Value);
parsedMediaType.Encoding = encoding;
return parsedMediaType.ToString();
}
private static Encoding GetEncodingFromCharset(StringSegment charset)
{
if (charset.Equals("utf-8", StringComparison.OrdinalIgnoreCase))
{
// This is an optimization for utf-8 that prevents the Substring caused by
// charset.Value
return Encoding.UTF8;
}
try
{
// charset.Value might be an invalid encoding name as in charset=invalid.
// For that reason, we catch the exception thrown by Encoding.GetEncoding
// and return null instead.
return charset.HasValue ? Encoding.GetEncoding(charset.Value) : null;
}
catch (Exception)
{
return null;
}
}
}
}

View File

@ -0,0 +1,41 @@
// 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 Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Mvc.Formatters
{
/// <summary>
/// A media type with its associated quality.
/// </summary>
public struct MediaTypeSegmentWithQuality
{
/// <summary>
/// Initializes an instance of <see cref="MediaTypeSegmentWithQuality"/>.
/// </summary>
/// <param name="mediaType">The <see cref="StringSegment"/> containing the media type.</param>
/// <param name="quality">The quality parameter of the media type or 1 in the case it does not exist.</param>
public MediaTypeSegmentWithQuality(StringSegment mediaType, double quality)
{
MediaType = mediaType;
Quality = quality;
}
/// <summary>
/// Gets the media type of this <see cref="MediaTypeSegmentWithQuality"/>.
/// </summary>
public StringSegment MediaType { get; }
/// <summary>
/// Gets the quality of this <see cref="MediaTypeSegmentWithQuality"/>.
/// </summary>
public double Quality { get; }
/// <inheritdoc />
public override string ToString()
{
// For logging purposes
return MediaType.ToString();
}
}
}

View File

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ApiExplorer; using Microsoft.AspNet.Mvc.ApiExplorer;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters namespace Microsoft.AspNet.Mvc.Formatters
@ -17,16 +18,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
/// </summary> /// </summary>
public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider
{ {
// using a field so we can return it as both IList and IReadOnlyList
private readonly List<MediaTypeHeaderValue> _supportedMediaTypes;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OutputFormatter"/> class. /// Initializes a new instance of the <see cref="OutputFormatter"/> class.
/// </summary> /// </summary>
protected OutputFormatter() protected OutputFormatter()
{ {
SupportedEncodings = new List<Encoding>(); SupportedEncodings = new List<Encoding>();
_supportedMediaTypes = new List<MediaTypeHeaderValue>(); SupportedMediaTypes = new MediaTypeCollection();
} }
/// <summary> /// <summary>
@ -37,13 +35,10 @@ namespace Microsoft.AspNet.Mvc.Formatters
public IList<Encoding> SupportedEncodings { get; } public IList<Encoding> SupportedEncodings { get; }
/// <summary> /// <summary>
/// Gets the mutable collection of <see cref="MediaTypeHeaderValue"/> elements supported by /// Gets the mutable collection of media type elements supported by
/// this <see cref="OutputFormatter"/>. /// this <see cref="OutputFormatter"/>.
/// </summary> /// </summary>
public IList<MediaTypeHeaderValue> SupportedMediaTypes public MediaTypeCollection SupportedMediaTypes { get; }
{
get { return _supportedMediaTypes; }
}
/// <summary> /// <summary>
/// Returns a value indicating whether or not the given type can be written by this serializer. /// Returns a value indicating whether or not the given type can be written by this serializer.
@ -56,8 +51,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
} }
/// <inheritdoc /> /// <inheritdoc />
public virtual IReadOnlyList<MediaTypeHeaderValue> GetSupportedContentTypes( public virtual IReadOnlyList<string> GetSupportedContentTypes(
MediaTypeHeaderValue contentType, string contentType,
Type objectType) Type objectType)
{ {
if (!CanWriteType(objectType)) if (!CanWriteType(objectType))
@ -68,21 +63,21 @@ namespace Microsoft.AspNet.Mvc.Formatters
if (contentType == null) if (contentType == null)
{ {
// If contentType is null, then any type we support is valid. // If contentType is null, then any type we support is valid.
return _supportedMediaTypes.Count > 0 ? _supportedMediaTypes : null; return SupportedMediaTypes.Count > 0 ? SupportedMediaTypes : null;
} }
else else
{ {
List<MediaTypeHeaderValue> mediaTypes = null; List<string> mediaTypes = null;
// Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*" // 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. // 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 (MediaTypeComparisons.IsSubsetOf(new StringSegment(contentType), mediaType))
{ {
if (mediaTypes == null) if (mediaTypes == null)
{ {
mediaTypes = new List<MediaTypeHeaderValue>(); mediaTypes = new List<string>();
} }
mediaTypes.Add(mediaType); mediaTypes.Add(mediaType);
@ -114,15 +109,19 @@ namespace Microsoft.AspNet.Mvc.Formatters
return encoding; return encoding;
} }
var charset = context.ContentType?.Charset; if (context.ContentType.HasValue)
if (charset != null)
{ {
for (var i = 0; i < SupportedEncodings.Count; i++) var contentTypeEncoding = MediaTypeEncoding.GetCharsetParameter(context.ContentType);
if (contentTypeEncoding.HasValue)
{ {
if (string.Equals(charset, SupportedEncodings[i].WebName, StringComparison.OrdinalIgnoreCase)) for (var i = 0; i < SupportedEncodings.Count; i++)
{ {
// This is supported. var supportedEncoding = SupportedEncodings[i];
return context.ContentType.Encoding; if (contentTypeEncoding.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase))
{
// This is supported.
return SupportedEncodings[i];
}
} }
} }
} }
@ -138,19 +137,19 @@ namespace Microsoft.AspNet.Mvc.Formatters
{ {
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (!CanWriteType(context.ObjectType)) if (!CanWriteType(context.ObjectType))
{ {
return false; return false;
} }
if (context.ContentType == null) if (!context.ContentType.HasValue)
{ {
// If the desired content type is set to null, then the current formatter can write anything // If the desired content type is set to null, then the current formatter can write anything
// it wants. // it wants.
if (SupportedMediaTypes.Count > 0) if (SupportedMediaTypes.Count > 0)
{ {
context.ContentType = SupportedMediaTypes[0]; context.ContentType = new StringSegment(SupportedMediaTypes[0]);
return true; return true;
} }
else else
@ -163,12 +162,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
// Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*" // 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 // requested and formatter supports "text/plain". contentType is typically what we got in an Accept
// header. // header.
var contentType = context.ContentType;
for (var i = 0; i < SupportedMediaTypes.Count; i++) for (var i = 0; i < SupportedMediaTypes.Count; i++)
{ {
var mediaType = SupportedMediaTypes[i]; var supportedMediaType = SupportedMediaTypes[i];
if (mediaType.IsSubsetOf(context.ContentType)) if (MediaTypeComparisons.IsSubsetOf(contentType, supportedMediaType))
{ {
context.ContentType = mediaType; context.ContentType = new StringSegment(SupportedMediaTypes[i]);
return true; return true;
} }
} }
@ -186,12 +186,12 @@ namespace Microsoft.AspNet.Mvc.Formatters
} }
var selectedMediaType = context.ContentType; var selectedMediaType = context.ContentType;
if (selectedMediaType == null) if (!selectedMediaType.HasValue)
{ {
// If content type is not set then set it based on supported media types. // If content type is not set then set it based on supported media types.
if (SupportedEncodings.Count > 0) if (SupportedEncodings.Count > 0)
{ {
selectedMediaType = SupportedMediaTypes[0]; selectedMediaType = new StringSegment(SupportedMediaTypes[0]);
} }
else else
{ {
@ -199,9 +199,6 @@ namespace Microsoft.AspNet.Mvc.Formatters
} }
} }
// Copy the media type as it may be a 'frozen' instance.
selectedMediaType = selectedMediaType.Copy();
// Note: 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.
// //
@ -215,7 +212,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
if (selectedEncoding != null) if (selectedEncoding != null)
{ {
// Override the content type value even if one already existed. // Override the content type value even if one already existed.
selectedMediaType.Encoding = selectedEncoding; var mediaTypeWithCharset = GetMediaTypeWithCharset(selectedMediaType.Value, selectedEncoding);
selectedMediaType = new StringSegment(mediaTypeWithCharset);
} }
context.ContentType = selectedMediaType; context.ContentType = selectedMediaType;
@ -236,7 +234,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
} }
var response = context.HttpContext.Response; var response = context.HttpContext.Response;
response.ContentType = context.ContentType?.ToString(); response.ContentType = context.ContentType.Value;
} }
/// <summary> /// <summary>
@ -246,6 +244,33 @@ namespace Microsoft.AspNet.Mvc.Formatters
/// <returns>A task which can write the response body.</returns> /// <returns>A task which can write the response body.</returns>
public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context); public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context);
/// <summary>
/// Adds or replaces the charset parameter in a given <paramref name="mediaType"/> with the
/// given <paramref name="encoding"/>.
/// </summary>
/// <param name="mediaType">The <see cref="StringSegment"/> with the media type.</param>
/// <param name="encoding">
/// The <see cref="Encoding"/> to add or replace in the <paramref name="mediaType"/>.
/// </param>
/// <returns>The mediaType with the given encoding.</returns>
protected string GetMediaTypeWithCharset(string mediaType, Encoding encoding)
{
var mediaTypeEncoding = MediaTypeEncoding.GetEncoding(mediaType);
if (mediaTypeEncoding == encoding)
{
return mediaType;
}
else if (mediaTypeEncoding == null)
{
return CreateMediaTypeWithEncoding(mediaType, encoding);
}
else
{
// This can happen if the user has overriden SelectCharacterEncoding
return MediaTypeEncoding.ReplaceEncoding(mediaType, encoding);
}
}
private Encoding MatchAcceptCharacterEncoding(IList<StringWithQualityHeaderValue> acceptCharsetHeaders) private Encoding MatchAcceptCharacterEncoding(IList<StringWithQualityHeaderValue> acceptCharsetHeaders)
{ {
if (acceptCharsetHeaders != null && acceptCharsetHeaders.Count > 0) if (acceptCharsetHeaders != null && acceptCharsetHeaders.Count > 0)
@ -329,5 +354,15 @@ namespace Microsoft.AspNet.Mvc.Formatters
sorted.Reverse(); sorted.Reverse();
return sorted; return sorted;
} }
private static string CreateMediaTypeWithEncoding(string mediaType, Encoding encoding)
{
return CreateMediaTypeWithEncoding(new StringSegment(mediaType), encoding);
}
private static string CreateMediaTypeWithEncoding(StringSegment mediaType, Encoding encoding)
{
return $"{mediaType.Value}; charset={encoding.WebName}";
}
} }
} }

View File

@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Internal; using Microsoft.AspNet.Mvc.Internal;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters namespace Microsoft.AspNet.Mvc.Formatters
@ -19,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
{ {
SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode); SupportedEncodings.Add(Encoding.Unicode);
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/plain").CopyAsReadOnly()); SupportedMediaTypes.Add("text/plain");
} }
public override bool CanWriteResult(OutputFormatterCanWriteContext context) public override bool CanWriteResult(OutputFormatterCanWriteContext context)
@ -33,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// always return it as a text/plain format. // always return it as a text/plain format.
if (context.ObjectType == typeof(string) || context.Object is string) if (context.ObjectType == typeof(string) || context.Object is string)
{ {
context.ContentType = SupportedMediaTypes[0]; context.ContentType = new StringSegment(SupportedMediaTypes[0]);
return true; return true;
} }
@ -55,7 +56,9 @@ namespace Microsoft.AspNet.Mvc.Formatters
var response = context.HttpContext.Response; var response = context.HttpContext.Response;
return response.WriteAsync(valueAsString, context.ContentType?.Encoding ?? Encoding.UTF8); return response.WriteAsync(
valueAsString,
MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8);
} }
} }
} }

View File

@ -13,6 +13,7 @@ using Microsoft.AspNet.Mvc.Internal;
using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.Logging;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Infrastructure namespace Microsoft.AspNet.Mvc.Infrastructure
@ -98,10 +99,10 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
{ {
if (result.ContentTypes == null) if (result.ContentTypes == null)
{ {
result.ContentTypes = new List<MediaTypeHeaderValue>(); result.ContentTypes = new MediaTypeCollection();
} }
result.ContentTypes.Add(MediaTypeHeaderValue.Parse(responseContentType)); result.ContentTypes.Add(responseContentType);
} }
} }
@ -157,7 +158,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
/// </returns> /// </returns>
protected virtual IOutputFormatter SelectFormatter( protected virtual IOutputFormatter SelectFormatter(
OutputFormatterWriteContext formatterContext, OutputFormatterWriteContext formatterContext,
IList<MediaTypeHeaderValue> contentTypes, MediaTypeCollection contentTypes,
IList<IOutputFormatter> formatters) IList<IOutputFormatter> formatters)
{ {
if (formatterContext == null) if (formatterContext == null)
@ -185,14 +186,14 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
} }
var request = formatterContext.HttpContext.Request; var request = formatterContext.HttpContext.Request;
var acceptValues = PrepareAcceptValues(request.GetTypedHeaders().Accept);
var mediaTypes = GetMediaTypes(contentTypes, request);
IOutputFormatter selectedFormatter = null; IOutputFormatter selectedFormatter = null;
if (contentTypes == null || contentTypes.Count == 0) if (contentTypes.Count == 0)
{ {
// 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. Let the formatter choose the Content-Type. // which can write the type. Let the formatter choose the Content-Type.
if (acceptValues == null || acceptValues.Count == 0) if (!(mediaTypes.Count > 0))
{ {
Logger.NoAcceptForNegotiation(); Logger.NoAcceptForNegotiation();
@ -207,13 +208,13 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
selectedFormatter = SelectFormatterUsingSortedAcceptHeaders( selectedFormatter = SelectFormatterUsingSortedAcceptHeaders(
formatterContext, formatterContext,
formatters, formatters,
acceptValues); mediaTypes);
// 2. No formatter was found based on Accept header. Fallback to the first formatter which can write // 2. No formatter was found based on Accept header. Fallback to the first formatter which can write
// the type. Let the formatter choose the Content-Type. // the type. Let the formatter choose the Content-Type.
if (selectedFormatter == null) if (selectedFormatter == null)
{ {
Logger.NoFormatterFromNegotiation(acceptValues); Logger.NoFormatterFromNegotiation(mediaTypes);
// Set this flag to indicate that content-negotiation has failed to let formatters decide // Set this flag to indicate that content-negotiation has failed to let formatters decide
// if they want to write the response or not. // if they want to write the response or not.
@ -224,32 +225,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
} }
else else
{ {
if (acceptValues != null && acceptValues.Count > 0) if (mediaTypes.Count > 0)
{ {
// 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".
for (var i = acceptValues.Count - 1; i >= 0; i--)
{
var isCompatible = false;
for (var j = 0; j < contentTypes.Count; j++)
{
if (contentTypes[j].IsSubsetOf(acceptValues[i]))
{
isCompatible = true;
}
}
if (!isCompatible)
{
acceptValues.RemoveAt(i);
}
}
selectedFormatter = SelectFormatterUsingSortedAcceptHeaders( selectedFormatter = SelectFormatterUsingSortedAcceptHeaders(
formatterContext, formatterContext,
formatters, formatters,
acceptValues); mediaTypes);
} }
if (selectedFormatter == null) if (selectedFormatter == null)
@ -270,6 +251,58 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
return selectedFormatter; return selectedFormatter;
} }
private List<MediaTypeSegmentWithQuality> GetMediaTypes(
MediaTypeCollection contentTypes,
HttpRequest request)
{
var result = new List<MediaTypeSegmentWithQuality>();
var parsedHeaders = request.GetTypedHeaders().Accept;
for (var i = 0; i < parsedHeaders?.Count; i++)
{
result.Add(new MediaTypeSegmentWithQuality(
new StringSegment(parsedHeaders[i].ToString()),
parsedHeaders[i].Quality ?? 1.0));
}
for (var i = 0; i < result.Count; i++)
{
if (!RespectBrowserAcceptHeader &&
MediaTypeComparisons.MatchesAllTypes(result[i].MediaType) &&
MediaTypeComparisons.MatchesAllSubtypes(result[i].MediaType))
{
result.Clear();
return result;
}
if (!InAcceptableMediaTypes(result[i].MediaType, contentTypes))
{
result.RemoveAt(i);
}
}
result.Sort((left, right) => left.Quality > right.Quality ? -1 : (left.Quality == right.Quality ? 0 : 1));
return result;
}
private static bool InAcceptableMediaTypes(StringSegment mediaType, MediaTypeCollection acceptableMediaTypes)
{
if (acceptableMediaTypes.Count == 0)
{
return true;
}
for (int i = 0; i < acceptableMediaTypes.Count; i++)
{
if (MediaTypeComparisons.IsSubsetOf(mediaType, acceptableMediaTypes[i]))
{
return true;
}
}
return false;
}
/// <summary> /// <summary>
/// Selects the <see cref="IOutputFormatter"/> to write the response. The first formatter which /// Selects the <see cref="IOutputFormatter"/> to write the response. The first formatter which
/// can write the response should be chosen without any consideration for content type. /// can write the response should be chosen without any consideration for content type.
@ -297,7 +330,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
foreach (var formatter in formatters) foreach (var formatter in formatters)
{ {
formatterContext.ContentType = null; formatterContext.ContentType = new StringSegment();
if (formatter.CanWriteResult(formatterContext)) if (formatter.CanWriteResult(formatterContext))
{ {
return formatter; return formatter;
@ -324,7 +357,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
protected virtual IOutputFormatter SelectFormatterUsingSortedAcceptHeaders( protected virtual IOutputFormatter SelectFormatterUsingSortedAcceptHeaders(
OutputFormatterWriteContext formatterContext, OutputFormatterWriteContext formatterContext,
IList<IOutputFormatter> formatters, IList<IOutputFormatter> formatters,
IList<MediaTypeHeaderValue> sortedAcceptHeaders) IList<MediaTypeSegmentWithQuality> sortedAcceptHeaders)
{ {
if (formatterContext == null) if (formatterContext == null)
{ {
@ -341,11 +374,13 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
throw new ArgumentNullException(nameof(sortedAcceptHeaders)); throw new ArgumentNullException(nameof(sortedAcceptHeaders));
} }
foreach (var contentType in sortedAcceptHeaders) for (var i = 0; i < sortedAcceptHeaders.Count; i++)
{ {
foreach (var formatter in formatters) var mediaType = sortedAcceptHeaders[i];
formatterContext.ContentType = mediaType.MediaType;
for(var j = 0;j < formatters.Count;j++)
{ {
formatterContext.ContentType = contentType; var formatter = formatters[j];
if (formatter.CanWriteResult(formatterContext)) if (formatter.CanWriteResult(formatterContext))
{ {
return formatter; return formatter;
@ -373,7 +408,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
protected virtual IOutputFormatter SelectFormatterUsingAnyAcceptableContentType( protected virtual IOutputFormatter SelectFormatterUsingAnyAcceptableContentType(
OutputFormatterWriteContext formatterContext, OutputFormatterWriteContext formatterContext,
IList<IOutputFormatter> formatters, IList<IOutputFormatter> formatters,
IList<MediaTypeHeaderValue> acceptableContentTypes) MediaTypeCollection acceptableContentTypes)
{ {
if (formatterContext == null) if (formatterContext == null)
{ {
@ -394,7 +429,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
{ {
foreach (var contentType in acceptableContentTypes) foreach (var contentType in acceptableContentTypes)
{ {
formatterContext.ContentType = contentType; formatterContext.ContentType = new StringSegment(contentType);
if (formatter.CanWriteResult(formatterContext)) if (formatter.CanWriteResult(formatterContext))
{ {
return formatter; return formatter;
@ -405,90 +440,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
return null; return null;
} }
// There's no allocation-free way to sort an IList so we're going to have to live with the private void ValidateContentTypes(MediaTypeCollection contentTypes)
// copy + insertion sort.
private IList<MediaTypeHeaderValue> PrepareAcceptValues(IList<MediaTypeHeaderValue> values)
{
if (values == null || values.Count == 0)
{
return null;
}
// By default we want to ignore considering accept headers for content negotiation when
// they have a media type like */* in them. Browsers typically have these media types.
// In these cases we would want the first formatter in the list of output formatters to
// write the response. This default behavior can be changed through options, so checking here.
if (!RespectBrowserAcceptHeader)
{
for (var i = 0; i < values.Count; i++)
{
if (values[i].MatchesAllTypes)
{
return null;
}
}
}
// Degenerate case, we can avoid copying anything.
if (values.Count == 1)
{
return values;
}
var sortNeeded = false;
var count = 0;
for (var i = 0; i < values.Count; i++)
{
var value = values[i];
if (value.Quality == HeaderQuality.NoMatch)
{
// Exclude this one
}
else if (value.Quality != null)
{
count++;
sortNeeded = true;
}
else
{
count++;
}
}
if (!sortNeeded)
{
return values;
}
var sorted = new List<MediaTypeHeaderValue>(count);
for (var i = 0; i < values.Count; i++)
{
var value = values[i];
if (value.Quality == HeaderQuality.NoMatch)
{
// Exclude this one
}
else
{
var position = sorted.BinarySearch(value, MediaTypeHeaderValueComparer.QualityComparer);
if (position >= 0)
{
sorted.Insert(position + 1, value);
}
else
{
sorted.Insert(~position, value);
}
}
}
// We want a descending sort, but BinarySearch does ascending
sorted.Reverse();
return sorted;
}
private void ValidateContentTypes(IList<MediaTypeHeaderValue> contentTypes)
{ {
if (contentTypes == null) if (contentTypes == null)
{ {
@ -498,7 +450,8 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
for (var i = 0; i < contentTypes.Count; i++) for (var i = 0; i < contentTypes.Count; i++)
{ {
var contentType = contentTypes[i]; var contentType = contentTypes[i];
if (contentType.MatchesAllTypes || contentType.MatchesAllSubTypes) if (MediaTypeComparisons.MatchesAllTypes(contentType) ||
MediaTypeComparisons.MatchesAllSubtypes(contentType))
{ {
var message = Resources.FormatObjectResult_MatchAllContentType( var message = Resources.FormatObjectResult_MatchAllContentType(
contentType, contentType,

View File

@ -3,8 +3,7 @@
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
using Microsoft.Net.Http.Headers; using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Mvc.Internal namespace Microsoft.AspNet.Mvc.Internal
{ {
@ -30,44 +29,47 @@ namespace Microsoft.AspNet.Mvc.Internal
/// <param name="resolvedContentType">The content type to be used for the response content type header</param> /// <param name="resolvedContentType">The content type to be used for the response content type header</param>
/// <param name="resolvedContentTypeEncoding">Encoding to be used for writing the response</param> /// <param name="resolvedContentTypeEncoding">Encoding to be used for writing the response</param>
public static void ResolveContentTypeAndEncoding( public static void ResolveContentTypeAndEncoding(
MediaTypeHeaderValue actionResultContentType, string actionResultContentType,
string httpResponseContentType, string httpResponseContentType,
MediaTypeHeaderValue defaultContentType, string defaultContentType,
out string resolvedContentType, out string resolvedContentType,
out Encoding resolvedContentTypeEncoding) out Encoding resolvedContentTypeEncoding)
{ {
Debug.Assert(defaultContentType != null); Debug.Assert(defaultContentType != null);
Debug.Assert(defaultContentType.Encoding != null);
var defaultContentTypeEncoding = MediaTypeEncoding.GetEncoding(defaultContentType);
Debug.Assert(defaultContentTypeEncoding != null);
// 1. User sets the ContentType property on the action result // 1. User sets the ContentType property on the action result
if (actionResultContentType != null) if (actionResultContentType != null)
{ {
resolvedContentType = actionResultContentType.ToString(); resolvedContentType = actionResultContentType;
resolvedContentTypeEncoding = actionResultContentType.Encoding ?? defaultContentType.Encoding; var actionResultEncoding = MediaTypeEncoding.GetEncoding(actionResultContentType);
resolvedContentTypeEncoding = actionResultEncoding ?? defaultContentTypeEncoding;
return; return;
} }
// 2. User sets the ContentType property on the http response directly // 2. User sets the ContentType property on the http response directly
if (!string.IsNullOrEmpty(httpResponseContentType)) if (!string.IsNullOrEmpty(httpResponseContentType))
{ {
MediaTypeHeaderValue mediaType; var mediaTypeEncoding = MediaTypeEncoding.GetEncoding(httpResponseContentType);
if (MediaTypeHeaderValue.TryParse(httpResponseContentType, out mediaType)) if (mediaTypeEncoding != null)
{ {
resolvedContentType = httpResponseContentType; resolvedContentType = httpResponseContentType;
resolvedContentTypeEncoding = mediaType.Encoding ?? defaultContentType.Encoding; resolvedContentTypeEncoding = mediaTypeEncoding;
} }
else else
{ {
resolvedContentType = httpResponseContentType; resolvedContentType = httpResponseContentType;
resolvedContentTypeEncoding = defaultContentType.Encoding; resolvedContentTypeEncoding = defaultContentTypeEncoding;
} }
return; return;
} }
// 3. Fall-back to the default content type // 3. Fall-back to the default content type
resolvedContentType = defaultContentType.ToString(); resolvedContentType = defaultContentType;
resolvedContentTypeEncoding = defaultContentType.Encoding; resolvedContentTypeEncoding = defaultContentTypeEncoding;
} }
} }
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Logging namespace Microsoft.AspNet.Mvc.Logging
@ -16,7 +17,7 @@ namespace Microsoft.AspNet.Mvc.Logging
private static readonly Action<ILogger, IOutputFormatter, string, Exception> _formatterSelected; private static readonly Action<ILogger, IOutputFormatter, string, Exception> _formatterSelected;
private static readonly Action<ILogger, string, Exception> _skippedContentNegotiation; private static readonly Action<ILogger, string, Exception> _skippedContentNegotiation;
private static readonly Action<ILogger, string, Exception> _noAcceptForNegotiation; private static readonly Action<ILogger, string, Exception> _noAcceptForNegotiation;
private static readonly Action<ILogger, IEnumerable<MediaTypeHeaderValue>, Exception> _noFormatterFromNegotiation; private static readonly Action<ILogger, IEnumerable<MediaTypeSegmentWithQuality>, Exception> _noFormatterFromNegotiation;
static ObjectResultExecutorLoggerExtensions() static ObjectResultExecutorLoggerExtensions()
{ {
@ -40,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.Logging
LogLevel.Debug, LogLevel.Debug,
4, 4,
"No information found on request to perform content negotiation."); "No information found on request to perform content negotiation.");
_noFormatterFromNegotiation = LoggerMessage.Define<IEnumerable<MediaTypeHeaderValue>>( _noFormatterFromNegotiation = LoggerMessage.Define<IEnumerable<MediaTypeSegmentWithQuality>>(
LogLevel.Debug, LogLevel.Debug,
5, 5,
"Could not find an output formatter based on content negotiation. Accepted types were ({AcceptTypes})"); "Could not find an output formatter based on content negotiation. Accepted types were ({AcceptTypes})");
@ -67,9 +68,9 @@ namespace Microsoft.AspNet.Mvc.Logging
_formatterSelected(logger, outputFormatter, contentType, null); _formatterSelected(logger, outputFormatter, contentType, null);
} }
public static void SkippedContentNegotiation(this ILogger logger, MediaTypeHeaderValue contentType) public static void SkippedContentNegotiation(this ILogger logger, string contentType)
{ {
_skippedContentNegotiation(logger, Convert.ToString(contentType), null); _skippedContentNegotiation(logger, contentType, null);
} }
public static void NoAcceptForNegotiation(this ILogger logger) public static void NoAcceptForNegotiation(this ILogger logger)
@ -77,7 +78,7 @@ namespace Microsoft.AspNet.Mvc.Logging
_noAcceptForNegotiation(logger, null, null); _noAcceptForNegotiation(logger, null, null);
} }
public static void NoFormatterFromNegotiation(this ILogger logger, IEnumerable<MediaTypeHeaderValue> acceptTypes) public static void NoFormatterFromNegotiation(this ILogger logger, IList<MediaTypeSegmentWithQuality> acceptTypes)
{ {
_noFormatterFromNegotiation(logger, acceptTypes, null); _noFormatterFromNegotiation(logger, acceptTypes, null);
} }

View File

@ -53,8 +53,7 @@ namespace Microsoft.AspNet.Mvc
public FilterCollection Filters { get; } public FilterCollection Filters { get; }
/// <summary> /// <summary>
/// Used to specify mapping between the URL Format and corresponding /// Used to specify mapping between the URL Format and corresponding media type.
/// <see cref="Net.Http.Headers.MediaTypeHeaderValue"/>.
/// </summary> /// </summary>
public FormatterMappings FormatterMappings { get; } public FormatterMappings FormatterMappings { get; }

View File

@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -17,14 +15,14 @@ namespace Microsoft.AspNet.Mvc
{ {
Value = value; Value = value;
Formatters = new FormatterCollection<IOutputFormatter>(); Formatters = new FormatterCollection<IOutputFormatter>();
ContentTypes = new List<MediaTypeHeaderValue>(); ContentTypes = new MediaTypeCollection();
} }
public object Value { get; set; } public object Value { get; set; }
public FormatterCollection<IOutputFormatter> Formatters { get; set; } public FormatterCollection<IOutputFormatter> Formatters { get; set; }
public IList<MediaTypeHeaderValue> ContentTypes { get; set; } public MediaTypeCollection ContentTypes { get; set; }
public Type DeclaredType { get; set; } public Type DeclaredType { get; set; }

View File

@ -34,10 +34,6 @@ namespace Microsoft.AspNet.Mvc
{ {
throw new ArgumentNullException(nameof(fileName)); throw new ArgumentNullException(nameof(fileName));
} }
if (contentType == null)
{
throw new ArgumentNullException(nameof(contentType));
}
} }
/// <summary> /// <summary>
@ -47,18 +43,13 @@ namespace Microsoft.AspNet.Mvc
/// <param name="fileName">The path to the file. The path must be an absolute path.</param> /// <param name="fileName">The path to the file. The path must be an absolute path.</param>
/// <param name="contentType">The Content-Type header of the response.</param> /// <param name="contentType">The Content-Type header of the response.</param>
public PhysicalFileResult(string fileName, MediaTypeHeaderValue contentType) public PhysicalFileResult(string fileName, MediaTypeHeaderValue contentType)
: base(contentType) : base(contentType?.ToString())
{ {
if (fileName == null) if (fileName == null)
{ {
throw new ArgumentNullException(nameof(fileName)); throw new ArgumentNullException(nameof(fileName));
} }
if (contentType == null)
{
throw new ArgumentNullException(nameof(contentType));
}
FileName = fileName; FileName = fileName;
} }

View File

@ -8,6 +8,7 @@ using Microsoft.AspNet.Mvc.ApiExplorer;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
@ -31,7 +32,7 @@ namespace Microsoft.AspNet.Mvc
} }
Type = type; Type = type;
ContentTypes = new List<MediaTypeHeaderValue>(); ContentTypes = new MediaTypeCollection();
} }
/// <summary> /// <summary>
@ -46,12 +47,21 @@ namespace Microsoft.AspNet.Mvc
throw new ArgumentNullException(nameof(contentType)); throw new ArgumentNullException(nameof(contentType));
} }
// We want to ensure that the given provided content types are valid values, so
// we validate them using the semantics of MediaTypeHeaderValue.
MediaTypeHeaderValue.Parse(contentType);
for (var i = 0; i < additionalContentTypes.Length; i++)
{
MediaTypeHeaderValue.Parse(additionalContentTypes[i]);
}
ContentTypes = GetContentTypes(contentType, additionalContentTypes); ContentTypes = GetContentTypes(contentType, additionalContentTypes);
} }
public Type Type { get; set; } public Type Type { get; set; }
public IList<MediaTypeHeaderValue> ContentTypes { get; set; } public MediaTypeCollection ContentTypes { get; set; }
public override void OnResultExecuting(ResultExecutingContext context) public override void OnResultExecuting(ResultExecutingContext context)
{ {
@ -74,16 +84,17 @@ namespace Microsoft.AspNet.Mvc
} }
} }
private List<MediaTypeHeaderValue> GetContentTypes(string firstArg, string[] args) private MediaTypeCollection GetContentTypes(string firstArg, string[] args)
{ {
var completeArgs = new List<string>(); var completeArgs = new List<string>();
completeArgs.Add(firstArg); completeArgs.Add(firstArg);
completeArgs.AddRange(args); completeArgs.AddRange(args);
var contentTypes = new List<MediaTypeHeaderValue>(); var contentTypes = new MediaTypeCollection();
foreach (var arg in completeArgs) foreach (var arg in completeArgs)
{ {
var contentType = MediaTypeHeaderValue.Parse(arg); var contentType = arg;
if (contentType.MatchesAllSubTypes || contentType.MatchesAllTypes) if (MediaTypeComparisons.MatchesAllSubtypes(contentType)||
MediaTypeComparisons.MatchesAllTypes(contentType))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
Resources.FormatMatchAllContentTypeIsNotAllowed(arg)); Resources.FormatMatchAllContentTypeIsNotAllowed(arg));
@ -95,7 +106,7 @@ namespace Microsoft.AspNet.Mvc
return contentTypes; return contentTypes;
} }
public void SetContentTypes(IList<MediaTypeHeaderValue> contentTypes) public void SetContentTypes(MediaTypeCollection contentTypes)
{ {
contentTypes.Clear(); contentTypes.Clear();
foreach (var contentType in ContentTypes) foreach (var contentType in ContentTypes)

View File

@ -1034,6 +1034,22 @@ namespace Microsoft.AspNet.Mvc.Core
return GetString("HttpResponseStreamWriter_StreamNotWritable"); return GetString("HttpResponseStreamWriter_StreamNotWritable");
} }
/// <summary>
/// The argument '{0}' is invalid. Empty or null formats are not supported.
/// </summary>
internal static string FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat
{
get { return GetString("FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat"); }
}
/// <summary>
/// The argument '{0}' is invalid. Empty or null formats are not supported.
/// </summary>
internal static string FormatFormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat"), p0);
}
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -319,4 +319,7 @@
<data name="HttpResponseStreamWriter_StreamNotWritable" xml:space="preserve"> <data name="HttpResponseStreamWriter_StreamNotWritable" xml:space="preserve">
<value>The stream must support writing.</value> <value>The stream must support writing.</value>
</data> </data>
<data name="FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat" xml:space="preserve">
<value>The argument '{0}' is invalid. Empty or null formats are not supported.</value>
</data>
</root> </root>

View File

@ -37,10 +37,6 @@ namespace Microsoft.AspNet.Mvc
{ {
throw new ArgumentNullException(nameof(fileName)); throw new ArgumentNullException(nameof(fileName));
} }
if (contentType == null)
{
throw new ArgumentNullException(nameof(contentType));
}
} }
/// <summary> /// <summary>
@ -51,18 +47,13 @@ namespace Microsoft.AspNet.Mvc
/// <param name="fileName">The path to the file. The path must be relative/virtual.</param> /// <param name="fileName">The path to the file. The path must be relative/virtual.</param>
/// <param name="contentType">The Content-Type header of the response.</param> /// <param name="contentType">The Content-Type header of the response.</param>
public VirtualFileResult(string fileName, MediaTypeHeaderValue contentType) public VirtualFileResult(string fileName, MediaTypeHeaderValue contentType)
: base(contentType) : base(contentType?.ToString())
{ {
if (fileName == null) if (fileName == null)
{ {
throw new ArgumentNullException(nameof(fileName)); throw new ArgumentNullException(nameof(fileName));
} }
if (contentType == null)
{
throw new ArgumentNullException(nameof(contentType));
}
FileName = fileName; FileName = fileName;
} }

View File

@ -18,10 +18,10 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
/// </summary> /// </summary>
public class JsonResultExecutor public class JsonResultExecutor
{ {
private static readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("application/json") private static readonly string DefaultContentType = new MediaTypeHeaderValue("application/json")
{ {
Encoding = Encoding.UTF8 Encoding = Encoding.UTF8
}.CopyAsReadOnly(); }.ToString();
/// <summary> /// <summary>
/// Creates a new <see cref="JsonResultExecutor"/>. /// Creates a new <see cref="JsonResultExecutor"/>.

View File

@ -140,7 +140,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
} }
var response = context.HttpContext.Response; var response = context.HttpContext.Response;
var selectedEncoding = context.ContentType?.Encoding ?? Encoding.UTF8; var selectedEncoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8;
using (var writer = context.WriterFactory(response.Body, selectedEncoding)) using (var writer = context.WriterFactory(response.Body, selectedEncoding))
{ {

View File

@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc
/// <summary> /// <summary>
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response. /// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
/// </summary> /// </summary>
public MediaTypeHeaderValue ContentType { get; set; } public string ContentType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="JsonSerializerSettings"/>. /// Gets or sets the <see cref="JsonSerializerSettings"/>.

View File

@ -12,6 +12,7 @@ using System.Xml;
using Microsoft.AspNet.Mvc.Formatters.Xml; using Microsoft.AspNet.Mvc.Formatters.Xml;
using Microsoft.AspNet.Mvc.Formatters.Xml.Internal; using Microsoft.AspNet.Mvc.Formatters.Xml.Internal;
using Microsoft.AspNet.Mvc.Internal; using Microsoft.AspNet.Mvc.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters namespace Microsoft.AspNet.Mvc.Formatters
{ {
@ -187,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
} }
var writerSettings = WriterSettings.Clone(); var writerSettings = WriterSettings.Clone();
writerSettings.Encoding = context.ContentType?.Encoding ?? Encoding.UTF8; writerSettings.Encoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8;
// Wrap the object only if there is a wrapping type. // Wrap the object only if there is a wrapping type.
var value = context.Object; var value = context.Object;

View File

@ -12,6 +12,7 @@ using System.Xml.Serialization;
using Microsoft.AspNet.Mvc.Formatters.Xml; using Microsoft.AspNet.Mvc.Formatters.Xml;
using Microsoft.AspNet.Mvc.Formatters.Xml.Internal; using Microsoft.AspNet.Mvc.Formatters.Xml.Internal;
using Microsoft.AspNet.Mvc.Internal; using Microsoft.AspNet.Mvc.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Formatters namespace Microsoft.AspNet.Mvc.Formatters
{ {
@ -164,7 +165,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
var response = context.HttpContext.Response; var response = context.HttpContext.Response;
var writerSettings = WriterSettings.Clone(); var writerSettings = WriterSettings.Clone();
writerSettings.Encoding = context.ContentType.Encoding ?? Encoding.UTF8; writerSettings.Encoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8;
// Wrap the object only if there is a wrapping type. // Wrap the object only if there is a wrapping type.
var value = context.Object; var value = context.Object;

View File

@ -46,9 +46,9 @@ namespace Microsoft.AspNet.Mvc
public IViewEngine ViewEngine { get; set; } public IViewEngine ViewEngine { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response. /// Gets or sets the Content-Type header for the response.
/// </summary> /// </summary>
public MediaTypeHeaderValue ContentType { get; set; } public string ContentType { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public override async Task ExecuteResultAsync(ActionContext context) public override async Task ExecuteResultAsync(ActionContext context)

View File

@ -16,7 +16,6 @@ using Microsoft.AspNet.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -31,9 +30,9 @@ namespace Microsoft.AspNet.Mvc
public object Arguments { get; set; } public object Arguments { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response. /// Gets or sets the Content-Type header for the response.
/// </summary> /// </summary>
public MediaTypeHeaderValue ContentType { get; set; } public string ContentType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the HTTP status code. /// Gets or sets the HTTP status code.

View File

@ -23,12 +23,9 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
public class ViewExecutor public class ViewExecutor
{ {
/// <summary> /// <summary>
/// The default content-type header value for views, <c>text/html; charset=utf8</c>. /// The default content-type header value for views, <c>text/html; charset=utf-8</c>.
/// </summary> /// </summary>
public static readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("text/html") public static readonly string DefaultContentType = "text/html; charset=utf-8";
{
Encoding = Encoding.UTF8
}.CopyAsReadOnly();
/// <summary> /// <summary>
/// Creates a new <see cref="ViewExecutor"/>. /// Creates a new <see cref="ViewExecutor"/>.
@ -122,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
IView view, IView view,
ViewDataDictionary viewData, ViewDataDictionary viewData,
ITempDataDictionary tempData, ITempDataDictionary tempData,
MediaTypeHeaderValue contentType, string contentType,
int? statusCode) int? statusCode)
{ {
if (actionContext == null) if (actionContext == null)

View File

@ -51,9 +51,9 @@ namespace Microsoft.AspNet.Mvc
public IViewEngine ViewEngine { get; set; } public IViewEngine ViewEngine { get; set; }
/// <summary> /// <summary>
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response. /// Gets or sets the Content-Type header for the response.
/// </summary> /// </summary>
public MediaTypeHeaderValue ContentType { get; set; } public string ContentType { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public override async Task ExecuteResultAsync(ActionContext context) public override async Task ExecuteResultAsync(ActionContext context)

View File

@ -409,10 +409,7 @@ namespace System.Web.Http
} }
var result = new JsonResult(content, serializerSettings); var result = new JsonResult(content, serializerSettings);
result.ContentType = new MediaTypeHeaderValue("application/json") result.ContentType = $"application/json; charset={encoding.WebName}";
{
Encoding = encoding
};
return result; return result;
} }

View File

@ -18,6 +18,7 @@ using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Constraints; using Microsoft.AspNet.Routing.Constraints;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Moq; using Moq;
using Xunit; using Xunit;
@ -1385,14 +1386,14 @@ namespace Microsoft.AspNet.Mvc.Description
{ {
public ContentTypeAttribute(string mediaType) public ContentTypeAttribute(string mediaType)
{ {
ContentTypes.Add(MediaTypeHeaderValue.Parse(mediaType)); ContentTypes.Add(mediaType);
} }
public List<MediaTypeHeaderValue> ContentTypes { get; } = new List<MediaTypeHeaderValue>(); public MediaTypeCollection ContentTypes { get; } = new MediaTypeCollection();
public Type Type { get; set; } public Type Type { get; set; }
public void SetContentTypes(IList<MediaTypeHeaderValue> contentTypes) public void SetContentTypes(MediaTypeCollection contentTypes)
{ {
contentTypes.Clear(); contentTypes.Clear();
foreach (var contentType in ContentTypes) foreach (var contentType in ContentTypes)

View File

@ -9,6 +9,8 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features; 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.TestCommon;
using Microsoft.AspNet.Mvc.ViewComponents; using Microsoft.AspNet.Mvc.ViewComponents;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -32,7 +34,7 @@ namespace Microsoft.AspNet.Mvc
ContentType = new MediaTypeHeaderValue("text/plain") ContentType = new MediaTypeHeaderValue("text/plain")
{ {
Encoding = Encoding.UTF7 Encoding = Encoding.UTF7
} }.ToString()
}; };
var httpContext = GetHttpContext(); var httpContext = GetHttpContext();
var actionContext = GetActionContext(httpContext); var actionContext = GetActionContext(httpContext);
@ -41,7 +43,7 @@ namespace Microsoft.AspNet.Mvc
await contentResult.ExecuteResultAsync(actionContext); await contentResult.ExecuteResultAsync(actionContext);
// Assert // Assert
Assert.Equal("text/plain; charset=utf-7", httpContext.Response.ContentType); MediaTypeAssert.Equal("text/plain; charset=utf-7", httpContext.Response.ContentType);
} }
[Fact] [Fact]
@ -55,7 +57,7 @@ namespace Microsoft.AspNet.Mvc
ContentType = new MediaTypeHeaderValue("text/plain") ContentType = new MediaTypeHeaderValue("text/plain")
{ {
Encoding = Encoding.ASCII Encoding = Encoding.ASCII
} }.ToString()
}; };
var httpContext = GetHttpContext(); var httpContext = GetHttpContext();
httpContext.Features.Set<IHttpBufferingFeature>(new TestBufferingFeature()); httpContext.Features.Set<IHttpBufferingFeature>(new TestBufferingFeature());
@ -153,7 +155,7 @@ namespace Microsoft.AspNet.Mvc
var contentResult = new ContentResult var contentResult = new ContentResult
{ {
Content = content, Content = content,
ContentType = contentType ContentType = contentType?.ToString()
}; };
var httpContext = GetHttpContext(); var httpContext = GetHttpContext();
var memoryStream = new MemoryStream(); var memoryStream = new MemoryStream();
@ -165,7 +167,8 @@ namespace Microsoft.AspNet.Mvc
await contentResult.ExecuteResultAsync(actionContext); await contentResult.ExecuteResultAsync(actionContext);
// Assert // Assert
Assert.Equal(expectedContentType, httpContext.Response.ContentType); var finalResponseContentType = httpContext.Response.ContentType;
Assert.Equal(expectedContentType, finalResponseContentType);
Assert.Equal(expectedContentData, memoryStream.ToArray()); Assert.Equal(expectedContentData, memoryStream.ToArray());
} }

View File

@ -10,8 +10,10 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Testing; using Microsoft.AspNet.Testing;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@ -975,7 +977,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
// Assert // Assert
Assert.IsType<ContentResult>(actualContentResult); Assert.IsType<ContentResult>(actualContentResult);
Assert.Equal("TestContent", actualContentResult.Content); Assert.Equal("TestContent", actualContentResult.Content);
Assert.Null(actualContentResult.ContentType.Encoding); Assert.Null(MediaTypeEncoding.GetEncoding(actualContentResult.ContentType));
Assert.Equal("text/plain", actualContentResult.ContentType.ToString()); Assert.Equal("text/plain", actualContentResult.ContentType.ToString());
} }
@ -991,7 +993,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
// Assert // Assert
Assert.IsType<ContentResult>(actualContentResult); Assert.IsType<ContentResult>(actualContentResult);
Assert.Equal("TestContent", actualContentResult.Content); Assert.Equal("TestContent", actualContentResult.Content);
Assert.Same(Encoding.UTF8, actualContentResult.ContentType.Encoding); Assert.Same(Encoding.UTF8, MediaTypeEncoding.GetEncoding(actualContentResult.ContentType));
Assert.Equal("text/plain; charset=utf-8", actualContentResult.ContentType.ToString()); Assert.Equal("text/plain; charset=utf-8", actualContentResult.ContentType.ToString());
} }
@ -1024,7 +1026,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
Assert.NotNull(contentResult.ContentType); Assert.NotNull(contentResult.ContentType);
Assert.Equal(contentType, contentResult.ContentType.ToString()); Assert.Equal(contentType, contentResult.ContentType.ToString());
// The default encoding of ContentResult is used when this result is executed. // The default encoding of ContentResult is used when this result is executed.
Assert.Null(contentResult.ContentType.Encoding); Assert.Null(MediaTypeEncoding.GetEncoding(contentResult.ContentType));
} }
[Fact] [Fact]
@ -1032,13 +1034,13 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ {
// Arrange // Arrange
var contentController = new ContentController(); var contentController = new ContentController();
var contentType = MediaTypeHeaderValue.Parse("text/xml; charset=us-ascii; p1=p1-value"); var contentType = "text/xml; charset=us-ascii; p1=p1-value";
// Act // Act
var contentResult = (ContentResult)contentController.Content_WithEncodingInCharset_AndEncodingParameter(); var contentResult = (ContentResult)contentController.Content_WithEncodingInCharset_AndEncodingParameter();
// Assert // Assert
Assert.Equal(contentType, contentResult.ContentType); MediaTypeAssert.Equal(contentType, contentResult.ContentType);
} }
[Fact] [Fact]
@ -1046,7 +1048,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ {
// Arrange // Arrange
var contentController = new ContentController(); var contentController = new ContentController();
var contentType = MediaTypeHeaderValue.Parse("text/xml; charset=us-ascii; p1=p1-value"); var contentType = "text/xml; charset=us-ascii; p1=p1-value";
// Act // Act
var contentResult = (ContentResult)contentController.Content_WithEncodingInCharset(); var contentResult = (ContentResult)contentController.Content_WithEncodingInCharset();

View File

@ -7,11 +7,11 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features; 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.TestCommon;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Net.Http.Headers;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
@ -37,14 +37,14 @@ namespace Microsoft.AspNet.Mvc
// Arrange // Arrange
var fileContents = new byte[0]; var fileContents = new byte[0];
var contentType = "text/plain; charset=us-ascii; p1=p1-value"; var contentType = "text/plain; charset=us-ascii; p1=p1-value";
var expectedMediaType = MediaTypeHeaderValue.Parse(contentType); var expectedMediaType = contentType;
// Act // Act
var result = new FileContentResult(fileContents, contentType); var result = new FileContentResult(fileContents, contentType);
// Assert // Assert
Assert.Same(fileContents, result.FileContents); Assert.Same(fileContents, result.FileContents);
Assert.Equal(expectedMediaType, result.ContentType); MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
} }
[Fact] [Fact]
@ -83,7 +83,7 @@ namespace Microsoft.AspNet.Mvc
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var result = new FileContentResult(buffer, MediaTypeHeaderValue.Parse(expectedContentType)); var result = new FileContentResult(buffer, expectedContentType);
// Act // Act
await result.ExecuteResultAsync(context); await result.ExecuteResultAsync(context);
@ -108,7 +108,7 @@ namespace Microsoft.AspNet.Mvc
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var result = new FileContentResult(buffer, MediaTypeHeaderValue.Parse(expectedContentType)); var result = new FileContentResult(buffer, expectedContentType);
// Act // Act
await result.ExecuteResultAsync(context); await result.ExecuteResultAsync(context);

View File

@ -274,12 +274,12 @@ namespace Microsoft.AspNet.Mvc
public bool WasWriteFileCalled; public bool WasWriteFileCalled;
public EmptyFileResult() public EmptyFileResult()
: base(MediaTypeHeaderValue.Parse("application/octet")) : base("application/octet")
{ {
} }
public EmptyFileResult(string contentType) public EmptyFileResult(string contentType)
: base(MediaTypeHeaderValue.Parse(contentType)) : base(contentType)
{ {
} }

View File

@ -10,11 +10,11 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features; 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.TestCommon;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Net.Http.Headers;
using Moq; using Moq;
using Xunit; using Xunit;
@ -41,14 +41,14 @@ namespace Microsoft.AspNet.Mvc
// Arrange // Arrange
var stream = Stream.Null; var stream = Stream.Null;
var contentType = "text/plain; charset=us-ascii; p1=p1-value"; var contentType = "text/plain; charset=us-ascii; p1=p1-value";
var expectedMediaType = MediaTypeHeaderValue.Parse(contentType); var expectedMediaType = contentType;
// Act // Act
var result = new FileStreamResult(stream, contentType); var result = new FileStreamResult(stream, contentType);
// Assert // Assert
Assert.Equal(stream, result.FileStream); Assert.Equal(stream, result.FileStream);
Assert.Equal(expectedMediaType, result.ContentType); MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
} }
[Fact] [Fact]
@ -130,7 +130,7 @@ namespace Microsoft.AspNet.Mvc
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var result = new FileStreamResult(originalStream, MediaTypeHeaderValue.Parse(expectedContentType)); var result = new FileStreamResult(originalStream, expectedContentType);
// Act // Act
await result.ExecuteResultAsync(actionContext); await result.ExecuteResultAsync(actionContext);
@ -157,7 +157,7 @@ namespace Microsoft.AspNet.Mvc
httpContext.Response.Body = outStream; httpContext.Response.Body = outStream;
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var result = new FileStreamResult(originalStream, MediaTypeHeaderValue.Parse(expectedContentType)); var result = new FileStreamResult(originalStream, expectedContentType);
// Act // Act
await result.ExecuteResultAsync(actionContext); await result.ExecuteResultAsync(actionContext);

View File

@ -4,9 +4,10 @@
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Moq; using Moq;
using Xunit; using Xunit;
@ -32,7 +33,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
string contentType) string contentType)
{ {
// Arrange // Arrange
var mediaType = MediaTypeHeaderValue.Parse("application/json"); var mediaType = new StringSegment("application/json");
var mockObjects = new MockObjects(format, place); var mockObjects = new MockObjects(format, place);
var resultExecutingContext = mockObjects.CreateResultExecutingContext(); var resultExecutingContext = mockObjects.CreateResultExecutingContext();
@ -52,7 +53,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// Assert // Assert
var objectResult = Assert.IsType<ObjectResult>(resultExecutingContext.Result); var objectResult = Assert.IsType<ObjectResult>(resultExecutingContext.Result);
Assert.Equal(1, objectResult.ContentTypes.Count); Assert.Equal(1, objectResult.ContentTypes.Count);
AssertMediaTypesEqual(mediaType, objectResult.ContentTypes[0]); MediaTypeAssert.Equal(mediaType, objectResult.ContentTypes[0]);
} }
[Fact] [Fact]
@ -61,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// If the format is present in both route and query data, the one in route data wins // If the format is present in both route and query data, the one in route data wins
// Arrange // Arrange
var mediaType = MediaTypeHeaderValue.Parse("application/json"); var mediaType = new StringSegment("application/json");
var mockObjects = new MockObjects("json", FormatSource.RouteData); var mockObjects = new MockObjects("json", FormatSource.RouteData);
var httpContext = new Mock<HttpContext>(); var httpContext = new Mock<HttpContext>();
httpContext.Setup(c => c.Response).Returns(new Mock<HttpResponse>().Object); httpContext.Setup(c => c.Response).Returns(new Mock<HttpResponse>().Object);
@ -95,7 +96,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// Assert // Assert
var objectResult = Assert.IsType<ObjectResult>(resultExecutingContext.Result); var objectResult = Assert.IsType<ObjectResult>(resultExecutingContext.Result);
Assert.Equal(1, objectResult.ContentTypes.Count); Assert.Equal(1, objectResult.ContentTypes.Count);
AssertMediaTypesEqual(mediaType, objectResult.ContentTypes[0]); MediaTypeAssert.Equal(mediaType, objectResult.ContentTypes[0]);
} }
[Theory] [Theory]
@ -108,7 +109,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
string contentType) string contentType)
{ {
// Arrange // Arrange
var mediaType = MediaTypeHeaderValue.Parse(contentType); var mediaType = new StringSegment(contentType);
var mockObjects = new MockObjects(format, place); var mockObjects = new MockObjects(format, place);
var resultExecutingContext = mockObjects.CreateResultExecutingContext(); var resultExecutingContext = mockObjects.CreateResultExecutingContext();
@ -127,7 +128,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// Assert // Assert
var objectResult = Assert.IsType<ObjectResult>(resultExecutingContext.Result); var objectResult = Assert.IsType<ObjectResult>(resultExecutingContext.Result);
Assert.Equal(1, objectResult.ContentTypes.Count); Assert.Equal(1, objectResult.ContentTypes.Count);
AssertMediaTypesEqual(mediaType, objectResult.ContentTypes[0]); MediaTypeAssert.Equal(mediaType, objectResult.ContentTypes[0]);
} }
[Theory] [Theory]
@ -303,7 +304,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
public void FormatFilter_ExplicitContentType_SetOnObjectResult_TakesPrecedence() public void FormatFilter_ExplicitContentType_SetOnObjectResult_TakesPrecedence()
{ {
// Arrange // Arrange
var mediaType = MediaTypeHeaderValue.Parse("application/foo"); var mediaType = new StringSegment("application/foo");
var mockObjects = new MockObjects("json", FormatSource.QueryData); var mockObjects = new MockObjects("json", FormatSource.QueryData);
var httpContext = new Mock<HttpContext>(); var httpContext = new Mock<HttpContext>();
httpContext.Setup(c => c.Response).Returns(new Mock<HttpResponse>().Object); httpContext.Setup(c => c.Response).Returns(new Mock<HttpResponse>().Object);
@ -330,7 +331,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// Assert // Assert
var result = Assert.IsType<ObjectResult>(resultExecutingContext.Result); var result = Assert.IsType<ObjectResult>(resultExecutingContext.Result);
Assert.Equal(1, result.ContentTypes.Count); Assert.Equal(1, result.ContentTypes.Count);
AssertMediaTypesEqual(mediaType, result.ContentTypes[0]); MediaTypeAssert.Equal(mediaType, result.ContentTypes[0]);
} }
[Fact] [Fact]
@ -366,21 +367,6 @@ namespace Microsoft.AspNet.Mvc.Formatters
Assert.Equal(0, result.ContentTypes.Count); Assert.Equal(0, result.ContentTypes.Count);
} }
private static void AssertMediaTypesEqual(
MediaTypeHeaderValue expectedMediaType,
MediaTypeHeaderValue actualMediaType)
{
Assert.Equal(expectedMediaType.MediaType, actualMediaType.MediaType);
Assert.Equal(expectedMediaType.SubType, actualMediaType.SubType);
Assert.Equal(expectedMediaType.Charset, actualMediaType.Charset);
Assert.Equal(expectedMediaType.MatchesAllTypes, actualMediaType.MatchesAllTypes);
Assert.Equal(expectedMediaType.MatchesAllSubTypes, actualMediaType.MatchesAllSubTypes);
Assert.Equal(expectedMediaType.Parameters.Count, actualMediaType.Parameters.Count);
foreach (var item in expectedMediaType.Parameters)
{
Assert.Equal(item.Value, NameValueHeaderValue.Find(actualMediaType.Parameters, item.Name).Value);
}
}
private class MockObjects private class MockObjects
{ {

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Xunit; using Xunit;
@ -10,33 +12,44 @@ namespace Microsoft.AspNet.Mvc.Formatters
{ {
public class FormatterMappingsTest public class FormatterMappingsTest
{ {
[Theory]
[InlineData(null)]
[InlineData("")]
public void FormatterMappings_GetMediaTypeMappingForFormat_ThrowsForInvalidFormats(string format)
{
// Arrange
var options = new FormatterMappings();
// Act & Assert
Assert.Throws<ArgumentException>("format", () => options.GetMediaTypeMappingForFormat(format));
}
[Theory] [Theory]
[InlineData(".xml", "application/xml", "xml")] [InlineData(".xml", "application/xml", "xml")]
[InlineData("json", "application/json", "JSON")] [InlineData("json", "application/json", "JSON")]
[InlineData(".foo", "text/foo", "Foo")] [InlineData(".foo", "text/foo", "Foo")]
[InlineData(".Json", "application/json", "json")] [InlineData(".Json", "application/json", "json")]
[InlineData("FOo", "text/foo", "FOO")] [InlineData("FOo", "text/foo", "FOO")]
public void FormatterMappings_SetFormatMapping_DiffSetGetFormat(string setFormat, string contentType, string getFormat) public void FormatterMappings_SetFormatMapping_DiffSetGetFormat(string setFormat, string contentType, string getFormat)
{ {
// Arrange // Arrange
var mediaType = MediaTypeHeaderValue.Parse(contentType);
var options = new FormatterMappings(); var options = new FormatterMappings();
options.SetMediaTypeMappingForFormat(setFormat, mediaType); options.SetMediaTypeMappingForFormat(setFormat, MediaTypeHeaderValue.Parse(contentType));
// Act // Act
var returnMediaType = options.GetMediaTypeMappingForFormat(getFormat); var returnMediaType = options.GetMediaTypeMappingForFormat(getFormat);
// Assert // Assert
Assert.Equal(mediaType, returnMediaType); MediaTypeAssert.Equal(contentType, returnMediaType);
} }
[Fact] [Fact]
public void FormatterMappings_Invalid_Period() public void FormatterMappings_Invalid_Period()
{ {
// Arrange // Arrange
var options = new FormatterMappings(); var options = new FormatterMappings();
var format = "."; var format = ".";
var expected = string.Format(@"The format provided is invalid '{0}'. A format must be a non-empty file-" + var expected = string.Format(@"The format provided is invalid '{0}'. A format must be a non-empty file-" +
"extension, optionally prefixed with a '.' character.", format); "extension, optionally prefixed with a '.' character.", format);
// Act and assert // Act and assert
@ -70,12 +83,12 @@ namespace Microsoft.AspNet.Mvc.Formatters
{ {
// Arrange // Arrange
var options = new FormatterMappings(); var options = new FormatterMappings();
var expected = string.Format(@"The media type ""{0}"" is not valid. MediaTypes containing wildcards (*) " + var expected = string.Format(@"The media type ""{0}"" is not valid. MediaTypes containing wildcards (*) " +
"are not allowed in formatter mappings.", format); "are not allowed in formatter mappings.", format);
// Act and assert // Act and assert
var exception = Assert.Throws<ArgumentException>(() => options.SetMediaTypeMappingForFormat( var exception = Assert.Throws<ArgumentException>(() => options.SetMediaTypeMappingForFormat(
"star", "star",
MediaTypeHeaderValue.Parse(format))); MediaTypeHeaderValue.Parse(format)));
Assert.Equal(expected, exception.Message); Assert.Equal(expected, exception.Message);
} }

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
using Xunit;
namespace Microsoft.AspNet.Mvc.Formatters
{
public class MediaTypeComparisonsTest
{
[Theory]
[InlineData("application/json", "application/json", true)]
[InlineData("application/json", "application/json;charset=utf-8", true)]
[InlineData("application/json;charset=utf-8", "application/json", false)]
[InlineData("application/json;q=0.8", "application/json;q=0.9", true)]
[InlineData("application/json;q=0.8;charset=utf-7", "application/json;charset=utf-8;q=0.9", true)]
[InlineData("application/json;format=indent;charset=utf-8", "application/json", false)]
[InlineData("application/json", "application/json;format=indent;charset=utf-8", true)]
[InlineData("application/json;format=indent;charset=utf-8", "application/json;format=indent;charset=utf-8", true)]
[InlineData("application/json;charset=utf-8;format=indent", "application/json;format=indent;charset=utf-8", true)]
public void IsSubsetOf(string set, string subset, bool expectedResult)
{
// Arrange & Act
var result = MediaTypeComparisons.IsSubsetOf(
new StringSegment(set),
new StringSegment(subset));
// Assert
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("*/*", true)]
[InlineData("text/*", false)]
[InlineData("text/plain", false)]
public void MatchesAllTypes(string value, bool expectedResult)
{
// Arrange
var mediaType = new StringSegment(value);
// Act
var result = MediaTypeComparisons.MatchesAllTypes(mediaType);
// Assert
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("*/*", true)]
[InlineData("text/*", true)]
[InlineData("text/plain", false)]
public void MatchesAllSubtypes(string value, bool expectedResult)
{
// Arrange
var mediaType = new StringSegment(value);
// Act
var result = MediaTypeComparisons.MatchesAllSubtypes(mediaType);
// Assert
Assert.Equal(expectedResult, result);
}
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Xunit; using Xunit;
@ -41,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
{ {
// Arrange // Arrange
var type = declaredTypeAsString ? typeof(string) : typeof(object); var type = declaredTypeAsString ? typeof(string) : typeof(object);
var contentType = useNonNullContentType ? MediaTypeHeaderValue.Parse("text/plain") : null; var contentType = useNonNullContentType ? new StringSegment("text/plain") : new StringSegment();
var context = new OutputFormatterWriteContext( var context = new OutputFormatterWriteContext(
new DefaultHttpContext(), new DefaultHttpContext(),
@ -73,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
declaredType, declaredType,
"Something non null.") "Something non null.")
{ {
ContentType = MediaTypeHeaderValue.Parse("text/plain"), ContentType = new StringSegment("text/plain"),
}; };
var formatter = new HttpNoContentOutputFormatter(); var formatter = new HttpNoContentOutputFormatter();
@ -101,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
typeof(string), typeof(string),
value) value)
{ {
ContentType = MediaTypeHeaderValue.Parse("text/plain"), ContentType = new StringSegment("text/plain"),
}; };
var formatter = new HttpNoContentOutputFormatter() var formatter = new HttpNoContentOutputFormatter()

View File

@ -7,8 +7,10 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Moq; using Moq;
using Moq.Protected;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.Formatters namespace Microsoft.AspNet.Mvc.Formatters
@ -59,7 +61,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
typeof(string), typeof(string),
"someValue") "someValue")
{ {
ContentType = MediaTypeHeaderValue.Parse(httpRequest.Headers[HeaderNames.Accept]), ContentType = new StringSegment(httpRequest.Headers[HeaderNames.Accept]),
}; };
// Act // Act
@ -69,17 +71,47 @@ namespace Microsoft.AspNet.Mvc.Formatters
Assert.Equal(Encoding.GetEncoding(expectedEncoding), actualEncoding); Assert.Equal(Encoding.GetEncoding(expectedEncoding), actualEncoding);
} }
[Theory]
[InlineData("application/json; charset=utf-16", "application/json; charset=utf-32")]
[InlineData("application/json; charset=utf-16; format=indent", "application/json; charset=utf-32; format=indent")]
public void WriteResponse_OverridesCharset_IfDifferentFromContentTypeCharset(
string contentType,
string expectedContentType)
{
// Arrange
var formatter = new Mock<OutputFormatter>();
formatter
.Setup(f => f.SelectCharacterEncoding(It.IsAny<OutputFormatterWriteContext>()))
.Returns(Encoding.UTF32);
var formatterContext = new OutputFormatterWriteContext(
new DefaultHttpContext(),
new TestHttpResponseStreamWriterFactory().CreateWriter,
objectType: null,
@object: null)
{
ContentType = new StringSegment(contentType),
};
// Act
formatter.Object.WriteAsync(formatterContext);
// Assert
Assert.Equal(new StringSegment(expectedContentType), formatterContext.ContentType);
}
[Fact] [Fact]
public void WriteResponseContentHeaders_NoSupportedEncodings_NoEncodingIsSet() public void WriteResponseContentHeaders_NoSupportedEncodings_NoEncodingIsSet()
{ {
// Arrange // Arrange
var formatter = new TestOutputFormatter(); var formatter = new TestOutputFormatter();
var testContentType = MediaTypeHeaderValue.Parse("text/json"); var testContentType = new StringSegment("text/json");
formatter.SupportedEncodings.Clear(); formatter.SupportedEncodings.Clear();
formatter.SupportedMediaTypes.Clear(); formatter.SupportedMediaTypes.Clear();
formatter.SupportedMediaTypes.Add(testContentType); formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
var context = new OutputFormatterWriteContext( var context = new OutputFormatterWriteContext(
new DefaultHttpContext(), new DefaultHttpContext(),
@ -94,11 +126,11 @@ namespace Microsoft.AspNet.Mvc.Formatters
formatter.WriteResponseHeaders(context); formatter.WriteResponseHeaders(context);
// Assert // Assert
Assert.Null(context.ContentType.Encoding); Assert.Null(MediaTypeHeaderValue.Parse(context.ContentType.Value).Encoding);
Assert.Equal(testContentType, context.ContentType); Assert.Equal(testContentType, context.ContentType);
// If we had set an encoding, it would be part of the content type header // If we had set an encoding, it would be part of the content type header
Assert.Equal(testContentType, context.HttpContext.Response.GetTypedHeaders().ContentType); Assert.Equal(MediaTypeHeaderValue.Parse(testContentType.Value), context.HttpContext.Response.GetTypedHeaders().ContentType);
} }
[Fact] [Fact]
@ -154,7 +186,9 @@ namespace Microsoft.AspNet.Mvc.Formatters
formatter.SupportedTypes.Add(typeof(int)); formatter.SupportedTypes.Add(typeof(int));
// Act // Act
var contentTypes = formatter.GetSupportedContentTypes(contentType: null, objectType: typeof(string)); var contentTypes = formatter.GetSupportedContentTypes(
contentType: null,
objectType: typeof(string));
// Assert // Assert
Assert.Null(contentTypes); Assert.Null(contentTypes);
@ -174,7 +208,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
typeof(string), typeof(string),
"Hello, world!") "Hello, world!")
{ {
ContentType = formatter.SupportedMediaTypes[0], ContentType = new StringSegment(formatter.SupportedMediaTypes[0].ToString()),
}; };
// Act // Act
@ -194,7 +228,9 @@ namespace Microsoft.AspNet.Mvc.Formatters
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml")); formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml"));
// Act // Act
var contentTypes = formatter.GetSupportedContentTypes(contentType: null, objectType: typeof(string)); var contentTypes = formatter.GetSupportedContentTypes(
contentType: null,
objectType: typeof(string));
// Assert // Assert
Assert.Equal(2, contentTypes.Count); Assert.Equal(2, contentTypes.Count);
@ -214,7 +250,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// Act // Act
var contentTypes = formatter.GetSupportedContentTypes( var contentTypes = formatter.GetSupportedContentTypes(
MediaTypeHeaderValue.Parse("application/*"), "application/*",
typeof(int)); typeof(int));
// Assert // Assert
@ -234,7 +270,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
// Act // Act
var contentTypes = formatter.GetSupportedContentTypes( var contentTypes = formatter.GetSupportedContentTypes(
MediaTypeHeaderValue.Parse("application/xml"), "application/xml",
typeof(int)); typeof(int));
// Assert // Assert
@ -251,7 +287,9 @@ namespace Microsoft.AspNet.Mvc.Formatters
formatter.SupportedMediaTypes.Clear(); formatter.SupportedMediaTypes.Clear();
// Act // Act
var contentTypes = formatter.GetSupportedContentTypes(contentType: null, objectType: typeof(int)); var contentTypes = formatter.GetSupportedContentTypes(
contentType: null,
objectType: typeof(int));
// Assert // Assert
Assert.Null(contentTypes); Assert.Null(contentTypes);

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Xunit; using Xunit;
@ -21,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
{ {
// Arrange // Arrange
var formatter = new StreamOutputFormatter(); var formatter = new StreamOutputFormatter();
var contentTypeHeader = contentType == null ? null : new MediaTypeHeaderValue(contentType); var contentTypeHeader = new StringSegment(contentType);
var context = new OutputFormatterWriteContext( var context = new OutputFormatterWriteContext(
new DefaultHttpContext(), new DefaultHttpContext(),
@ -46,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
{ {
// Arrange // Arrange
var formatter = new StreamOutputFormatter(); var formatter = new StreamOutputFormatter();
var contentTypeHeader = contentType == null ? null : new MediaTypeHeaderValue(contentType); var contentTypeHeader = contentType == null ? new StringSegment() : new StringSegment(contentType);
var context = new OutputFormatterWriteContext( var context = new OutputFormatterWriteContext(
new DefaultHttpContext(), new DefaultHttpContext(),

View File

@ -7,7 +7,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.Net.Http.Headers; using Microsoft.Extensions.Primitives;
using Moq; using Moq;
using Xunit; using Xunit;
@ -35,9 +35,9 @@ namespace Microsoft.AspNet.Mvc.Formatters
bool expectedCanWriteResult) bool expectedCanWriteResult)
{ {
// Arrange // Arrange
var expectedContentType = expectedCanWriteResult ? var expectedContentType = expectedCanWriteResult ?
MediaTypeHeaderValue.Parse("text/plain") : new StringSegment("text/plain") :
MediaTypeHeaderValue.Parse("application/json"); new StringSegment("application/json");
var formatter = new StringOutputFormatter(); var formatter = new StringOutputFormatter();
var type = useDeclaredTypeAsString ? typeof(string) : typeof(object); var type = useDeclaredTypeAsString ? typeof(string) : typeof(object);
@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
new TestHttpResponseStreamWriterFactory().CreateWriter, new TestHttpResponseStreamWriterFactory().CreateWriter,
type, type,
value); value);
context.ContentType = MediaTypeHeaderValue.Parse("application/json"); context.ContentType = new StringSegment("application/json");
// Act // Act
var result = formatter.CanWriteResult(context); var result = formatter.CanWriteResult(context);

View File

@ -9,10 +9,12 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Xunit; using Xunit;
@ -43,12 +45,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
// Act // Act
var formatter = executor.SelectFormatter( var formatter = executor.SelectFormatter(
context, context,
new[] { new MediaTypeHeaderValue("application/json") }, new MediaTypeCollection { "application/json" },
formatters); formatters);
// Assert // Assert
Assert.Same(formatters[1], formatter); Assert.Same(formatters[1], formatter);
Assert.Equal(new MediaTypeHeaderValue("application/json"), context.ContentType); MediaTypeAssert.Equal("application/json", context.ContentType);
} }
// For this test case probably the most common use case is when there is a format mapping based // For this test case probably the most common use case is when there is a format mapping based
@ -72,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
// Assert // Assert
Assert.IsType<TestStringOutputFormatter>(executor.SelectedOutputFormatter); Assert.IsType<TestStringOutputFormatter>(executor.SelectedOutputFormatter);
Assert.Equal("text/plain; charset=utf-8", httpContext.Response.ContentType); MediaTypeAssert.Equal("text/plain; charset=utf-8", httpContext.Response.ContentType);
} }
[Fact] [Fact]
@ -98,12 +100,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
// Act // Act
var formatter = executor.SelectFormatter( var formatter = executor.SelectFormatter(
context, context,
new[] { new MediaTypeHeaderValue("application/json") }, new MediaTypeCollection { "application/json" },
formatters); formatters);
// Assert // Assert
Assert.Same(formatters[1], formatter); Assert.Same(formatters[1], formatter);
Assert.Equal(new MediaTypeHeaderValue("application/json"), context.ContentType); Assert.Equal(new StringSegment("application/json"), context.ContentType);
} }
[Fact] [Fact]
@ -149,7 +151,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
// Act // Act
var formatter = executor.SelectFormatter( var formatter = executor.SelectFormatter(
context, context,
new[] { new MediaTypeHeaderValue("application/json") }, new MediaTypeCollection { "application/json" },
formatters); formatters);
// Assert // Assert
@ -176,18 +178,18 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
} }
// ObjectResult.ContentTypes, Accept header, expected content type // ObjectResult.ContentTypes, Accept header, expected content type
public static TheoryData<string[], string, string> ContentTypes public static TheoryData<MediaTypeCollection, string, string> ContentTypes
{ {
get get
{ {
var contentTypes = new string[] var contentTypes = new MediaTypeCollection
{ {
"text/plain", "text/plain",
"text/xml", "text/xml",
"application/json", "application/json",
}; };
return new TheoryData<string[], string, string>() return new TheoryData<MediaTypeCollection, string, string>()
{ {
// Empty accept header, should select based on ObjectResult.ContentTypes. // Empty accept header, should select based on ObjectResult.ContentTypes.
{ contentTypes, "", "application/json" }, { contentTypes, "", "application/json" },
@ -216,7 +218,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
[Theory] [Theory]
[MemberData(nameof(ContentTypes))] [MemberData(nameof(ContentTypes))]
public void SelectFormatter_WithMultipleProvidedContentTypes_DoesConneg( public void SelectFormatter_WithMultipleProvidedContentTypes_DoesConneg(
IEnumerable<string> contentTypes, MediaTypeCollection contentTypes,
string acceptHeader, string acceptHeader,
string expectedContentType) string expectedContentType)
{ {
@ -240,12 +242,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
// Act // Act
var formatter = executor.SelectFormatter( var formatter = executor.SelectFormatter(
context, context,
contentTypes.Select(contentType => MediaTypeHeaderValue.Parse(contentType)).ToList(), contentTypes,
formatters); formatters);
// Assert // Assert
Assert.Same(formatters[1], formatter); Assert.Same(formatters[1], formatter);
Assert.Equal(new MediaTypeHeaderValue(expectedContentType), context.ContentType); Assert.Equal(new StringSegment(expectedContentType), context.ContentType);
} }
[Fact] [Fact]
@ -270,12 +272,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
// Act // Act
var formatter = executor.SelectFormatter( var formatter = executor.SelectFormatter(
context, context,
new List<MediaTypeHeaderValue>(), new MediaTypeCollection(),
formatters); formatters);
// Assert // Assert
Assert.Same(formatters[1], formatter); Assert.Same(formatters[1], formatter);
Assert.Equal(new MediaTypeHeaderValue("application/json"), context.ContentType); Assert.Equal(new StringSegment("application/json"), context.ContentType);
Assert.Null(context.FailedContentNegotiation); Assert.Null(context.FailedContentNegotiation);
} }
@ -297,17 +299,17 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
objectType: null, objectType: null,
@object: null); @object: null);
context.HttpContext.Request.Headers[HeaderNames.Accept] = "text/custom, application/custom"; context.HttpContext.Request.Headers[HeaderNames.Accept] = "text/custom,application/custom";
// Act // Act
var formatter = executor.SelectFormatter( var formatter = executor.SelectFormatter(
context, context,
new MediaTypeHeaderValue[] { }, new MediaTypeCollection { },
formatters); formatters);
// Assert // Assert
Assert.Same(formatters[0], formatter); Assert.Same(formatters[0], formatter);
Assert.Equal(new MediaTypeHeaderValue("application/xml"), context.ContentType); Assert.Equal(new StringSegment("application/xml"), context.ContentType);
Assert.True(context.FailedContentNegotiation); Assert.True(context.FailedContentNegotiation);
} }
@ -373,9 +375,14 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
{ {
// Arrange // Arrange
var result = new ObjectResult("input"); var result = new ObjectResult("input");
result.ContentTypes = contentTypes
.Select(contentType => MediaTypeHeaderValue.Parse(contentType)) var mediaTypes = new MediaTypeCollection();
.ToList(); foreach (var contentType in contentTypes)
{
mediaTypes.Add(contentType);
}
result.ContentTypes = mediaTypes;
var executor = CreateExecutor(); var executor = CreateExecutor();
@ -395,7 +402,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
// Chrome & Opera // Chrome & Opera
[InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "application/json; charset=utf-8")] [InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "application/json; charset=utf-8")]
// IE // IE
[InlineData("text/html, application/xhtml+xml, */*", "application/json; charset=utf-8")] [InlineData("text/html,application/xhtml+xml,*/*", "application/json; charset=utf-8")]
// Firefox & Safari // Firefox & Safari
[InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "application/json; charset=utf-8")] [InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "application/json; charset=utf-8")]
// Misc // Misc
@ -432,7 +439,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
// Chrome & Opera // Chrome & Opera
[InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "application/xml; charset=utf-8")] [InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "application/xml; charset=utf-8")]
// IE // IE
[InlineData("text/html, application/xhtml+xml, */*", "application/json; charset=utf-8")] [InlineData("text/html,application/xhtml+xml,*/*", "application/json; charset=utf-8")]
// Firefox & Safari // Firefox & Safari
[InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "application/xml; charset=utf-8")] [InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "application/xml; charset=utf-8")]
// Misc // Misc
@ -462,7 +469,8 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
await executor.ExecuteAsync(actionContext, result); await executor.ExecuteAsync(actionContext, result);
// Assert // Assert
Assert.Equal(expectedContentType, actionContext.HttpContext.Response.Headers[HeaderNames.ContentType]); var responseContentType = actionContext.HttpContext.Response.Headers[HeaderNames.ContentType];
MediaTypeAssert.Equal(expectedContentType, responseContentType);
} }
private static IServiceCollection CreateServices() private static IServiceCollection CreateServices()
@ -572,7 +580,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
new public IOutputFormatter SelectFormatter( new public IOutputFormatter SelectFormatter(
OutputFormatterWriteContext formatterContext, OutputFormatterWriteContext formatterContext,
IList<MediaTypeHeaderValue> contentTypes, MediaTypeCollection contentTypes,
IList<IOutputFormatter> formatters) IList<IOutputFormatter> formatters)
{ {
return base.SelectFormatter(formatterContext, contentTypes, formatters); return base.SelectFormatter(formatterContext, contentTypes, formatters);
@ -593,7 +601,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
protected override IOutputFormatter SelectFormatter( protected override IOutputFormatter SelectFormatter(
OutputFormatterWriteContext formatterContext, OutputFormatterWriteContext formatterContext,
IList<MediaTypeHeaderValue> contentTypes, MediaTypeCollection contentTypes,
IList<IOutputFormatter> formatters) IList<IOutputFormatter> formatters)
{ {
SelectedOutputFormatter = base.SelectFormatter(formatterContext, contentTypes, formatters); SelectedOutputFormatter = base.SelectFormatter(formatterContext, contentTypes, formatters);

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text; using System.Text;
using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Xunit; using Xunit;
@ -102,13 +104,13 @@ namespace Microsoft.AspNet.Mvc.Internal
string expectedContentType) string expectedContentType)
{ {
// Arrange // Arrange
var defaultContentType = MediaTypeHeaderValue.Parse("text/default; p1=p1-value; charset=utf-8"); var defaultContentType = "text/default; p1=p1-value; charset=utf-8";
// Act // Act
string resolvedContentType = null; string resolvedContentType = null;
Encoding resolvedContentTypeEncoding = null; Encoding resolvedContentTypeEncoding = null;
ResponseContentTypeHelper.ResolveContentTypeAndEncoding( ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
contentType, contentType?.ToString(),
responseContentType, responseContentType,
defaultContentType, defaultContentType,
out resolvedContentType, out resolvedContentType,
@ -123,7 +125,7 @@ namespace Microsoft.AspNet.Mvc.Internal
{ {
// Arrange // Arrange
var expectedContentType = "invalid-content-type"; var expectedContentType = "invalid-content-type";
var defaultContentType = MediaTypeHeaderValue.Parse("text/plain; charset=utf-8"); var defaultContentType = "text/plain; charset=utf-8";
// Act // Act
string resolvedContentType = null; string resolvedContentType = null;
@ -136,6 +138,7 @@ namespace Microsoft.AspNet.Mvc.Internal
out resolvedContentTypeEncoding); out resolvedContentTypeEncoding);
// Assert // Assert
Assert.Equal(expectedContentType, resolvedContentType); Assert.Equal(expectedContentType, resolvedContentType);
Assert.Equal(Encoding.UTF8, resolvedContentTypeEncoding); Assert.Equal(Encoding.UTF8, resolvedContentTypeEncoding);
} }

View File

@ -0,0 +1,61 @@
// 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 Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.AspNet.Mvc.Formatters
{
public class MediaTypeCollectionTest
{
[Fact]
public void Add_MediaTypeHeaderValue_AddsTheStringSegmentRepresentationOfTheMediaType()
{
// Arrange
var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse("application/json;charset=utf-16");
var collection = new MediaTypeCollection();
// Act
collection.Add(mediaTypeHeaderValue);
// Assert
Assert.Contains("application/json; charset=utf-16", collection);
}
[Fact]
public void Insert_MediaTypeHeaderValue_AddsTheStringSegmentRepresentationOfTheMediaTypeOnTheGivenIndex()
{
// Arrange
var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse("application/json;charset=utf-16");
var collection = new MediaTypeCollection
{
MediaTypeHeaderValue.Parse("text/plain"),
MediaTypeHeaderValue.Parse("text/xml")
};
// Act
collection.Insert(1, mediaTypeHeaderValue);
// Assert
Assert.Equal(1, collection.IndexOf("application/json; charset=utf-16"));
}
[Fact]
public void Remove_MediaTypeHeaderValue_RemovesTheStringSegmentRepresentationOfTheMediaType()
{
// Arrange
var collection = new MediaTypeCollection
{
MediaTypeHeaderValue.Parse("text/plain"),
MediaTypeHeaderValue.Parse("text/xml")
};
// Act
collection.Remove(MediaTypeHeaderValue.Parse("text/xml"));
// Assert
Assert.DoesNotContain("text/xml", collection);
}
}
}

View File

@ -10,11 +10,11 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features; 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.TestCommon;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Net.Http.Headers;
using Moq; using Moq;
using Xunit; using Xunit;
@ -41,14 +41,14 @@ namespace Microsoft.AspNet.Mvc
// Arrange // Arrange
var path = Path.GetFullPath("helllo.txt"); var path = Path.GetFullPath("helllo.txt");
var contentType = "text/plain; charset=us-ascii; p1=p1-value"; var contentType = "text/plain; charset=us-ascii; p1=p1-value";
var expectedMediaType = MediaTypeHeaderValue.Parse(contentType); var expectedMediaType = contentType;
// Act // Act
var result = new PhysicalFileResult(path, contentType); var result = new PhysicalFileResult(path, contentType);
// Assert // Assert
Assert.Equal(path, result.FileName); Assert.Equal(path, result.FileName);
Assert.Equal(expectedMediaType, result.ContentType); MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
} }
[Fact] [Fact]
@ -99,7 +99,7 @@ namespace Microsoft.AspNet.Mvc
// Arrange // Arrange
var expectedContentType = "text/foo; charset=us-ascii"; var expectedContentType = "text/foo; charset=us-ascii";
var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile_ASCII.txt")); var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile_ASCII.txt"));
var result = new TestPhysicalFileResult(path, MediaTypeHeaderValue.Parse(expectedContentType)) var result = new TestPhysicalFileResult(path, expectedContentType)
{ {
IsAscii = true IsAscii = true
}; };
@ -205,11 +205,6 @@ namespace Microsoft.AspNet.Mvc
{ {
} }
public TestPhysicalFileResult(string filePath, MediaTypeHeaderValue contentType)
: base(filePath, contentType)
{
}
public bool IsAscii { get; set; } = false; public bool IsAscii { get; set; } = false;
protected override Stream GetFileStream(string path) protected override Stream GetFileStream(string path)

View File

@ -8,7 +8,9 @@ using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Moq; using Moq;
using Xunit; using Xunit;
@ -21,8 +23,8 @@ namespace Microsoft.AspNet.Mvc.Test
public async Task ProducesAttribute_SetsContentType() public async Task ProducesAttribute_SetsContentType()
{ {
// Arrange // Arrange
var mediaType1 = MediaTypeHeaderValue.Parse("application/json"); var mediaType1 = new StringSegment("application/json");
var mediaType2 = MediaTypeHeaderValue.Parse("text/json;charset=utf-8"); var mediaType2 = new StringSegment("text/json;charset=utf-8");
var producesContentAttribute = new ProducesAttribute("application/json", "text/json;charset=utf-8"); var producesContentAttribute = new ProducesAttribute("application/json", "text/json;charset=utf-8");
var resultExecutingContext = CreateResultExecutingContext(new IFilterMetadata[] { producesContentAttribute }); var resultExecutingContext = CreateResultExecutingContext(new IFilterMetadata[] { producesContentAttribute });
var next = new ResultExecutionDelegate( var next = new ResultExecutionDelegate(
@ -34,8 +36,8 @@ namespace Microsoft.AspNet.Mvc.Test
// Assert // Assert
var objectResult = resultExecutingContext.Result as ObjectResult; var objectResult = resultExecutingContext.Result as ObjectResult;
Assert.Equal(2, objectResult.ContentTypes.Count); Assert.Equal(2, objectResult.ContentTypes.Count);
ValidateMediaType(mediaType1, objectResult.ContentTypes[0]); MediaTypeAssert.Equal(mediaType1, objectResult.ContentTypes[0]);
ValidateMediaType(mediaType2, objectResult.ContentTypes[1]); MediaTypeAssert.Equal(mediaType2, objectResult.ContentTypes[1]);
} }
[Fact] [Fact]
@ -153,20 +155,6 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Empty(producesAttribute.ContentTypes); Assert.Empty(producesAttribute.ContentTypes);
} }
private static void ValidateMediaType(MediaTypeHeaderValue expectedMediaType, MediaTypeHeaderValue actualMediaType)
{
Assert.Equal(expectedMediaType.MediaType, actualMediaType.MediaType);
Assert.Equal(expectedMediaType.SubType, actualMediaType.SubType);
Assert.Equal(expectedMediaType.Charset, actualMediaType.Charset);
Assert.Equal(expectedMediaType.MatchesAllTypes, actualMediaType.MatchesAllTypes);
Assert.Equal(expectedMediaType.MatchesAllSubTypes, actualMediaType.MatchesAllSubTypes);
Assert.Equal(expectedMediaType.Parameters.Count, actualMediaType.Parameters.Count);
foreach (var item in expectedMediaType.Parameters)
{
Assert.Equal(item.Value, NameValueHeaderValue.Find(actualMediaType.Parameters, item.Name).Value);
}
}
private static ResultExecutedContext CreateResultExecutedContext(ResultExecutingContext context) private static ResultExecutedContext CreateResultExecutedContext(ResultExecutingContext context)
{ {
return new ResultExecutedContext(context, context.Filters, context.Result, context.Controller); return new ResultExecutedContext(context, context.Filters, context.Result, context.Controller);

View File

@ -11,11 +11,11 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features; 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.TestCommon;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Net.Http.Headers;
using Moq; using Moq;
using Xunit; using Xunit;
@ -42,14 +42,14 @@ namespace Microsoft.AspNet.Mvc
// Arrange // Arrange
var path = Path.GetFullPath("helllo.txt"); var path = Path.GetFullPath("helllo.txt");
var contentType = "text/plain; charset=us-ascii; p1=p1-value"; var contentType = "text/plain; charset=us-ascii; p1=p1-value";
var expectedMediaType = MediaTypeHeaderValue.Parse(contentType); var expectedMediaType = contentType;
// Act // Act
var result = new VirtualFileResult(path, contentType); var result = new VirtualFileResult(path, contentType);
// Assert // Assert
Assert.Equal(path, result.FileName); Assert.Equal(path, result.FileName);
Assert.Equal(expectedMediaType, result.ContentType); MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
} }
[Fact] [Fact]
@ -137,7 +137,7 @@ namespace Microsoft.AspNet.Mvc
// Arrange // Arrange
var expectedContentType = "text/foo; charset=us-ascii"; var expectedContentType = "text/foo; charset=us-ascii";
var result = new TestVirtualFileResult( var result = new TestVirtualFileResult(
"FilePathResultTestFile_ASCII.txt", MediaTypeHeaderValue.Parse(expectedContentType)) "FilePathResultTestFile_ASCII.txt", expectedContentType)
{ {
FileProvider = GetFileProvider("FilePathResultTestFile_ASCII.txt"), FileProvider = GetFileProvider("FilePathResultTestFile_ASCII.txt"),
IsAscii = true, IsAscii = true,
@ -347,11 +347,6 @@ namespace Microsoft.AspNet.Mvc
{ {
} }
public TestVirtualFileResult(string filePath, MediaTypeHeaderValue contentType)
: base(filePath, contentType)
{
}
public bool IsAscii { get; set; } = false; public bool IsAscii { get; set; } = false;
protected override Stream GetFileStream(IFileInfo fileInfo) protected override Stream GetFileStream(IFileInfo fileInfo)

View File

@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
var context = GetActionContext(); var context = GetActionContext();
var result = new JsonResult(new { foo = "abcd" }); var result = new JsonResult(new { foo = "abcd" });
result.ContentType = new MediaTypeHeaderValue("text/json"); result.ContentType = "text/json";
var executor = CreateExcutor(); var executor = CreateExcutor();
// Act // Act
@ -74,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
result.ContentType = new MediaTypeHeaderValue("text/json") result.ContentType = new MediaTypeHeaderValue("text/json")
{ {
Encoding = Encoding.ASCII Encoding = Encoding.ASCII
}; }.ToString();
var executor = CreateExcutor(); var executor = CreateExcutor();
// Act // Act

View File

@ -14,6 +14,7 @@ using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Testing; using Microsoft.AspNet.Testing;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Moq; using Moq;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -262,7 +263,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
typeof(string), typeof(string),
content) content)
{ {
ContentType = mediaType, ContentType = new StringSegment(mediaType.ToString()),
}; };
// Act // Act
@ -313,7 +314,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
outputType, outputType,
outputValue) outputValue)
{ {
ContentType = mediaTypeHeaderValue, ContentType = new StringSegment(contentType),
}; };
} }

View File

@ -12,6 +12,7 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Formatters.Xml.Internal; using Microsoft.AspNet.Mvc.Formatters.Xml.Internal;
using Microsoft.AspNet.Testing.xunit; using Microsoft.AspNet.Testing.xunit;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Moq; using Moq;
using Xunit; using Xunit;
@ -123,7 +124,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var formatter = new TestXmlDataContractSerializerOutputFormatter(); var formatter = new TestXmlDataContractSerializerOutputFormatter();
var context = GetOutputFormatterContext(input, typeof(DummyClass)); var context = GetOutputFormatterContext(input, typeof(DummyClass));
context.ContentType = MediaTypeHeaderValue.Parse("application/xml"); context.ContentType = new StringSegment("application/xml");
// Act // Act
formatter.CanWriteResult(context); formatter.CanWriteResult(context);
@ -382,7 +383,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
// Arrange // Arrange
var formatter = new XmlDataContractSerializerOutputFormatter(); var formatter = new XmlDataContractSerializerOutputFormatter();
var outputFormatterContext = GetOutputFormatterContext(input, declaredType); var outputFormatterContext = GetOutputFormatterContext(input, declaredType);
outputFormatterContext.ContentType = MediaTypeHeaderValue.Parse("application/xml"); outputFormatterContext.ContentType = new StringSegment("application/xml");
// Act // Act
var result = formatter.CanWriteResult(outputFormatterContext); var result = formatter.CanWriteResult(outputFormatterContext);
@ -412,7 +413,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
// Act // Act
var result = formatter.GetSupportedContentTypes( var result = formatter.GetSupportedContentTypes(
MediaTypeHeaderValue.Parse("application/xml"), "application/xml",
type); type);
// Assert // Assert

View File

@ -11,6 +11,7 @@ using System.Xml.Serialization;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Formatters.Xml.Internal; using Microsoft.AspNet.Mvc.Formatters.Xml.Internal;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Moq; using Moq;
using Xunit; using Xunit;
@ -76,7 +77,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var formatter = new TestXmlSerializerOutputFormatter(); var formatter = new TestXmlSerializerOutputFormatter();
var context = GetOutputFormatterContext(input, typeof(DummyClass)); var context = GetOutputFormatterContext(input, typeof(DummyClass));
context.ContentType = MediaTypeHeaderValue.Parse("application/xml"); context.ContentType = new StringSegment("application/xml");
// Act // Act
formatter.CanWriteResult(context); formatter.CanWriteResult(context);
@ -300,7 +301,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
// Arrange // Arrange
var formatter = new XmlSerializerOutputFormatter(); var formatter = new XmlSerializerOutputFormatter();
var outputFormatterContext = GetOutputFormatterContext(input, declaredType); var outputFormatterContext = GetOutputFormatterContext(input, declaredType);
outputFormatterContext.ContentType = MediaTypeHeaderValue.Parse("application/xml"); outputFormatterContext.ContentType = new StringSegment("application/xml");
// Act // Act
var result = formatter.CanWriteResult(outputFormatterContext); var result = formatter.CanWriteResult(outputFormatterContext);
@ -342,7 +343,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
var formatter = new XmlSerializerOutputFormatter(); var formatter = new XmlSerializerOutputFormatter();
// Act // Act
var result = formatter.GetSupportedContentTypes(MediaTypeHeaderValue.Parse("application/xml"), type); var result = formatter.GetSupportedContentTypes("application/xml", type);
// Assert // Assert
if (expectedOutput != null) if (expectedOutput != null)

View File

@ -0,0 +1,47 @@
using System;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Xunit.Sdk;
namespace Microsoft.AspNet.Mvc.TestCommon
{
public class MediaTypeAssert
{
public static void Equal(string left, string right)
{
Equal(new StringSegment(left), new StringSegment(right));
}
public static void Equal(string left, StringSegment right)
{
Equal(new StringSegment(left), right);
}
public static void Equal(StringSegment left, string right)
{
Equal(left, new StringSegment(right));
}
public static void Equal(StringSegment left, StringSegment right)
{
if (!left.HasValue && !right.HasValue)
{
return;
}
else if (!left.HasValue || !right.HasValue)
{
throw new EqualException(left.ToString(), right.ToString());
}
MediaTypeHeaderValue leftMediaType = null;
MediaTypeHeaderValue rightMediaType = null;
if (!MediaTypeHeaderValue.TryParse(left.Value, out leftMediaType) ||
!MediaTypeHeaderValue.TryParse(right.Value, out rightMediaType) ||
!leftMediaType.Equals(rightMediaType))
{
throw new EqualException(left.ToString(), right.ToString());
}
}
}
}

View File

@ -8,6 +8,7 @@ using System.Text;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Controllers; using Microsoft.AspNet.Mvc.Controllers;
using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Moq; using Moq;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -90,7 +91,7 @@ namespace Microsoft.AspNet.Mvc
var contentResult = Assert.IsType<ContentResult>(result); var contentResult = Assert.IsType<ContentResult>(result);
Assert.Equal(content, contentResult.Content); Assert.Equal(content, contentResult.Content);
Assert.Equal("text/asp; charset=us-ascii", contentResult.ContentType.ToString()); Assert.Equal("text/asp; charset=us-ascii", contentResult.ContentType.ToString());
Assert.Equal(encoding, contentResult.ContentType.Encoding); Assert.Equal(encoding, MediaTypeEncoding.GetEncoding(contentResult.ContentType));
} }
[Theory] [Theory]

View File

@ -12,9 +12,11 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
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.Infrastructure; using Microsoft.AspNet.Mvc.Infrastructure;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.AspNet.Mvc.ViewComponents; using Microsoft.AspNet.Mvc.ViewComponents;
using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; using Microsoft.AspNet.Mvc.ViewFeatures.Buffer;
@ -367,26 +369,26 @@ namespace Microsoft.AspNet.Mvc
Assert.Equal(404, actionContext.HttpContext.Response.StatusCode); Assert.Equal(404, actionContext.HttpContext.Response.StatusCode);
} }
public static TheoryData<MediaTypeHeaderValue, string> ViewComponentResultContentTypeData public static TheoryData<string, string> ViewComponentResultContentTypeData
{ {
get get
{ {
return new TheoryData<MediaTypeHeaderValue, string> return new TheoryData<string, string>
{ {
{ {
null, null,
"text/html; charset=utf-8" "text/html; charset=utf-8"
}, },
{ {
new MediaTypeHeaderValue("text/foo"), "text/foo",
"text/foo" "text/foo"
}, },
{ {
MediaTypeHeaderValue.Parse("text/foo;p1=p1-value"), "text/foo;p1=p1-value",
"text/foo; p1=p1-value" "text/foo; p1=p1-value"
}, },
{ {
new MediaTypeHeaderValue("text/foo") { Encoding = Encoding.ASCII }, new MediaTypeHeaderValue("text/foo") { Encoding = Encoding.ASCII }.ToString(),
"text/foo; charset=us-ascii" "text/foo; charset=us-ascii"
} }
}; };
@ -396,8 +398,8 @@ namespace Microsoft.AspNet.Mvc
[Theory] [Theory]
[MemberData(nameof(ViewComponentResultContentTypeData))] [MemberData(nameof(ViewComponentResultContentTypeData))]
public async Task ViewComponentResult_SetsContentTypeHeader( public async Task ViewComponentResult_SetsContentTypeHeader(
MediaTypeHeaderValue contentType, string contentType,
string expectedContentTypeHeaderValue) string expectedContentType)
{ {
// Arrange // Arrange
var descriptor = new ViewComponentDescriptor() var descriptor = new ViewComponentDescriptor()
@ -424,13 +426,14 @@ namespace Microsoft.AspNet.Mvc
await viewComponentResult.ExecuteResultAsync(actionContext); await viewComponentResult.ExecuteResultAsync(actionContext);
// Assert // Assert
Assert.Equal(expectedContentTypeHeaderValue, actionContext.HttpContext.Response.ContentType); var resultContentType = actionContext.HttpContext.Response.ContentType;
MediaTypeAssert.Equal(expectedContentType, resultContentType);
// Check if the original instance provided by the user has not changed. // Check if the original instance provided by the user has not changed.
// Since we do not have access to the new instance created within the view executor, // Since we do not have access to the new instance created within the view executor,
// check if at least the content is the same. // check if at least the content is the same.
var contentTypeAfterViewResultExecution = contentType?.ToString(); var contentTypeAfterViewResultExecution = contentType?.ToString();
Assert.Equal(contentTypeBeforeViewResultExecution, contentTypeAfterViewResultExecution); MediaTypeAssert.Equal(contentTypeBeforeViewResultExecution, contentTypeAfterViewResultExecution);
} }
[Fact] [Fact]
@ -454,7 +457,7 @@ namespace Microsoft.AspNet.Mvc
{ {
Arguments = new { name = "World!" }, Arguments = new { name = "World!" },
ViewComponentName = "Text", ViewComponentName = "Text",
ContentType = new MediaTypeHeaderValue("text/html") { Encoding = Encoding.UTF8 }, ContentType = new MediaTypeHeaderValue("text/html") { Encoding = Encoding.UTF8 }.ToString(),
TempData = _tempDataDictionary, TempData = _tempDataDictionary,
}; };

View File

@ -6,6 +6,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
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.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewEngines;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
@ -262,7 +263,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
var context = GetActionContext(); var context = GetActionContext();
var executor = GetViewExecutor(); var executor = GetViewExecutor();
var contentType = MediaTypeHeaderValue.Parse("application/x-my-content-type"); var contentType = "application/x-my-content-type";
var viewResult = new PartialViewResult var viewResult = new PartialViewResult
{ {
@ -281,7 +282,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
// Check if the original instance provided by the user has not changed. // Check if the original instance provided by the user has not changed.
// Since we do not have access to the new instance created within the view executor, // Since we do not have access to the new instance created within the view executor,
// check if at least the content is the same. // check if at least the content is the same.
Assert.Null(contentType.Encoding); Assert.Null(MediaTypeEncoding.GetEncoding(contentType));
} }
[Fact] [Fact]

View File

@ -10,8 +10,10 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
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.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewEngines;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -115,11 +117,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
view, view,
viewData, viewData,
Mock.Of<ITempDataDictionary>(), Mock.Of<ITempDataDictionary>(),
contentType, contentType?.ToString(),
statusCode: null); statusCode: null);
// Assert // Assert
Assert.Equal(expectedContentType, context.Response.ContentType); MediaTypeAssert.Equal(expectedContentType, context.Response.ContentType);
Assert.Equal("abcd", Encoding.UTF8.GetString(memoryStream.ToArray())); Assert.Equal("abcd", Encoding.UTF8.GetString(memoryStream.ToArray()));
} }

View File

@ -6,6 +6,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
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.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewEngines;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
@ -258,7 +259,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
var context = GetActionContext(); var context = GetActionContext();
var executor = GetViewExecutor(); var executor = GetViewExecutor();
var contentType = MediaTypeHeaderValue.Parse("application/x-my-content-type"); var contentType = "application/x-my-content-type";
var viewResult = new ViewResult var viewResult = new ViewResult
{ {
@ -273,11 +274,6 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
// Assert // Assert
Assert.Equal("application/x-my-content-type", context.HttpContext.Response.ContentType); Assert.Equal("application/x-my-content-type", context.HttpContext.Response.ContentType);
// Check if the original instance provided by the user has not changed.
// Since we do not have access to the new instance created within the view executor,
// check if at least the content is the same.
Assert.Null(contentType.Encoding);
} }
[Fact] [Fact]

View File

@ -10,6 +10,7 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Controllers; using Microsoft.AspNet.Mvc.Controllers;
using Microsoft.AspNet.Mvc.Formatters;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -264,7 +265,7 @@ namespace System.Web.Http
var jsonResult = Assert.IsType<JsonResult>(result); var jsonResult = Assert.IsType<JsonResult>(result);
Assert.Same(product, jsonResult.Value); Assert.Same(product, jsonResult.Value);
Assert.Same(Encoding.UTF8, jsonResult.ContentType.Encoding); Assert.Same(Encoding.UTF8, MediaTypeEncoding.GetEncoding(jsonResult.ContentType));
} }
[Fact] [Fact]

View File

@ -20,7 +20,7 @@ namespace BasicWebSite.Controllers
public JsonResult CustomContentType() public JsonResult CustomContentType()
{ {
var result = new JsonResult(new { Message = "hello" }); var result = new JsonResult(new { Message = "hello" });
result.ContentType = MediaTypeHeaderValue.Parse("application/message+json"); result.ContentType = "application/message+json";
return result; return result;
} }

View File

@ -38,9 +38,11 @@ namespace BasicWebSite.Formatters
builder.AppendLine(); builder.AppendLine();
builder.AppendLine("END:VCARD"); builder.AppendLine("END:VCARD");
var selectedEncoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8;
await context.HttpContext.Response.WriteAsync( await context.HttpContext.Response.WriteAsync(
builder.ToString(), builder.ToString(),
context.ContentType?.Encoding ?? Encoding.UTF8); selectedEncoding);
} }
} }
} }

View File

@ -41,9 +41,11 @@ namespace BasicWebSite.Formatters
builder.AppendLine(); builder.AppendLine();
builder.AppendLine("END:VCARD"); builder.AppendLine("END:VCARD");
var selectedEncoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8;
await context.HttpContext.Response.WriteAsync( await context.HttpContext.Response.WriteAsync(
builder.ToString(), builder.ToString(),
context.ContentType?.Encoding ?? Encoding.UTF8); selectedEncoding);
} }
} }
} }

View File

@ -14,7 +14,7 @@ namespace FiltersWebSite
context.Result = new ContentResult() context.Result = new ContentResult()
{ {
Content = "4", Content = "4",
ContentType = new MediaTypeHeaderValue("text/plain") ContentType = "text/plain"
}; };
} }

View File

@ -14,7 +14,7 @@ namespace FiltersWebSite
context.Result = new ContentResult context.Result = new ContentResult
{ {
Content = "The Action was never executed", Content = "The Action was never executed",
ContentType = new MediaTypeHeaderValue("text/plain") ContentType = "text/plain"
}; };
} }
} }

View File

@ -21,7 +21,7 @@ namespace FiltersWebSite
return new ContentResult() return new ContentResult()
{ {
Content = content, Content = content,
ContentType = new MediaTypeHeaderValue("text/plain"), ContentType = "text/plain",
}; };
} }
} }