From 830fd410f5c3d0c02bf0d6b4a60749eb4b743d05 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Thu, 10 Dec 2015 11:15:33 -0800 Subject: [PATCH] Remove runtime dependency usage of MediaTypeHeaderValue. Removes usage of MediaTypeHeaderValue in ApiExplorer, InputFormatters and OutputFormatters Public interface changes with stub implementation. --- .../OutputFormatterCanWriteContext.cs | 4 +- .../ApiResponseFormat.cs | 3 +- .../DefaultApiDescriptionProvider.cs | 5 +- .../IApiResponseFormatMetadataProvider.cs | 5 +- .../IApiResponseMetadataProvider.cs | 5 +- .../ConsumesAttribute.cs | 33 +-- .../ContentResult.cs | 8 +- .../ControllerBase.cs | 2 +- .../FileContentResult.cs | 11 +- src/Microsoft.AspNet.Mvc.Core/FileResult.cs | 7 +- .../FileStreamResult.cs | 7 +- .../Formatters/FormatFilter.cs | 6 +- .../Formatters/FormatterMappings.cs | 53 +++-- .../Formatters/InputFormatter.cs | 27 +-- .../Formatters/MediaTypeCollection.cs | 73 +++++++ .../Formatters/MediaTypeComparisons.cs | 120 +++++++++++ .../Formatters/MediaTypeEncoding.cs | 99 +++++++++ .../Formatters/MediaTypeSegmentWithQuality.cs | 41 ++++ .../Formatters/OutputFormatter.cs | 107 ++++++---- .../Formatters/StringOutputFormatter.cs | 9 +- .../Infrastructure/ObjectResultExecutor.cs | 197 +++++++----------- .../Internal/ResponseContentTypeHelper.cs | 28 +-- .../ObjectResultExecutorLoggerExtensions.cs | 11 +- src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs | 3 +- src/Microsoft.AspNet.Mvc.Core/ObjectResult.cs | 6 +- .../PhysicalFileResult.cs | 11 +- .../ProducesAttribute.cs | 25 ++- .../Properties/Resources.Designer.cs | 16 ++ src/Microsoft.AspNet.Mvc.Core/Resources.resx | 3 + .../VirtualFileResult.cs | 11 +- .../Infrastructure/JsonResultExecutor.cs | 4 +- .../JsonOutputFormatter.cs | 2 +- .../JsonResult.cs | 2 +- ...mlDataContractSerializerOutputFormatter.cs | 3 +- .../XmlSerializerOutputFormatter.cs | 3 +- .../PartialViewResult.cs | 4 +- .../ViewComponentResult.cs | 5 +- .../ViewFeatures/ViewExecutor.cs | 9 +- .../ViewResult.cs | 4 +- .../ApiController.cs | 5 +- .../DefaultApiDescriptionProviderTest.cs | 7 +- .../ContentResultTest.cs | 13 +- .../ControllerBaseTest.cs | 14 +- .../FileContentResultTest.cs | 10 +- .../FileResultTest.cs | 4 +- .../FileStreamResultTest.cs | 10 +- .../Formatters/FormatFilterTest.cs | 34 +-- .../Formatters/FormatterMappingsTest.cs | 29 ++- .../Formatters/MediaTypeComparisonsTest.cs | 65 ++++++ .../Formatters/NoContentFormatterTests.cs | 7 +- .../Formatters/OutputFormatterTests.cs | 60 +++++- .../Formatters/StreamOutputFormatterTest.cs | 5 +- .../Formatters/StringOutputFormatterTests.cs | 10 +- .../ObjectResultExecutorTest.cs | 58 +++--- .../Internal/ResponseContentTypeHelperTest.cs | 9 +- .../MediaTypeCollectionTest.cs | 61 ++++++ .../PhysicalFileResultTest.cs | 13 +- .../ProducesAttributeTests.cs | 24 +-- .../VirtualFileResultTest.cs | 13 +- .../Infrastructure/JsonResultExecutorTest.cs | 4 +- .../JsonOutputFormatterTests.cs | 5 +- ...taContractSerializerOutputFormatterTest.cs | 7 +- .../XmlSerializerOutputFormatterTest.cs | 7 +- .../MediaTypeAssert.cs | 47 +++++ .../ControllerUnitTestabilityTests.cs | 3 +- .../ViewComponentResultTest.cs | 23 +- .../PartialViewResultExecutorTest.cs | 5 +- .../ViewFeatures/ViewExecutorTest.cs | 6 +- .../ViewFeatures/ViewResultExecutorTest.cs | 8 +- .../ApiControllerTest.cs | 3 +- .../Controllers/JsonResultController.cs | 2 +- .../Formatters/VCardFormatter_V3.cs | 4 +- .../Formatters/VCardFormatter_V4.cs | 4 +- .../Filters/RandomNumberFilter.cs | 2 +- .../Filters/ShortCircuitActionFilter.cs | 2 +- .../WebSites/FiltersWebSite/Helper/Helpers.cs | 2 +- 76 files changed, 1061 insertions(+), 496 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeCollection.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeComparisons.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeEncoding.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeSegmentWithQuality.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeComparisonsTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/MediaTypeCollectionTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.TestCommon/MediaTypeAssert.cs diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs index 4a2f905d38..588e13db40 100644 --- a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs +++ b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.Net.Http.Headers; +using Microsoft.Extensions.Primitives; namespace Microsoft.AspNet.Mvc.Formatters { @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.Formatters /// and expect to see the same value provided in /// /// - public virtual MediaTypeHeaderValue ContentType { get; set; } + public virtual StringSegment ContentType { get; set; } /// /// Gets or sets a value indicating that content-negotiation could not find a formatter based on the diff --git a/src/Microsoft.AspNet.Mvc.ApiExplorer/ApiResponseFormat.cs b/src/Microsoft.AspNet.Mvc.ApiExplorer/ApiResponseFormat.cs index dad272f440..ff475cb545 100644 --- a/src/Microsoft.AspNet.Mvc.ApiExplorer/ApiResponseFormat.cs +++ b/src/Microsoft.AspNet.Mvc.ApiExplorer/ApiResponseFormat.cs @@ -2,7 +2,6 @@ // 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.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.ApiExplorer { @@ -19,6 +18,6 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer /// /// The media type of the response. /// - public MediaTypeHeaderValue MediaType { get; set; } + public string MediaType { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNet.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index ccfde0a0dd..c79a935497 100644 --- a/src/Microsoft.AspNet.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -15,7 +15,6 @@ using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Template; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Options; -using Microsoft.Net.Http.Headers; 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 // the results of the previous ones. This is similar to the execution path for content-negotiation. - var contentTypes = new List(); + var contentTypes = new MediaTypeCollection(); if (responseMetadataAttributes != null) { foreach (var metadataAttribute in responseMetadataAttributes) @@ -322,7 +321,7 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer if (contentTypes.Count == 0) { - contentTypes.Add(null); + contentTypes.Add((string)null); } foreach (var contentType in contentTypes) diff --git a/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs index 80c8c088ed..7bf083a0d8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.ApiExplorer { @@ -28,8 +27,8 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer /// The for which the supported content types are desired. /// /// Content types which are supported by the . - IReadOnlyList GetSupportedContentTypes( - MediaTypeHeaderValue contentType, + IReadOnlyList GetSupportedContentTypes( + string contentType, Type objectType); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs index 5db0c96c93..d1addd7c60 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs @@ -2,8 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using Microsoft.Net.Http.Headers; +using Microsoft.AspNet.Mvc.Formatters; namespace Microsoft.AspNet.Mvc.ApiExplorer { @@ -20,6 +19,6 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer /// /// Configures a collection of allowed content types which can be produced by the action. /// - void SetContentTypes(IList contentTypes); + void SetContentTypes(MediaTypeCollection contentTypes); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ConsumesAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/ConsumesAttribute.cs index 83ef895467..7b44014b56 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ConsumesAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ConsumesAttribute.cs @@ -8,6 +8,8 @@ using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.ActionConstraints; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Filters; +using Microsoft.AspNet.Mvc.Formatters; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc @@ -30,6 +32,15 @@ namespace Microsoft.AspNet.Mvc 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); } @@ -39,7 +50,7 @@ namespace Microsoft.AspNet.Mvc int IActionConstraint.Order { get; } = ConsumesActionConstraintOrder; /// - public IList ContentTypes { get; set; } + public MediaTypeCollection ContentTypes { get; set; } /// 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. if (IsApplicable(context.ActionDescriptor)) { - MediaTypeHeaderValue requestContentType = null; - MediaTypeHeaderValue.TryParse(context.HttpContext.Request.ContentType, out requestContentType); + var requestContentType = context.HttpContext.Request.ContentType; // 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 (requestContentType != null && - !ContentTypes.Any(contentType => requestContentType.IsSubsetOf(contentType))) + !ContentTypes.Any(contentType => MediaTypeComparisons.IsSubsetOf(contentType, requestContentType))) { context.Result = new UnsupportedMediaTypeResult(); } @@ -86,8 +96,7 @@ namespace Microsoft.AspNet.Mvc return true; } - MediaTypeHeaderValue requestContentType = null; - MediaTypeHeaderValue.TryParse(context.RouteContext.HttpContext.Request.ContentType, out requestContentType); + var requestContentType = context.RouteContext.HttpContext.Request.ContentType; // 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. @@ -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 // 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; } @@ -164,22 +173,22 @@ namespace Microsoft.AspNet.Mvc } - private List GetContentTypes(string firstArg, string[] args) + private MediaTypeCollection GetContentTypes(string firstArg, string[] args) { var completeArgs = new List(); completeArgs.Add(firstArg); completeArgs.AddRange(args); - var contentTypes = new List(); + var contentTypes = new MediaTypeCollection(); foreach (var arg in completeArgs) { - var contentType = MediaTypeHeaderValue.Parse(arg); - if (contentType.MatchesAllSubTypes || contentType.MatchesAllTypes) + if (MediaTypeComparisons.MatchesAllSubtypes(arg) || + MediaTypeComparisons.MatchesAllTypes(arg)) { throw new InvalidOperationException( Resources.FormatMatchAllContentTypeIsNotAllowed(arg)); } - contentTypes.Add(contentType); + contentTypes.Add(arg); } return contentTypes; diff --git a/src/Microsoft.AspNet.Mvc.Core/ContentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ContentResult.cs index 6af9614891..bef0fae947 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ContentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ContentResult.cs @@ -16,10 +16,10 @@ namespace Microsoft.AspNet.Mvc { public class ContentResult : ActionResult { - private readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("text/plain") + private readonly string DefaultContentType = new MediaTypeHeaderValue("text/plain") { Encoding = Encoding.UTF8 - }; + }.ToString(); /// /// Gets or set the content representing the body of the response. @@ -27,9 +27,9 @@ namespace Microsoft.AspNet.Mvc public string Content { get; set; } /// - /// Gets or sets the representing the Content-Type header of the response. + /// Gets or sets the Content-Type header for the response. /// - public MediaTypeHeaderValue ContentType { get; set; } + public string ContentType { get; set; } /// /// Gets or sets the HTTP status code. diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerBase.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerBase.cs index 7e68bae65c..d76963088b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerBase.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerBase.cs @@ -271,7 +271,7 @@ namespace Microsoft.AspNet.Mvc var result = new ContentResult { Content = content, - ContentType = contentType + ContentType = contentType?.ToString() }; return result; diff --git a/src/Microsoft.AspNet.Mvc.Core/FileContentResult.cs b/src/Microsoft.AspNet.Mvc.Core/FileContentResult.cs index c9745663ba..7022554aa4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/FileContentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/FileContentResult.cs @@ -31,10 +31,6 @@ namespace Microsoft.AspNet.Mvc { throw new ArgumentNullException(nameof(fileContents)); } - if (contentType == null) - { - throw new ArgumentNullException(nameof(contentType)); - } } /// @@ -45,18 +41,13 @@ namespace Microsoft.AspNet.Mvc /// The bytes that represent the file contents. /// The Content-Type header of the response. public FileContentResult(byte[] fileContents, MediaTypeHeaderValue contentType) - : base(contentType) + : base(contentType?.ToString()) { if (fileContents == null) { throw new ArgumentNullException(nameof(fileContents)); } - if (contentType == null) - { - throw new ArgumentNullException(nameof(contentType)); - } - FileContents = fileContents; } diff --git a/src/Microsoft.AspNet.Mvc.Core/FileResult.cs b/src/Microsoft.AspNet.Mvc.Core/FileResult.cs index 04bb55e48a..2413eb1096 100644 --- a/src/Microsoft.AspNet.Mvc.Core/FileResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/FileResult.cs @@ -7,6 +7,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc @@ -24,7 +25,7 @@ namespace Microsoft.AspNet.Mvc /// the provided . /// /// The Content-Type header of the response. - protected FileResult(MediaTypeHeaderValue contentType) + protected FileResult(string contentType) { if (contentType == null) { @@ -35,9 +36,9 @@ namespace Microsoft.AspNet.Mvc } /// - /// Gets the representing the Content-Type header of the response. + /// Gets the Content-Type header for the response. /// - public MediaTypeHeaderValue ContentType { get; } + public string ContentType { get; } /// /// Gets the file name that will be used in the Content-Disposition header of the response. diff --git a/src/Microsoft.AspNet.Mvc.Core/FileStreamResult.cs b/src/Microsoft.AspNet.Mvc.Core/FileStreamResult.cs index 8f31fce05c..ef65b8dc0a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/FileStreamResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/FileStreamResult.cs @@ -42,18 +42,13 @@ namespace Microsoft.AspNet.Mvc /// The stream with the file. /// The Content-Type header of the response. public FileStreamResult(Stream fileStream, MediaTypeHeaderValue contentType) - : base(contentType) + : base(contentType?.ToString()) { if (fileStream == null) { throw new ArgumentNullException(nameof(fileStream)); } - if (contentType == null) - { - throw new ArgumentNullException(nameof(contentType)); - } - FileStream = fileStream; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs index 7bf4f07299..b64bc64d9f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs @@ -2,12 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.Mvc.ApiExplorer; using Microsoft.AspNet.Mvc.Filters; using Microsoft.Extensions.Options; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters { @@ -78,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // Determine media types this action supports. var responseTypeFilters = context.Filters.OfType(); - var supportedMediaTypes = new List(); + var supportedMediaTypes = new MediaTypeCollection(); foreach (var filter in responseTypeFilters) { filter.SetContentTypes(supportedMediaTypes); @@ -91,7 +89,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // 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 // 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(); } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatterMappings.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatterMappings.cs index e7b0b70ac5..518864dbf6 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatterMappings.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatterMappings.cs @@ -4,24 +4,46 @@ using System; using System.Collections.Generic; using Microsoft.AspNet.Mvc.Core; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters { /// - /// Used to specify mapping between the URL Format and corresponding . + /// Used to specify mapping between the URL Format and corresponding media type. /// public class FormatterMappings { - private readonly Dictionary _map = - new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _map = + new Dictionary(StringComparer.OrdinalIgnoreCase); /// - /// Sets mapping for the format to specified . - /// If the format already exists, the will be overwritten with the new value. + /// Sets mapping for the format to specified media type. + /// If the format already exists, the media type will be overwritten with the new value. /// /// The format value. - /// The for the format value. + /// The media type for the format value. + 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)); + } + + /// + /// Sets mapping for the format to specified media type. + /// If the format already exists, the media type will be overwritten with the new value. + /// + /// The format value. + /// The media type for the format value. public void SetMediaTypeMappingForFormat(string format, MediaTypeHeaderValue contentType) { if (format == null) @@ -36,31 +58,34 @@ namespace Microsoft.AspNet.Mvc.Formatters ValidateContentType(contentType); format = RemovePeriodIfPresent(format); - _map[format] = contentType.CopyAsReadOnly(); + _map[format] = contentType.ToString(); } /// - /// Gets for the specified format. + /// Gets the media type for the specified format. /// /// The format value. - /// The for input format. - public MediaTypeHeaderValue GetMediaTypeMappingForFormat(string format) + /// The media type for input 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); - MediaTypeHeaderValue value = null; + string value = null; _map.TryGetValue(format, out value); return value; } /// - /// Clears the mapping for the format. + /// Clears the media type mapping for the format. /// /// The format value. /// true if the format is successfully found and cleared; otherwise, false. diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs index a6fd215ef0..7330c0ef9d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters @@ -37,10 +38,10 @@ namespace Microsoft.AspNet.Mvc.Formatters public IList SupportedEncodings { get; } = new List(); /// - /// Gets the mutable collection of elements supported by + /// Gets the mutable collection of media type elements supported by /// this . /// - public IList SupportedMediaTypes { get; } = new List(); + public MediaTypeCollection SupportedMediaTypes { get; } = new MediaTypeCollection(); protected object GetDefaultValueForType(Type modelType) { @@ -61,8 +62,7 @@ namespace Microsoft.AspNet.Mvc.Formatters } var contentType = context.HttpContext.Request.ContentType; - MediaTypeHeaderValue requestContentType; - if (!MediaTypeHeaderValue.TryParse(contentType, out requestContentType)) + if (string.IsNullOrEmpty(contentType)) { return false; } @@ -71,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // client sent "text/plain" data and this formatter supports "text/*". return SupportedMediaTypes.Any(supportedMediaType => { - return requestContentType.IsSubsetOf(supportedMediaType); + return MediaTypeComparisons.IsSubsetOf(supportedMediaType, contentType); }); } @@ -107,27 +107,28 @@ namespace Microsoft.AspNet.Mvc.Formatters /// /// Returns an based on 's - /// . + /// character set. /// /// The . /// /// An based on 's - /// . null if no supported encoding was found. + /// character set. null if no supported encoding was found. /// protected Encoding SelectCharacterEncoding(InputFormatterContext context) { var request = context.HttpContext.Request; - MediaTypeHeaderValue contentType; - MediaTypeHeaderValue.TryParse(request.ContentType, out contentType); - if (contentType != null) + if (request.ContentType != null) { - var charset = contentType.Charset; - if (!string.IsNullOrEmpty(charset)) + var encoding = MediaTypeEncoding.GetEncoding(request.ContentType); + if (encoding != null) { foreach (var supportedEncoding in SupportedEncodings) { - if (string.Equals(charset, supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals( + encoding.WebName, + supportedEncoding.WebName, + StringComparison.OrdinalIgnoreCase)) { return supportedEncoding; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeCollection.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeCollection.cs new file mode 100644 index 0000000000..8af4c214eb --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeCollection.cs @@ -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 +{ + /// + /// A collection of media types. + /// + public class MediaTypeCollection : Collection + { + /// + /// Initializes a new instance of . + /// + public MediaTypeCollection() + { + } + + /// + /// Adds an object to the end of the . + /// + /// The media type to be added to the end of the . + public void Add(MediaTypeHeaderValue item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + Add(item.ToString()); + } + + /// + /// Inserts an element into the at the specified index. + /// + /// The zero-based index at which should be inserted. + /// The media type to insert. + 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()); + } + + /// + /// Removes the first occurrence of a specific media type from the . + /// + /// + /// true if is successfully removed; otherwise, false. + /// This method also returns false if was not found in the original + /// . + public bool Remove(MediaTypeHeaderValue item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + return Remove(item.ToString()); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeComparisons.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeComparisons.cs new file mode 100644 index 0000000000..33775d5ff4 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeComparisons.cs @@ -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 +{ + /// + /// Different types of tests against media type values. + /// + public static class MediaTypeComparisons + { + /// + /// Determines if the media type is a subset of the media type + /// without taking into account the quality parameter. + /// + /// The more general media type. + /// The more specific media type. + /// true if is a more general media type than ; + /// otherwise false. + public static bool IsSubsetOf(StringSegment set, string subset) + { + return IsSubsetOf(set, new StringSegment(subset)); + } + + /// + /// Determines if the media type is a subset of the media type + /// without taking into account the quality parameter. + /// + /// The more general media type. + /// The more specific media type. + /// true if is a more general media type than ; + /// otherwise false. + public static bool IsSubsetOf(string set, string subset) + { + return IsSubsetOf(new StringSegment(set), new StringSegment(subset)); + } + + /// + /// Determines if the media type is a subset of the media type. + /// Two media types are compatible if one is a subset of the other ignoring any charset + /// parameter. + /// + /// The more general media type. + /// The more specific media type. + /// Whether or not we should skip checking the quality parameter. + /// true if is a more general media type than ; + /// otherwise false. + 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); + } + + /// + /// Determines if the type of a given matches all types, E.g, */*. + /// + /// The media type to check + /// true if the matches all subtypes; otherwise false. + public static bool MatchesAllTypes(string mediaType) + { + return MatchesAllTypes(new StringSegment(mediaType)); + } + + /// + /// Determines if the type of a given matches all types, E.g, */*. + /// + /// The media type to check + /// true if the matches all subtypes; otherwise false. + public static bool MatchesAllTypes(StringSegment mediaType) + { + if (!mediaType.HasValue) + { + return false; + } + + MediaTypeHeaderValue parsedMediaType; + return MediaTypeHeaderValue.TryParse(mediaType.Value, out parsedMediaType) && + parsedMediaType.MatchesAllTypes; + } + + /// + /// Determines if the given matches all subtypes, E.g, text/*. + /// + /// The media type to check + /// true if the matches all subtypes; otherwise false. + public static bool MatchesAllSubtypes(string mediaType) + { + return MatchesAllSubtypes(new StringSegment(mediaType)); + } + + /// + /// Determines if the given matches all subtypes, E.g, text/*. + /// + /// The media type to check + /// true if the matches all subtypes; otherwise false. + public static bool MatchesAllSubtypes(StringSegment mediaType) + { + if (!mediaType.HasValue) + { + return false; + } + + MediaTypeHeaderValue parsedMediaType; + return MediaTypeHeaderValue.TryParse(mediaType.Value, out parsedMediaType) && + parsedMediaType.MatchesAllSubTypes; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeEncoding.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeEncoding.cs new file mode 100644 index 0000000000..a8da6ae7a1 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeEncoding.cs @@ -0,0 +1,99 @@ +using System; +using System.Text; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNet.Mvc.Formatters +{ + /// + /// A set of operations to manipulate the encoding of a media type value. + /// + public class MediaTypeEncoding + { + /// + /// Gets the for the given if it exists. + /// + /// The media type from which to get the charset parameter. + /// The of the media type if it exists; otherwise null. + public static Encoding GetEncoding(StringSegment mediaType) + { + var charset = GetCharsetParameter(mediaType); + return GetEncodingFromCharset(charset); + } + + /// + /// Gets the for the given if it exists. + /// + /// The media type from which to get the charset parameter. + /// The of the media type if it exists or a without value if not. + public static Encoding GetEncoding(string mediaType) + { + var charset = GetCharsetParameter(new StringSegment(mediaType)); + return GetEncodingFromCharset(charset); + } + + /// + /// Gets the charset parameter of the given if it exists. + /// + /// The media type from which to get the charset parameter. + /// The charset of the media type if it exists or a without value if not. + public static StringSegment GetCharsetParameter(StringSegment mediaType) + { + MediaTypeHeaderValue parsedMediaType; + if (MediaTypeHeaderValue.TryParse(mediaType.Value, out parsedMediaType)) + { + return new StringSegment(parsedMediaType.Charset); + } + return new StringSegment(); + } + + /// + /// Replaces the encoding of the given with the provided + /// . + /// + /// The media type whose encoding will be replaced. + /// The encoding that will replace the encoding in the + /// A media type with the replaced encoding. + public static string ReplaceEncoding(string mediaType, Encoding encoding) + { + return ReplaceEncoding(new StringSegment(mediaType), encoding); + } + + /// + /// Replaces the encoding of the given with the provided + /// . + /// + /// The media type whose encoding will be replaced. + /// The encoding that will replace the encoding in the + /// A media type with the replaced encoding. + 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; + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeSegmentWithQuality.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeSegmentWithQuality.cs new file mode 100644 index 0000000000..16f06fd876 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeSegmentWithQuality.cs @@ -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 +{ + /// + /// A media type with its associated quality. + /// + public struct MediaTypeSegmentWithQuality + { + /// + /// Initializes an instance of . + /// + /// The containing the media type. + /// The quality parameter of the media type or 1 in the case it does not exist. + public MediaTypeSegmentWithQuality(StringSegment mediaType, double quality) + { + MediaType = mediaType; + Quality = quality; + } + + /// + /// Gets the media type of this . + /// + public StringSegment MediaType { get; } + + /// + /// Gets the quality of this . + /// + public double Quality { get; } + + /// + public override string ToString() + { + // For logging purposes + return MediaType.ToString(); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs index 7863a8a498..41a8d9e57d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ApiExplorer; using Microsoft.AspNet.Mvc.Core; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters @@ -17,16 +18,13 @@ namespace Microsoft.AspNet.Mvc.Formatters /// public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider { - // using a field so we can return it as both IList and IReadOnlyList - private readonly List _supportedMediaTypes; - /// /// Initializes a new instance of the class. /// protected OutputFormatter() { SupportedEncodings = new List(); - _supportedMediaTypes = new List(); + SupportedMediaTypes = new MediaTypeCollection(); } /// @@ -37,13 +35,10 @@ namespace Microsoft.AspNet.Mvc.Formatters public IList SupportedEncodings { get; } /// - /// Gets the mutable collection of elements supported by + /// Gets the mutable collection of media type elements supported by /// this . /// - public IList SupportedMediaTypes - { - get { return _supportedMediaTypes; } - } + public MediaTypeCollection SupportedMediaTypes { get; } /// /// 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 } /// - public virtual IReadOnlyList GetSupportedContentTypes( - MediaTypeHeaderValue contentType, + public virtual IReadOnlyList GetSupportedContentTypes( + string contentType, Type objectType) { if (!CanWriteType(objectType)) @@ -68,21 +63,21 @@ namespace Microsoft.AspNet.Mvc.Formatters if (contentType == null) { // If contentType is null, then any type we support is valid. - return _supportedMediaTypes.Count > 0 ? _supportedMediaTypes : null; + return SupportedMediaTypes.Count > 0 ? SupportedMediaTypes : null; } else { - List mediaTypes = null; + List mediaTypes = null; // Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*" // requested and formatter supports "text/plain". Treat contentType like it came from an Accept header. - foreach (var mediaType in _supportedMediaTypes) + foreach (var mediaType in SupportedMediaTypes) { - if (mediaType.IsSubsetOf(contentType)) + if (MediaTypeComparisons.IsSubsetOf(new StringSegment(contentType), mediaType)) { if (mediaTypes == null) { - mediaTypes = new List(); + mediaTypes = new List(); } mediaTypes.Add(mediaType); @@ -114,15 +109,19 @@ namespace Microsoft.AspNet.Mvc.Formatters return encoding; } - var charset = context.ContentType?.Charset; - if (charset != null) + if (context.ContentType.HasValue) { - 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. - return context.ContentType.Encoding; + var supportedEncoding = SupportedEncodings[i]; + 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)); } - + if (!CanWriteType(context.ObjectType)) { 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 // it wants. if (SupportedMediaTypes.Count > 0) { - context.ContentType = SupportedMediaTypes[0]; + context.ContentType = new StringSegment(SupportedMediaTypes[0]); return true; } 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/*" // requested and formatter supports "text/plain". contentType is typically what we got in an Accept // header. + var contentType = context.ContentType; for (var i = 0; i < SupportedMediaTypes.Count; i++) { - var mediaType = SupportedMediaTypes[i]; - if (mediaType.IsSubsetOf(context.ContentType)) + var supportedMediaType = SupportedMediaTypes[i]; + if (MediaTypeComparisons.IsSubsetOf(contentType, supportedMediaType)) { - context.ContentType = mediaType; + context.ContentType = new StringSegment(SupportedMediaTypes[i]); return true; } } @@ -186,12 +186,12 @@ namespace Microsoft.AspNet.Mvc.Formatters } 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 (SupportedEncodings.Count > 0) { - selectedMediaType = SupportedMediaTypes[0]; + selectedMediaType = new StringSegment(SupportedMediaTypes[0]); } 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 // 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) { // 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; @@ -236,7 +234,7 @@ namespace Microsoft.AspNet.Mvc.Formatters } var response = context.HttpContext.Response; - response.ContentType = context.ContentType?.ToString(); + response.ContentType = context.ContentType.Value; } /// @@ -246,6 +244,33 @@ namespace Microsoft.AspNet.Mvc.Formatters /// A task which can write the response body. public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context); + /// + /// Adds or replaces the charset parameter in a given with the + /// given . + /// + /// The with the media type. + /// + /// The to add or replace in the . + /// + /// The mediaType with the given encoding. + 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 acceptCharsetHeaders) { if (acceptCharsetHeaders != null && acceptCharsetHeaders.Count > 0) @@ -329,5 +354,15 @@ namespace Microsoft.AspNet.Mvc.Formatters sorted.Reverse(); 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}"; + } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs index 0d8b335189..a8d0265096 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.Internal; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters @@ -19,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.Formatters { SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/plain").CopyAsReadOnly()); + SupportedMediaTypes.Add("text/plain"); } public override bool CanWriteResult(OutputFormatterCanWriteContext context) @@ -33,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // always return it as a text/plain format. if (context.ObjectType == typeof(string) || context.Object is string) { - context.ContentType = SupportedMediaTypes[0]; + context.ContentType = new StringSegment(SupportedMediaTypes[0]); return true; } @@ -55,7 +56,9 @@ namespace Microsoft.AspNet.Mvc.Formatters var response = context.HttpContext.Response; - return response.WriteAsync(valueAsString, context.ContentType?.Encoding ?? Encoding.UTF8); + return response.WriteAsync( + valueAsString, + MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8); } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs index 5ce9702056..2f6cee1935 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs @@ -13,6 +13,7 @@ using Microsoft.AspNet.Mvc.Internal; using Microsoft.AspNet.Mvc.Logging; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Infrastructure @@ -98,10 +99,10 @@ namespace Microsoft.AspNet.Mvc.Infrastructure { if (result.ContentTypes == null) { - result.ContentTypes = new List(); + result.ContentTypes = new MediaTypeCollection(); } - result.ContentTypes.Add(MediaTypeHeaderValue.Parse(responseContentType)); + result.ContentTypes.Add(responseContentType); } } @@ -157,7 +158,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure /// protected virtual IOutputFormatter SelectFormatter( OutputFormatterWriteContext formatterContext, - IList contentTypes, + MediaTypeCollection contentTypes, IList formatters) { if (formatterContext == null) @@ -185,14 +186,14 @@ namespace Microsoft.AspNet.Mvc.Infrastructure } var request = formatterContext.HttpContext.Request; - var acceptValues = PrepareAcceptValues(request.GetTypedHeaders().Accept); + var mediaTypes = GetMediaTypes(contentTypes, request); 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 // which can write the type. Let the formatter choose the Content-Type. - if (acceptValues == null || acceptValues.Count == 0) + if (!(mediaTypes.Count > 0)) { Logger.NoAcceptForNegotiation(); @@ -207,13 +208,13 @@ namespace Microsoft.AspNet.Mvc.Infrastructure selectedFormatter = SelectFormatterUsingSortedAcceptHeaders( formatterContext, formatters, - acceptValues); + mediaTypes); // 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. if (selectedFormatter == null) { - Logger.NoFormatterFromNegotiation(acceptValues); + Logger.NoFormatterFromNegotiation(mediaTypes); // Set this flag to indicate that content-negotiation has failed to let formatters decide // if they want to write the response or not. @@ -224,32 +225,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure } 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( formatterContext, formatters, - acceptValues); + mediaTypes); } if (selectedFormatter == null) @@ -270,6 +251,58 @@ namespace Microsoft.AspNet.Mvc.Infrastructure return selectedFormatter; } + private List GetMediaTypes( + MediaTypeCollection contentTypes, + HttpRequest request) + { + var result = new List(); + 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; + } + /// /// Selects the to write the response. The first formatter which /// 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) { - formatterContext.ContentType = null; + formatterContext.ContentType = new StringSegment(); if (formatter.CanWriteResult(formatterContext)) { return formatter; @@ -324,7 +357,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure protected virtual IOutputFormatter SelectFormatterUsingSortedAcceptHeaders( OutputFormatterWriteContext formatterContext, IList formatters, - IList sortedAcceptHeaders) + IList sortedAcceptHeaders) { if (formatterContext == null) { @@ -341,11 +374,13 @@ namespace Microsoft.AspNet.Mvc.Infrastructure 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)) { return formatter; @@ -373,7 +408,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure protected virtual IOutputFormatter SelectFormatterUsingAnyAcceptableContentType( OutputFormatterWriteContext formatterContext, IList formatters, - IList acceptableContentTypes) + MediaTypeCollection acceptableContentTypes) { if (formatterContext == null) { @@ -394,7 +429,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure { foreach (var contentType in acceptableContentTypes) { - formatterContext.ContentType = contentType; + formatterContext.ContentType = new StringSegment(contentType); if (formatter.CanWriteResult(formatterContext)) { return formatter; @@ -405,90 +440,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure return null; } - // There's no allocation-free way to sort an IList so we're going to have to live with the - // copy + insertion sort. - private IList PrepareAcceptValues(IList 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(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 contentTypes) + private void ValidateContentTypes(MediaTypeCollection contentTypes) { if (contentTypes == null) { @@ -498,7 +450,8 @@ namespace Microsoft.AspNet.Mvc.Infrastructure for (var i = 0; i < contentTypes.Count; i++) { var contentType = contentTypes[i]; - if (contentType.MatchesAllTypes || contentType.MatchesAllSubTypes) + if (MediaTypeComparisons.MatchesAllTypes(contentType) || + MediaTypeComparisons.MatchesAllSubtypes(contentType)) { var message = Resources.FormatObjectResult_MatchAllContentType( contentType, diff --git a/src/Microsoft.AspNet.Mvc.Core/Internal/ResponseContentTypeHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Internal/ResponseContentTypeHelper.cs index b2397a8030..ab7b078d2f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Internal/ResponseContentTypeHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Internal/ResponseContentTypeHelper.cs @@ -3,8 +3,7 @@ using System.Diagnostics; using System.Text; -using Microsoft.Net.Http.Headers; -using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.Formatters; namespace Microsoft.AspNet.Mvc.Internal { @@ -30,44 +29,47 @@ namespace Microsoft.AspNet.Mvc.Internal /// The content type to be used for the response content type header /// Encoding to be used for writing the response public static void ResolveContentTypeAndEncoding( - MediaTypeHeaderValue actionResultContentType, + string actionResultContentType, string httpResponseContentType, - MediaTypeHeaderValue defaultContentType, + string defaultContentType, out string resolvedContentType, out Encoding resolvedContentTypeEncoding) { 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 if (actionResultContentType != null) { - resolvedContentType = actionResultContentType.ToString(); - resolvedContentTypeEncoding = actionResultContentType.Encoding ?? defaultContentType.Encoding; + resolvedContentType = actionResultContentType; + var actionResultEncoding = MediaTypeEncoding.GetEncoding(actionResultContentType); + resolvedContentTypeEncoding = actionResultEncoding ?? defaultContentTypeEncoding; return; } // 2. User sets the ContentType property on the http response directly if (!string.IsNullOrEmpty(httpResponseContentType)) { - MediaTypeHeaderValue mediaType; - if (MediaTypeHeaderValue.TryParse(httpResponseContentType, out mediaType)) + var mediaTypeEncoding = MediaTypeEncoding.GetEncoding(httpResponseContentType); + if (mediaTypeEncoding != null) { resolvedContentType = httpResponseContentType; - resolvedContentTypeEncoding = mediaType.Encoding ?? defaultContentType.Encoding; + resolvedContentTypeEncoding = mediaTypeEncoding; } else { resolvedContentType = httpResponseContentType; - resolvedContentTypeEncoding = defaultContentType.Encoding; + resolvedContentTypeEncoding = defaultContentTypeEncoding; } return; } // 3. Fall-back to the default content type - resolvedContentType = defaultContentType.ToString(); - resolvedContentTypeEncoding = defaultContentType.Encoding; + resolvedContentType = defaultContentType; + resolvedContentTypeEncoding = defaultContentTypeEncoding; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ObjectResultExecutorLoggerExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ObjectResultExecutorLoggerExtensions.cs index ec968c0f65..8b1677efb8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/ObjectResultExecutorLoggerExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ObjectResultExecutorLoggerExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Microsoft.AspNet.Mvc.Formatters; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Logging @@ -16,7 +17,7 @@ namespace Microsoft.AspNet.Mvc.Logging private static readonly Action _formatterSelected; private static readonly Action _skippedContentNegotiation; private static readonly Action _noAcceptForNegotiation; - private static readonly Action, Exception> _noFormatterFromNegotiation; + private static readonly Action, Exception> _noFormatterFromNegotiation; static ObjectResultExecutorLoggerExtensions() { @@ -40,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.Logging LogLevel.Debug, 4, "No information found on request to perform content negotiation."); - _noFormatterFromNegotiation = LoggerMessage.Define>( + _noFormatterFromNegotiation = LoggerMessage.Define>( LogLevel.Debug, 5, "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); } - 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) @@ -77,7 +78,7 @@ namespace Microsoft.AspNet.Mvc.Logging _noAcceptForNegotiation(logger, null, null); } - public static void NoFormatterFromNegotiation(this ILogger logger, IEnumerable acceptTypes) + public static void NoFormatterFromNegotiation(this ILogger logger, IList acceptTypes) { _noFormatterFromNegotiation(logger, acceptTypes, null); } diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs index 381e62d31a..512478ca00 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs @@ -53,8 +53,7 @@ namespace Microsoft.AspNet.Mvc public FilterCollection Filters { get; } /// - /// Used to specify mapping between the URL Format and corresponding - /// . + /// Used to specify mapping between the URL Format and corresponding media type. /// public FormatterMappings FormatterMappings { get; } diff --git a/src/Microsoft.AspNet.Mvc.Core/ObjectResult.cs b/src/Microsoft.AspNet.Mvc.Core/ObjectResult.cs index e9f3eb3528..30d488548b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ObjectResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ObjectResult.cs @@ -2,12 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc { @@ -17,14 +15,14 @@ namespace Microsoft.AspNet.Mvc { Value = value; Formatters = new FormatterCollection(); - ContentTypes = new List(); + ContentTypes = new MediaTypeCollection(); } public object Value { get; set; } public FormatterCollection Formatters { get; set; } - public IList ContentTypes { get; set; } + public MediaTypeCollection ContentTypes { get; set; } public Type DeclaredType { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.Core/PhysicalFileResult.cs b/src/Microsoft.AspNet.Mvc.Core/PhysicalFileResult.cs index 6c343d3fef..70d2465f55 100644 --- a/src/Microsoft.AspNet.Mvc.Core/PhysicalFileResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/PhysicalFileResult.cs @@ -34,10 +34,6 @@ namespace Microsoft.AspNet.Mvc { throw new ArgumentNullException(nameof(fileName)); } - if (contentType == null) - { - throw new ArgumentNullException(nameof(contentType)); - } } /// @@ -47,18 +43,13 @@ namespace Microsoft.AspNet.Mvc /// The path to the file. The path must be an absolute path. /// The Content-Type header of the response. public PhysicalFileResult(string fileName, MediaTypeHeaderValue contentType) - : base(contentType) + : base(contentType?.ToString()) { if (fileName == null) { throw new ArgumentNullException(nameof(fileName)); } - if (contentType == null) - { - throw new ArgumentNullException(nameof(contentType)); - } - FileName = fileName; } diff --git a/src/Microsoft.AspNet.Mvc.Core/ProducesAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/ProducesAttribute.cs index a969c3fbb8..82fdb42719 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ProducesAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ProducesAttribute.cs @@ -8,6 +8,7 @@ using Microsoft.AspNet.Mvc.ApiExplorer; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Formatters; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc @@ -31,7 +32,7 @@ namespace Microsoft.AspNet.Mvc } Type = type; - ContentTypes = new List(); + ContentTypes = new MediaTypeCollection(); } /// @@ -46,12 +47,21 @@ namespace Microsoft.AspNet.Mvc 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); } public Type Type { get; set; } - public IList ContentTypes { get; set; } + public MediaTypeCollection ContentTypes { get; set; } public override void OnResultExecuting(ResultExecutingContext context) { @@ -74,16 +84,17 @@ namespace Microsoft.AspNet.Mvc } } - private List GetContentTypes(string firstArg, string[] args) + private MediaTypeCollection GetContentTypes(string firstArg, string[] args) { var completeArgs = new List(); completeArgs.Add(firstArg); completeArgs.AddRange(args); - var contentTypes = new List(); + var contentTypes = new MediaTypeCollection(); foreach (var arg in completeArgs) { - var contentType = MediaTypeHeaderValue.Parse(arg); - if (contentType.MatchesAllSubTypes || contentType.MatchesAllTypes) + var contentType = arg; + if (MediaTypeComparisons.MatchesAllSubtypes(contentType)|| + MediaTypeComparisons.MatchesAllTypes(contentType)) { throw new InvalidOperationException( Resources.FormatMatchAllContentTypeIsNotAllowed(arg)); @@ -95,7 +106,7 @@ namespace Microsoft.AspNet.Mvc return contentTypes; } - public void SetContentTypes(IList contentTypes) + public void SetContentTypes(MediaTypeCollection contentTypes) { contentTypes.Clear(); foreach (var contentType in ContentTypes) diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index 2918a933b4..9307dd86d2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -1034,6 +1034,22 @@ namespace Microsoft.AspNet.Mvc.Core return GetString("HttpResponseStreamWriter_StreamNotWritable"); } + /// + /// The argument '{0}' is invalid. Empty or null formats are not supported. + /// + internal static string FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat + { + get { return GetString("FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat"); } + } + + /// + /// The argument '{0}' is invalid. Empty or null formats are not supported. + /// + 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) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 4e9132532f..946db5d5f7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -319,4 +319,7 @@ The stream must support writing. + + The argument '{0}' is invalid. Empty or null formats are not supported. + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/VirtualFileResult.cs b/src/Microsoft.AspNet.Mvc.Core/VirtualFileResult.cs index 96a708c257..d60cdfd9c7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/VirtualFileResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/VirtualFileResult.cs @@ -37,10 +37,6 @@ namespace Microsoft.AspNet.Mvc { throw new ArgumentNullException(nameof(fileName)); } - if (contentType == null) - { - throw new ArgumentNullException(nameof(contentType)); - } } /// @@ -51,18 +47,13 @@ namespace Microsoft.AspNet.Mvc /// The path to the file. The path must be relative/virtual. /// The Content-Type header of the response. public VirtualFileResult(string fileName, MediaTypeHeaderValue contentType) - : base(contentType) + : base(contentType?.ToString()) { if (fileName == null) { throw new ArgumentNullException(nameof(fileName)); } - if (contentType == null) - { - throw new ArgumentNullException(nameof(contentType)); - } - FileName = fileName; } diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/Infrastructure/JsonResultExecutor.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/Infrastructure/JsonResultExecutor.cs index 3ba5ec62dc..e3bbf4ea8d 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Json/Infrastructure/JsonResultExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/Infrastructure/JsonResultExecutor.cs @@ -18,10 +18,10 @@ namespace Microsoft.AspNet.Mvc.Infrastructure /// public class JsonResultExecutor { - private static readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("application/json") + private static readonly string DefaultContentType = new MediaTypeHeaderValue("application/json") { Encoding = Encoding.UTF8 - }.CopyAsReadOnly(); + }.ToString(); /// /// Creates a new . diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs index efc69a30fa..6a927c3de7 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs @@ -140,7 +140,7 @@ namespace Microsoft.AspNet.Mvc.Formatters } 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)) { diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonResult.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonResult.cs index 861335d8e6..8770a4de1e 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonResult.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonResult.cs @@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc /// /// Gets or sets the representing the Content-Type header of the response. /// - public MediaTypeHeaderValue ContentType { get; set; } + public string ContentType { get; set; } /// /// Gets or sets the . diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs index 758652a3fe..e17638161d 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs @@ -12,6 +12,7 @@ using System.Xml; using Microsoft.AspNet.Mvc.Formatters.Xml; using Microsoft.AspNet.Mvc.Formatters.Xml.Internal; using Microsoft.AspNet.Mvc.Internal; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters { @@ -187,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.Formatters } 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. var value = context.Object; diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs index bf088222f6..414de4d775 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs @@ -12,6 +12,7 @@ using System.Xml.Serialization; using Microsoft.AspNet.Mvc.Formatters.Xml; using Microsoft.AspNet.Mvc.Formatters.Xml.Internal; using Microsoft.AspNet.Mvc.Internal; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters { @@ -164,7 +165,7 @@ namespace Microsoft.AspNet.Mvc.Formatters var response = context.HttpContext.Response; 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. var value = context.Object; diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/PartialViewResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/PartialViewResult.cs index 3baf4a0505..b512bcd5c4 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/PartialViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/PartialViewResult.cs @@ -46,9 +46,9 @@ namespace Microsoft.AspNet.Mvc public IViewEngine ViewEngine { get; set; } /// - /// Gets or sets the representing the Content-Type header of the response. + /// Gets or sets the Content-Type header for the response. /// - public MediaTypeHeaderValue ContentType { get; set; } + public string ContentType { get; set; } /// public override async Task ExecuteResultAsync(ActionContext context) diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponentResult.cs index de9cba9191..5a2a358cf8 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponentResult.cs @@ -16,7 +16,6 @@ using Microsoft.AspNet.Mvc.ViewFeatures.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc { @@ -31,9 +30,9 @@ namespace Microsoft.AspNet.Mvc public object Arguments { get; set; } /// - /// Gets or sets the representing the Content-Type header of the response. + /// Gets or sets the Content-Type header for the response. /// - public MediaTypeHeaderValue ContentType { get; set; } + public string ContentType { get; set; } /// /// Gets or sets the HTTP status code. diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs index f43c6587db..5ff35adc6f 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs @@ -23,12 +23,9 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures public class ViewExecutor { /// - /// The default content-type header value for views, text/html; charset=utf8. + /// The default content-type header value for views, text/html; charset=utf-8. /// - public static readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("text/html") - { - Encoding = Encoding.UTF8 - }.CopyAsReadOnly(); + public static readonly string DefaultContentType = "text/html; charset=utf-8"; /// /// Creates a new . @@ -122,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, - MediaTypeHeaderValue contentType, + string contentType, int? statusCode) { if (actionContext == null) diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs index ce6f04033f..3997081da5 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs @@ -51,9 +51,9 @@ namespace Microsoft.AspNet.Mvc public IViewEngine ViewEngine { get; set; } /// - /// Gets or sets the representing the Content-Type header of the response. + /// Gets or sets the Content-Type header for the response. /// - public MediaTypeHeaderValue ContentType { get; set; } + public string ContentType { get; set; } /// public override async Task ExecuteResultAsync(ActionContext context) diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs index 9daa158e98..7d70486308 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs @@ -409,10 +409,7 @@ namespace System.Web.Http } var result = new JsonResult(content, serializerSettings); - result.ContentType = new MediaTypeHeaderValue("application/json") - { - Encoding = encoding - }; + result.ContentType = $"application/json; charset={encoding.WebName}"; return result; } diff --git a/test/Microsoft.AspNet.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNet.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index ca8d57080c..5d22c25bd4 100644 --- a/test/Microsoft.AspNet.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -18,6 +18,7 @@ using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Constraints; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -1385,14 +1386,14 @@ namespace Microsoft.AspNet.Mvc.Description { public ContentTypeAttribute(string mediaType) { - ContentTypes.Add(MediaTypeHeaderValue.Parse(mediaType)); + ContentTypes.Add(mediaType); } - public List ContentTypes { get; } = new List(); + public MediaTypeCollection ContentTypes { get; } = new MediaTypeCollection(); public Type Type { get; set; } - public void SetContentTypes(IList contentTypes) + public void SetContentTypes(MediaTypeCollection contentTypes) { contentTypes.Clear(); foreach (var contentType in ContentTypes) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ContentResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ContentResultTest.cs index 59614401e8..113cbd5c75 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ContentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ContentResultTest.cs @@ -9,6 +9,8 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.Formatters; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Mvc.ViewComponents; using Microsoft.AspNet.Routing; using Microsoft.Extensions.DependencyInjection; @@ -32,7 +34,7 @@ namespace Microsoft.AspNet.Mvc ContentType = new MediaTypeHeaderValue("text/plain") { Encoding = Encoding.UTF7 - } + }.ToString() }; var httpContext = GetHttpContext(); var actionContext = GetActionContext(httpContext); @@ -41,7 +43,7 @@ namespace Microsoft.AspNet.Mvc await contentResult.ExecuteResultAsync(actionContext); // Assert - Assert.Equal("text/plain; charset=utf-7", httpContext.Response.ContentType); + MediaTypeAssert.Equal("text/plain; charset=utf-7", httpContext.Response.ContentType); } [Fact] @@ -55,7 +57,7 @@ namespace Microsoft.AspNet.Mvc ContentType = new MediaTypeHeaderValue("text/plain") { Encoding = Encoding.ASCII - } + }.ToString() }; var httpContext = GetHttpContext(); httpContext.Features.Set(new TestBufferingFeature()); @@ -153,7 +155,7 @@ namespace Microsoft.AspNet.Mvc var contentResult = new ContentResult { Content = content, - ContentType = contentType + ContentType = contentType?.ToString() }; var httpContext = GetHttpContext(); var memoryStream = new MemoryStream(); @@ -165,7 +167,8 @@ namespace Microsoft.AspNet.Mvc await contentResult.ExecuteResultAsync(actionContext); // Assert - Assert.Equal(expectedContentType, httpContext.Response.ContentType); + var finalResponseContentType = httpContext.Response.ContentType; + Assert.Equal(expectedContentType, finalResponseContentType); Assert.Equal(expectedContentData, memoryStream.ToArray()); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerBaseTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerBaseTest.cs index 4b2fa08023..c00f95e894 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerBaseTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerBaseTest.cs @@ -10,8 +10,10 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; +using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Validation; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Testing; using Microsoft.Net.Http.Headers; @@ -975,7 +977,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Assert Assert.IsType(actualContentResult); Assert.Equal("TestContent", actualContentResult.Content); - Assert.Null(actualContentResult.ContentType.Encoding); + Assert.Null(MediaTypeEncoding.GetEncoding(actualContentResult.ContentType)); Assert.Equal("text/plain", actualContentResult.ContentType.ToString()); } @@ -991,7 +993,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Assert Assert.IsType(actualContentResult); 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()); } @@ -1024,7 +1026,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test Assert.NotNull(contentResult.ContentType); Assert.Equal(contentType, contentResult.ContentType.ToString()); // The default encoding of ContentResult is used when this result is executed. - Assert.Null(contentResult.ContentType.Encoding); + Assert.Null(MediaTypeEncoding.GetEncoding(contentResult.ContentType)); } [Fact] @@ -1032,13 +1034,13 @@ namespace Microsoft.AspNet.Mvc.Core.Test { // Arrange 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 var contentResult = (ContentResult)contentController.Content_WithEncodingInCharset_AndEncodingParameter(); // Assert - Assert.Equal(contentType, contentResult.ContentType); + MediaTypeAssert.Equal(contentType, contentResult.ContentType); } [Fact] @@ -1046,7 +1048,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { // Arrange 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 var contentResult = (ContentResult)contentController.Content_WithEncodingInCharset(); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/FileContentResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/FileContentResultTest.cs index 3895f81cd8..487b6a5a7a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/FileContentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/FileContentResultTest.cs @@ -7,11 +7,11 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; -using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNet.Mvc @@ -37,14 +37,14 @@ namespace Microsoft.AspNet.Mvc // Arrange var fileContents = new byte[0]; var contentType = "text/plain; charset=us-ascii; p1=p1-value"; - var expectedMediaType = MediaTypeHeaderValue.Parse(contentType); + var expectedMediaType = contentType; // Act var result = new FileContentResult(fileContents, contentType); // Assert Assert.Same(fileContents, result.FileContents); - Assert.Equal(expectedMediaType, result.ContentType); + MediaTypeAssert.Equal(expectedMediaType, result.ContentType); } [Fact] @@ -83,7 +83,7 @@ namespace Microsoft.AspNet.Mvc var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); - var result = new FileContentResult(buffer, MediaTypeHeaderValue.Parse(expectedContentType)); + var result = new FileContentResult(buffer, expectedContentType); // Act await result.ExecuteResultAsync(context); @@ -108,7 +108,7 @@ namespace Microsoft.AspNet.Mvc var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); - var result = new FileContentResult(buffer, MediaTypeHeaderValue.Parse(expectedContentType)); + var result = new FileContentResult(buffer, expectedContentType); // Act await result.ExecuteResultAsync(context); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/FileResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/FileResultTest.cs index 75f9c4214c..a9aa1be90d 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/FileResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/FileResultTest.cs @@ -274,12 +274,12 @@ namespace Microsoft.AspNet.Mvc public bool WasWriteFileCalled; public EmptyFileResult() - : base(MediaTypeHeaderValue.Parse("application/octet")) + : base("application/octet") { } public EmptyFileResult(string contentType) - : base(MediaTypeHeaderValue.Parse(contentType)) + : base(contentType) { } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/FileStreamResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/FileStreamResultTest.cs index 3e765b8d75..341a46bb82 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/FileStreamResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/FileStreamResultTest.cs @@ -10,11 +10,11 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; -using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -41,14 +41,14 @@ namespace Microsoft.AspNet.Mvc // Arrange var stream = Stream.Null; var contentType = "text/plain; charset=us-ascii; p1=p1-value"; - var expectedMediaType = MediaTypeHeaderValue.Parse(contentType); + var expectedMediaType = contentType; // Act var result = new FileStreamResult(stream, contentType); // Assert Assert.Equal(stream, result.FileStream); - Assert.Equal(expectedMediaType, result.ContentType); + MediaTypeAssert.Equal(expectedMediaType, result.ContentType); } [Fact] @@ -130,7 +130,7 @@ namespace Microsoft.AspNet.Mvc var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); - var result = new FileStreamResult(originalStream, MediaTypeHeaderValue.Parse(expectedContentType)); + var result = new FileStreamResult(originalStream, expectedContentType); // Act await result.ExecuteResultAsync(actionContext); @@ -157,7 +157,7 @@ namespace Microsoft.AspNet.Mvc httpContext.Response.Body = outStream; var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); - var result = new FileStreamResult(originalStream, MediaTypeHeaderValue.Parse(expectedContentType)); + var result = new FileStreamResult(originalStream, expectedContentType); // Act await result.ExecuteResultAsync(actionContext); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatFilterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatFilterTest.cs index ae39bbb995..c0c0a19717 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatFilterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatFilterTest.cs @@ -4,9 +4,10 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.Filters; -using Microsoft.AspNet.Mvc.Infrastructure; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Routing; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -32,7 +33,7 @@ namespace Microsoft.AspNet.Mvc.Formatters string contentType) { // Arrange - var mediaType = MediaTypeHeaderValue.Parse("application/json"); + var mediaType = new StringSegment("application/json"); var mockObjects = new MockObjects(format, place); var resultExecutingContext = mockObjects.CreateResultExecutingContext(); @@ -52,7 +53,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // Assert var objectResult = Assert.IsType(resultExecutingContext.Result); Assert.Equal(1, objectResult.ContentTypes.Count); - AssertMediaTypesEqual(mediaType, objectResult.ContentTypes[0]); + MediaTypeAssert.Equal(mediaType, objectResult.ContentTypes[0]); } [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 // Arrange - var mediaType = MediaTypeHeaderValue.Parse("application/json"); + var mediaType = new StringSegment("application/json"); var mockObjects = new MockObjects("json", FormatSource.RouteData); var httpContext = new Mock(); httpContext.Setup(c => c.Response).Returns(new Mock().Object); @@ -95,7 +96,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // Assert var objectResult = Assert.IsType(resultExecutingContext.Result); Assert.Equal(1, objectResult.ContentTypes.Count); - AssertMediaTypesEqual(mediaType, objectResult.ContentTypes[0]); + MediaTypeAssert.Equal(mediaType, objectResult.ContentTypes[0]); } [Theory] @@ -108,7 +109,7 @@ namespace Microsoft.AspNet.Mvc.Formatters string contentType) { // Arrange - var mediaType = MediaTypeHeaderValue.Parse(contentType); + var mediaType = new StringSegment(contentType); var mockObjects = new MockObjects(format, place); var resultExecutingContext = mockObjects.CreateResultExecutingContext(); @@ -127,7 +128,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // Assert var objectResult = Assert.IsType(resultExecutingContext.Result); Assert.Equal(1, objectResult.ContentTypes.Count); - AssertMediaTypesEqual(mediaType, objectResult.ContentTypes[0]); + MediaTypeAssert.Equal(mediaType, objectResult.ContentTypes[0]); } [Theory] @@ -303,7 +304,7 @@ namespace Microsoft.AspNet.Mvc.Formatters public void FormatFilter_ExplicitContentType_SetOnObjectResult_TakesPrecedence() { // Arrange - var mediaType = MediaTypeHeaderValue.Parse("application/foo"); + var mediaType = new StringSegment("application/foo"); var mockObjects = new MockObjects("json", FormatSource.QueryData); var httpContext = new Mock(); httpContext.Setup(c => c.Response).Returns(new Mock().Object); @@ -330,7 +331,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // Assert var result = Assert.IsType(resultExecutingContext.Result); Assert.Equal(1, result.ContentTypes.Count); - AssertMediaTypesEqual(mediaType, result.ContentTypes[0]); + MediaTypeAssert.Equal(mediaType, result.ContentTypes[0]); } [Fact] @@ -366,21 +367,6 @@ namespace Microsoft.AspNet.Mvc.Formatters 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 { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs index db35cbc6bf..47a37ef7d7 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNet.Mvc.TestCommon; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Xunit; @@ -10,33 +12,44 @@ namespace Microsoft.AspNet.Mvc.Formatters { public class FormatterMappingsTest { + [Theory] + [InlineData(null)] + [InlineData("")] + public void FormatterMappings_GetMediaTypeMappingForFormat_ThrowsForInvalidFormats(string format) + { + // Arrange + var options = new FormatterMappings(); + + // Act & Assert + Assert.Throws("format", () => options.GetMediaTypeMappingForFormat(format)); + } + [Theory] [InlineData(".xml", "application/xml", "xml")] [InlineData("json", "application/json", "JSON")] [InlineData(".foo", "text/foo", "Foo")] [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) { // Arrange - var mediaType = MediaTypeHeaderValue.Parse(contentType); var options = new FormatterMappings(); - options.SetMediaTypeMappingForFormat(setFormat, mediaType); + options.SetMediaTypeMappingForFormat(setFormat, MediaTypeHeaderValue.Parse(contentType)); // Act var returnMediaType = options.GetMediaTypeMappingForFormat(getFormat); // Assert - Assert.Equal(mediaType, returnMediaType); + MediaTypeAssert.Equal(contentType, returnMediaType); } - + [Fact] public void FormatterMappings_Invalid_Period() { // Arrange var options = new FormatterMappings(); 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); // Act and assert @@ -70,12 +83,12 @@ namespace Microsoft.AspNet.Mvc.Formatters { // Arrange 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); // Act and assert var exception = Assert.Throws(() => options.SetMediaTypeMappingForFormat( - "star", + "star", MediaTypeHeaderValue.Parse(format))); Assert.Equal(expected, exception.Message); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeComparisonsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeComparisonsTest.cs new file mode 100644 index 0000000000..0fa93d3a2a --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeComparisonsTest.cs @@ -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); + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs index 50526d4012..e5ca0a530a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Xunit; @@ -41,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.Formatters { // Arrange 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( new DefaultHttpContext(), @@ -73,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Formatters declaredType, "Something non null.") { - ContentType = MediaTypeHeaderValue.Parse("text/plain"), + ContentType = new StringSegment("text/plain"), }; var formatter = new HttpNoContentOutputFormatter(); @@ -101,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.Formatters typeof(string), value) { - ContentType = MediaTypeHeaderValue.Parse("text/plain"), + ContentType = new StringSegment("text/plain"), }; var formatter = new HttpNoContentOutputFormatter() diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs index 5954a183a1..8a4aebb122 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs @@ -7,8 +7,10 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Moq; +using Moq.Protected; using Xunit; namespace Microsoft.AspNet.Mvc.Formatters @@ -59,7 +61,7 @@ namespace Microsoft.AspNet.Mvc.Formatters typeof(string), "someValue") { - ContentType = MediaTypeHeaderValue.Parse(httpRequest.Headers[HeaderNames.Accept]), + ContentType = new StringSegment(httpRequest.Headers[HeaderNames.Accept]), }; // Act @@ -69,17 +71,47 @@ namespace Microsoft.AspNet.Mvc.Formatters 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(); + + formatter + .Setup(f => f.SelectCharacterEncoding(It.IsAny())) + .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] public void WriteResponseContentHeaders_NoSupportedEncodings_NoEncodingIsSet() { // Arrange var formatter = new TestOutputFormatter(); - var testContentType = MediaTypeHeaderValue.Parse("text/json"); + var testContentType = new StringSegment("text/json"); formatter.SupportedEncodings.Clear(); formatter.SupportedMediaTypes.Clear(); - formatter.SupportedMediaTypes.Add(testContentType); + formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json")); var context = new OutputFormatterWriteContext( new DefaultHttpContext(), @@ -94,11 +126,11 @@ namespace Microsoft.AspNet.Mvc.Formatters formatter.WriteResponseHeaders(context); // Assert - Assert.Null(context.ContentType.Encoding); + Assert.Null(MediaTypeHeaderValue.Parse(context.ContentType.Value).Encoding); Assert.Equal(testContentType, context.ContentType); // 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] @@ -154,7 +186,9 @@ namespace Microsoft.AspNet.Mvc.Formatters formatter.SupportedTypes.Add(typeof(int)); // Act - var contentTypes = formatter.GetSupportedContentTypes(contentType: null, objectType: typeof(string)); + var contentTypes = formatter.GetSupportedContentTypes( + contentType: null, + objectType: typeof(string)); // Assert Assert.Null(contentTypes); @@ -174,7 +208,7 @@ namespace Microsoft.AspNet.Mvc.Formatters typeof(string), "Hello, world!") { - ContentType = formatter.SupportedMediaTypes[0], + ContentType = new StringSegment(formatter.SupportedMediaTypes[0].ToString()), }; // Act @@ -194,7 +228,9 @@ namespace Microsoft.AspNet.Mvc.Formatters formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml")); // Act - var contentTypes = formatter.GetSupportedContentTypes(contentType: null, objectType: typeof(string)); + var contentTypes = formatter.GetSupportedContentTypes( + contentType: null, + objectType: typeof(string)); // Assert Assert.Equal(2, contentTypes.Count); @@ -214,7 +250,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // Act var contentTypes = formatter.GetSupportedContentTypes( - MediaTypeHeaderValue.Parse("application/*"), + "application/*", typeof(int)); // Assert @@ -234,7 +270,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // Act var contentTypes = formatter.GetSupportedContentTypes( - MediaTypeHeaderValue.Parse("application/xml"), + "application/xml", typeof(int)); // Assert @@ -251,7 +287,9 @@ namespace Microsoft.AspNet.Mvc.Formatters formatter.SupportedMediaTypes.Clear(); // Act - var contentTypes = formatter.GetSupportedContentTypes(contentType: null, objectType: typeof(int)); + var contentTypes = formatter.GetSupportedContentTypes( + contentType: null, + objectType: typeof(int)); // Assert Assert.Null(contentTypes); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs index 2a4598b4df..b654641c94 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Internal; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Xunit; @@ -21,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Formatters { // Arrange var formatter = new StreamOutputFormatter(); - var contentTypeHeader = contentType == null ? null : new MediaTypeHeaderValue(contentType); + var contentTypeHeader = new StringSegment(contentType); var context = new OutputFormatterWriteContext( new DefaultHttpContext(), @@ -46,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Formatters { // Arrange 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( new DefaultHttpContext(), diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs index e8222d6c5d..620ac53286 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs @@ -7,7 +7,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; -using Microsoft.Net.Http.Headers; +using Microsoft.Extensions.Primitives; using Moq; using Xunit; @@ -35,9 +35,9 @@ namespace Microsoft.AspNet.Mvc.Formatters bool expectedCanWriteResult) { // Arrange - var expectedContentType = expectedCanWriteResult ? - MediaTypeHeaderValue.Parse("text/plain") : - MediaTypeHeaderValue.Parse("application/json"); + var expectedContentType = expectedCanWriteResult ? + new StringSegment("text/plain") : + new StringSegment("application/json"); var formatter = new StringOutputFormatter(); var type = useDeclaredTypeAsString ? typeof(string) : typeof(object); @@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Formatters new TestHttpResponseStreamWriterFactory().CreateWriter, type, value); - context.ContentType = MediaTypeHeaderValue.Parse("application/json"); + context.ContentType = new StringSegment("application/json"); // Act var result = formatter.CanWriteResult(context); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs index 0565b37f67..f00a048cb8 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs @@ -9,10 +9,12 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Formatters; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Xunit; @@ -43,12 +45,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Act var formatter = executor.SelectFormatter( context, - new[] { new MediaTypeHeaderValue("application/json") }, + new MediaTypeCollection { "application/json" }, formatters); // Assert 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 @@ -72,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Assert Assert.IsType(executor.SelectedOutputFormatter); - Assert.Equal("text/plain; charset=utf-8", httpContext.Response.ContentType); + MediaTypeAssert.Equal("text/plain; charset=utf-8", httpContext.Response.ContentType); } [Fact] @@ -98,12 +100,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Act var formatter = executor.SelectFormatter( context, - new[] { new MediaTypeHeaderValue("application/json") }, + new MediaTypeCollection { "application/json" }, formatters); // Assert Assert.Same(formatters[1], formatter); - Assert.Equal(new MediaTypeHeaderValue("application/json"), context.ContentType); + Assert.Equal(new StringSegment("application/json"), context.ContentType); } [Fact] @@ -149,7 +151,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Act var formatter = executor.SelectFormatter( context, - new[] { new MediaTypeHeaderValue("application/json") }, + new MediaTypeCollection { "application/json" }, formatters); // Assert @@ -176,18 +178,18 @@ namespace Microsoft.AspNet.Mvc.Infrastructure } // ObjectResult.ContentTypes, Accept header, expected content type - public static TheoryData ContentTypes + public static TheoryData ContentTypes { get { - var contentTypes = new string[] + var contentTypes = new MediaTypeCollection { "text/plain", "text/xml", "application/json", }; - return new TheoryData() + return new TheoryData() { // Empty accept header, should select based on ObjectResult.ContentTypes. { contentTypes, "", "application/json" }, @@ -216,7 +218,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure [Theory] [MemberData(nameof(ContentTypes))] public void SelectFormatter_WithMultipleProvidedContentTypes_DoesConneg( - IEnumerable contentTypes, + MediaTypeCollection contentTypes, string acceptHeader, string expectedContentType) { @@ -240,12 +242,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Act var formatter = executor.SelectFormatter( context, - contentTypes.Select(contentType => MediaTypeHeaderValue.Parse(contentType)).ToList(), + contentTypes, formatters); // Assert Assert.Same(formatters[1], formatter); - Assert.Equal(new MediaTypeHeaderValue(expectedContentType), context.ContentType); + Assert.Equal(new StringSegment(expectedContentType), context.ContentType); } [Fact] @@ -270,12 +272,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Act var formatter = executor.SelectFormatter( context, - new List(), + new MediaTypeCollection(), formatters); // Assert 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); } @@ -297,17 +299,17 @@ namespace Microsoft.AspNet.Mvc.Infrastructure objectType: null, @object: null); - context.HttpContext.Request.Headers[HeaderNames.Accept] = "text/custom, application/custom"; + context.HttpContext.Request.Headers[HeaderNames.Accept] = "text/custom,application/custom"; // Act var formatter = executor.SelectFormatter( context, - new MediaTypeHeaderValue[] { }, + new MediaTypeCollection { }, formatters); // Assert 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); } @@ -373,9 +375,14 @@ namespace Microsoft.AspNet.Mvc.Infrastructure { // Arrange var result = new ObjectResult("input"); - result.ContentTypes = contentTypes - .Select(contentType => MediaTypeHeaderValue.Parse(contentType)) - .ToList(); + + var mediaTypes = new MediaTypeCollection(); + foreach (var contentType in contentTypes) + { + mediaTypes.Add(contentType); + } + + result.ContentTypes = mediaTypes; var executor = CreateExecutor(); @@ -395,7 +402,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Chrome & Opera [InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "application/json; charset=utf-8")] // 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 [InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "application/json; charset=utf-8")] // Misc @@ -432,7 +439,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Chrome & Opera [InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "application/xml; charset=utf-8")] // 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 [InlineData("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "application/xml; charset=utf-8")] // Misc @@ -462,7 +469,8 @@ namespace Microsoft.AspNet.Mvc.Infrastructure await executor.ExecuteAsync(actionContext, result); // 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() @@ -572,7 +580,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure new public IOutputFormatter SelectFormatter( OutputFormatterWriteContext formatterContext, - IList contentTypes, + MediaTypeCollection contentTypes, IList formatters) { return base.SelectFormatter(formatterContext, contentTypes, formatters); @@ -593,7 +601,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure protected override IOutputFormatter SelectFormatter( OutputFormatterWriteContext formatterContext, - IList contentTypes, + MediaTypeCollection contentTypes, IList formatters) { SelectedOutputFormatter = base.SelectFormatter(formatterContext, contentTypes, formatters); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs index ebfe90f163..a7c69f3358 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text; +using Microsoft.AspNet.Mvc.Formatters; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.Net.Http.Headers; using Xunit; @@ -102,13 +104,13 @@ namespace Microsoft.AspNet.Mvc.Internal string expectedContentType) { // Arrange - var defaultContentType = MediaTypeHeaderValue.Parse("text/default; p1=p1-value; charset=utf-8"); + var defaultContentType = "text/default; p1=p1-value; charset=utf-8"; // Act string resolvedContentType = null; Encoding resolvedContentTypeEncoding = null; ResponseContentTypeHelper.ResolveContentTypeAndEncoding( - contentType, + contentType?.ToString(), responseContentType, defaultContentType, out resolvedContentType, @@ -123,7 +125,7 @@ namespace Microsoft.AspNet.Mvc.Internal { // Arrange var expectedContentType = "invalid-content-type"; - var defaultContentType = MediaTypeHeaderValue.Parse("text/plain; charset=utf-8"); + var defaultContentType = "text/plain; charset=utf-8"; // Act string resolvedContentType = null; @@ -136,6 +138,7 @@ namespace Microsoft.AspNet.Mvc.Internal out resolvedContentTypeEncoding); // Assert + Assert.Equal(expectedContentType, resolvedContentType); Assert.Equal(Encoding.UTF8, resolvedContentTypeEncoding); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/MediaTypeCollectionTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/MediaTypeCollectionTest.cs new file mode 100644 index 0000000000..663f909bd0 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/MediaTypeCollectionTest.cs @@ -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); + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/PhysicalFileResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/PhysicalFileResultTest.cs index 00884f2eb3..b18039feb4 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/PhysicalFileResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/PhysicalFileResultTest.cs @@ -10,11 +10,11 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; -using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -41,14 +41,14 @@ namespace Microsoft.AspNet.Mvc // Arrange var path = Path.GetFullPath("helllo.txt"); var contentType = "text/plain; charset=us-ascii; p1=p1-value"; - var expectedMediaType = MediaTypeHeaderValue.Parse(contentType); + var expectedMediaType = contentType; // Act var result = new PhysicalFileResult(path, contentType); // Assert Assert.Equal(path, result.FileName); - Assert.Equal(expectedMediaType, result.ContentType); + MediaTypeAssert.Equal(expectedMediaType, result.ContentType); } [Fact] @@ -99,7 +99,7 @@ namespace Microsoft.AspNet.Mvc // Arrange var expectedContentType = "text/foo; charset=us-ascii"; 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 }; @@ -205,11 +205,6 @@ namespace Microsoft.AspNet.Mvc { } - public TestPhysicalFileResult(string filePath, MediaTypeHeaderValue contentType) - : base(filePath, contentType) - { - } - public bool IsAscii { get; set; } = false; protected override Stream GetFileStream(string path) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ProducesAttributeTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ProducesAttributeTests.cs index d70fa2cae1..535e16d186 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ProducesAttributeTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ProducesAttributeTests.cs @@ -8,7 +8,9 @@ using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Formatters; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Routing; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -21,8 +23,8 @@ namespace Microsoft.AspNet.Mvc.Test public async Task ProducesAttribute_SetsContentType() { // Arrange - var mediaType1 = MediaTypeHeaderValue.Parse("application/json"); - var mediaType2 = MediaTypeHeaderValue.Parse("text/json;charset=utf-8"); + var mediaType1 = new StringSegment("application/json"); + var mediaType2 = new StringSegment("text/json;charset=utf-8"); var producesContentAttribute = new ProducesAttribute("application/json", "text/json;charset=utf-8"); var resultExecutingContext = CreateResultExecutingContext(new IFilterMetadata[] { producesContentAttribute }); var next = new ResultExecutionDelegate( @@ -34,8 +36,8 @@ namespace Microsoft.AspNet.Mvc.Test // Assert var objectResult = resultExecutingContext.Result as ObjectResult; Assert.Equal(2, objectResult.ContentTypes.Count); - ValidateMediaType(mediaType1, objectResult.ContentTypes[0]); - ValidateMediaType(mediaType2, objectResult.ContentTypes[1]); + MediaTypeAssert.Equal(mediaType1, objectResult.ContentTypes[0]); + MediaTypeAssert.Equal(mediaType2, objectResult.ContentTypes[1]); } [Fact] @@ -153,20 +155,6 @@ namespace Microsoft.AspNet.Mvc.Test 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) { return new ResultExecutedContext(context, context.Filters, context.Result, context.Controller); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/VirtualFileResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/VirtualFileResultTest.cs index 3d6c5e3a4c..28a12761e3 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/VirtualFileResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/VirtualFileResultTest.cs @@ -11,11 +11,11 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; -using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -42,14 +42,14 @@ namespace Microsoft.AspNet.Mvc // Arrange var path = Path.GetFullPath("helllo.txt"); var contentType = "text/plain; charset=us-ascii; p1=p1-value"; - var expectedMediaType = MediaTypeHeaderValue.Parse(contentType); + var expectedMediaType = contentType; // Act var result = new VirtualFileResult(path, contentType); // Assert Assert.Equal(path, result.FileName); - Assert.Equal(expectedMediaType, result.ContentType); + MediaTypeAssert.Equal(expectedMediaType, result.ContentType); } [Fact] @@ -137,7 +137,7 @@ namespace Microsoft.AspNet.Mvc // Arrange var expectedContentType = "text/foo; charset=us-ascii"; var result = new TestVirtualFileResult( - "FilePathResultTestFile_ASCII.txt", MediaTypeHeaderValue.Parse(expectedContentType)) + "FilePathResultTestFile_ASCII.txt", expectedContentType) { FileProvider = GetFileProvider("FilePathResultTestFile_ASCII.txt"), 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; protected override Stream GetFileStream(IFileInfo fileInfo) diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/Infrastructure/JsonResultExecutorTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/Infrastructure/JsonResultExecutorTest.cs index fbd79009ce..92cb8c551d 100644 --- a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/Infrastructure/JsonResultExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/Infrastructure/JsonResultExecutorTest.cs @@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure var context = GetActionContext(); var result = new JsonResult(new { foo = "abcd" }); - result.ContentType = new MediaTypeHeaderValue("text/json"); + result.ContentType = "text/json"; var executor = CreateExcutor(); // Act @@ -74,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure result.ContentType = new MediaTypeHeaderValue("text/json") { Encoding = Encoding.ASCII - }; + }.ToString(); var executor = CreateExcutor(); // Act diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs index de891c2af5..f5f96cf73c 100644 --- a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs @@ -14,6 +14,7 @@ using Microsoft.AspNet.Routing; using Microsoft.AspNet.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Moq; using Newtonsoft.Json; @@ -262,7 +263,7 @@ namespace Microsoft.AspNet.Mvc.Formatters typeof(string), content) { - ContentType = mediaType, + ContentType = new StringSegment(mediaType.ToString()), }; // Act @@ -313,7 +314,7 @@ namespace Microsoft.AspNet.Mvc.Formatters outputType, outputValue) { - ContentType = mediaTypeHeaderValue, + ContentType = new StringSegment(contentType), }; } diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs index 7bcf82d570..294ae93152 100644 --- a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs @@ -12,6 +12,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Formatters.Xml.Internal; using Microsoft.AspNet.Testing.xunit; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -123,7 +124,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml var formatter = new TestXmlDataContractSerializerOutputFormatter(); var context = GetOutputFormatterContext(input, typeof(DummyClass)); - context.ContentType = MediaTypeHeaderValue.Parse("application/xml"); + context.ContentType = new StringSegment("application/xml"); // Act formatter.CanWriteResult(context); @@ -382,7 +383,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml // Arrange var formatter = new XmlDataContractSerializerOutputFormatter(); var outputFormatterContext = GetOutputFormatterContext(input, declaredType); - outputFormatterContext.ContentType = MediaTypeHeaderValue.Parse("application/xml"); + outputFormatterContext.ContentType = new StringSegment("application/xml"); // Act var result = formatter.CanWriteResult(outputFormatterContext); @@ -412,7 +413,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml // Act var result = formatter.GetSupportedContentTypes( - MediaTypeHeaderValue.Parse("application/xml"), + "application/xml", type); // Assert diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs index 8aec54e7e3..b876b565d8 100644 --- a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs @@ -11,6 +11,7 @@ using System.Xml.Serialization; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Formatters.Xml.Internal; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -76,7 +77,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml var formatter = new TestXmlSerializerOutputFormatter(); var context = GetOutputFormatterContext(input, typeof(DummyClass)); - context.ContentType = MediaTypeHeaderValue.Parse("application/xml"); + context.ContentType = new StringSegment("application/xml"); // Act formatter.CanWriteResult(context); @@ -300,7 +301,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml // Arrange var formatter = new XmlSerializerOutputFormatter(); var outputFormatterContext = GetOutputFormatterContext(input, declaredType); - outputFormatterContext.ContentType = MediaTypeHeaderValue.Parse("application/xml"); + outputFormatterContext.ContentType = new StringSegment("application/xml"); // Act var result = formatter.CanWriteResult(outputFormatterContext); @@ -342,7 +343,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml var formatter = new XmlSerializerOutputFormatter(); // Act - var result = formatter.GetSupportedContentTypes(MediaTypeHeaderValue.Parse("application/xml"), type); + var result = formatter.GetSupportedContentTypes("application/xml", type); // Assert if (expectedOutput != null) diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/MediaTypeAssert.cs b/test/Microsoft.AspNet.Mvc.TestCommon/MediaTypeAssert.cs new file mode 100644 index 0000000000..e44bc0179b --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.TestCommon/MediaTypeAssert.cs @@ -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()); + } + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs index 1c56d6055f..977c997a5e 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs @@ -8,6 +8,7 @@ using System.Text; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Controllers; +using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Routing; using Moq; using Newtonsoft.Json; @@ -90,7 +91,7 @@ namespace Microsoft.AspNet.Mvc var contentResult = Assert.IsType(result); Assert.Equal(content, contentResult.Content); Assert.Equal("text/asp; charset=us-ascii", contentResult.ContentType.ToString()); - Assert.Equal(encoding, contentResult.ContentType.Encoding); + Assert.Equal(encoding, MediaTypeEncoding.GetEncoding(contentResult.ContentType)); } [Theory] diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs index 93abe8738f..96a9ff892f 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs @@ -12,9 +12,11 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Mvc.ViewComponents; using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures.Buffer; @@ -367,26 +369,26 @@ namespace Microsoft.AspNet.Mvc Assert.Equal(404, actionContext.HttpContext.Response.StatusCode); } - public static TheoryData ViewComponentResultContentTypeData + public static TheoryData ViewComponentResultContentTypeData { get { - return new TheoryData + return new TheoryData { { null, "text/html; charset=utf-8" }, { - new MediaTypeHeaderValue("text/foo"), + "text/foo", "text/foo" }, { - MediaTypeHeaderValue.Parse("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" } }; @@ -396,8 +398,8 @@ namespace Microsoft.AspNet.Mvc [Theory] [MemberData(nameof(ViewComponentResultContentTypeData))] public async Task ViewComponentResult_SetsContentTypeHeader( - MediaTypeHeaderValue contentType, - string expectedContentTypeHeaderValue) + string contentType, + string expectedContentType) { // Arrange var descriptor = new ViewComponentDescriptor() @@ -424,13 +426,14 @@ namespace Microsoft.AspNet.Mvc await viewComponentResult.ExecuteResultAsync(actionContext); // 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. // Since we do not have access to the new instance created within the view executor, // check if at least the content is the same. var contentTypeAfterViewResultExecution = contentType?.ToString(); - Assert.Equal(contentTypeBeforeViewResultExecution, contentTypeAfterViewResultExecution); + MediaTypeAssert.Equal(contentTypeBeforeViewResultExecution, contentTypeAfterViewResultExecution); } [Fact] @@ -454,7 +457,7 @@ namespace Microsoft.AspNet.Mvc { Arguments = new { name = "World!" }, ViewComponentName = "Text", - ContentType = new MediaTypeHeaderValue("text/html") { Encoding = Encoding.UTF8 }, + ContentType = new MediaTypeHeaderValue("text/html") { Encoding = Encoding.UTF8 }.ToString(), TempData = _tempDataDictionary, }; diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs index d88a768cd1..a07a8cfe7a 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Routing; @@ -262,7 +263,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var context = GetActionContext(); var executor = GetViewExecutor(); - var contentType = MediaTypeHeaderValue.Parse("application/x-my-content-type"); + var contentType = "application/x-my-content-type"; 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. // 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); + Assert.Null(MediaTypeEncoding.GetEncoding(contentType)); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs index b7a09d8e4a..a6c5c47baf 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs @@ -10,8 +10,10 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Routing; using Microsoft.Extensions.DependencyInjection; @@ -115,11 +117,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures view, viewData, Mock.Of(), - contentType, + contentType?.ToString(), statusCode: null); // Assert - Assert.Equal(expectedContentType, context.Response.ContentType); + MediaTypeAssert.Equal(expectedContentType, context.Response.ContentType); Assert.Equal("abcd", Encoding.UTF8.GetString(memoryStream.ToArray())); } diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs index 63b0242203..8b0ff03888 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Routing; @@ -258,7 +259,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var context = GetActionContext(); var executor = GetViewExecutor(); - var contentType = MediaTypeHeaderValue.Parse("application/x-my-content-type"); + var contentType = "application/x-my-content-type"; var viewResult = new ViewResult { @@ -273,11 +274,6 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures // Assert 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] diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs index c2fe8834f1..f5aa58af41 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs @@ -10,6 +10,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Controllers; +using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Routing; using Newtonsoft.Json; @@ -264,7 +265,7 @@ namespace System.Web.Http var jsonResult = Assert.IsType(result); Assert.Same(product, jsonResult.Value); - Assert.Same(Encoding.UTF8, jsonResult.ContentType.Encoding); + Assert.Same(Encoding.UTF8, MediaTypeEncoding.GetEncoding(jsonResult.ContentType)); } [Fact] diff --git a/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs b/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs index 24b341b12d..1b7f3311de 100644 --- a/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs +++ b/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs @@ -20,7 +20,7 @@ namespace BasicWebSite.Controllers public JsonResult CustomContentType() { var result = new JsonResult(new { Message = "hello" }); - result.ContentType = MediaTypeHeaderValue.Parse("application/message+json"); + result.ContentType = "application/message+json"; return result; } diff --git a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs index 326d32d064..4a1225efbe 100644 --- a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs +++ b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs @@ -38,9 +38,11 @@ namespace BasicWebSite.Formatters builder.AppendLine(); builder.AppendLine("END:VCARD"); + var selectedEncoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8; + await context.HttpContext.Response.WriteAsync( builder.ToString(), - context.ContentType?.Encoding ?? Encoding.UTF8); + selectedEncoding); } } } \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs index ffcc5e2913..875c695db6 100644 --- a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs +++ b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs @@ -41,9 +41,11 @@ namespace BasicWebSite.Formatters builder.AppendLine(); builder.AppendLine("END:VCARD"); + var selectedEncoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8; + await context.HttpContext.Response.WriteAsync( builder.ToString(), - context.ContentType?.Encoding ?? Encoding.UTF8); + selectedEncoding); } } } \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/Filters/RandomNumberFilter.cs b/test/WebSites/FiltersWebSite/Filters/RandomNumberFilter.cs index 4cd83cbfa7..ca52a63b55 100644 --- a/test/WebSites/FiltersWebSite/Filters/RandomNumberFilter.cs +++ b/test/WebSites/FiltersWebSite/Filters/RandomNumberFilter.cs @@ -14,7 +14,7 @@ namespace FiltersWebSite context.Result = new ContentResult() { Content = "4", - ContentType = new MediaTypeHeaderValue("text/plain") + ContentType = "text/plain" }; } diff --git a/test/WebSites/FiltersWebSite/Filters/ShortCircuitActionFilter.cs b/test/WebSites/FiltersWebSite/Filters/ShortCircuitActionFilter.cs index 54a27638e8..dbc2891f79 100644 --- a/test/WebSites/FiltersWebSite/Filters/ShortCircuitActionFilter.cs +++ b/test/WebSites/FiltersWebSite/Filters/ShortCircuitActionFilter.cs @@ -14,7 +14,7 @@ namespace FiltersWebSite context.Result = new ContentResult { Content = "The Action was never executed", - ContentType = new MediaTypeHeaderValue("text/plain") + ContentType = "text/plain" }; } } diff --git a/test/WebSites/FiltersWebSite/Helper/Helpers.cs b/test/WebSites/FiltersWebSite/Helper/Helpers.cs index 897d5088f2..99a8fd830d 100644 --- a/test/WebSites/FiltersWebSite/Helper/Helpers.cs +++ b/test/WebSites/FiltersWebSite/Helper/Helpers.cs @@ -21,7 +21,7 @@ namespace FiltersWebSite return new ContentResult() { Content = content, - ContentType = new MediaTypeHeaderValue("text/plain"), + ContentType = "text/plain", }; } }