Support caching of the Content-Type string used by OutputFormatter

when the encoding is Utf-8.
This commit is contained in:
javiercn 2016-01-04 17:32:34 -08:00 committed by jacalvar
parent 830fd410f5
commit 739f83a978
3 changed files with 119 additions and 33 deletions

View File

@ -69,8 +69,13 @@ namespace Microsoft.AspNet.Mvc.Formatters
public static string ReplaceEncoding(StringSegment mediaType, Encoding encoding)
{
var parsedMediaType = MediaTypeHeaderValue.Parse(mediaType.Value);
parsedMediaType.Encoding = encoding;
if (string.Equals(parsedMediaType.Encoding?.WebName, encoding?.WebName, StringComparison.OrdinalIgnoreCase))
{
return mediaType.Value;
}
parsedMediaType.Encoding = encoding;
return parsedMediaType.ToString();
}

View File

@ -18,6 +18,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
/// </summary>
public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider
{
private IDictionary<string, string> _outputMediaTypeCache;
/// <summary>
/// Initializes a new instance of the <see cref="OutputFormatter"/> class.
/// </summary>
@ -40,6 +42,28 @@ namespace Microsoft.AspNet.Mvc.Formatters
/// </summary>
public MediaTypeCollection SupportedMediaTypes { get; }
private IDictionary<string, string> OutputMediaTypeCache
{
get
{
if (_outputMediaTypeCache == null)
{
var cache = new Dictionary<string, string>();
foreach (var mediaType in SupportedMediaTypes)
{
cache.Add(mediaType, MediaTypeEncoding.ReplaceEncoding(mediaType, Encoding.UTF8));
}
// Safe race condition, worst case scenario we initialize the field multiple times with dictionaries containing
// the same values.
_outputMediaTypeCache = cache;
}
return _outputMediaTypeCache;
}
}
/// <summary>
/// Returns a value indicating whether or not the given type can be written by this serializer.
/// </summary>
@ -244,31 +268,15 @@ namespace Microsoft.AspNet.Mvc.Formatters
/// <returns>A task which can write the response body.</returns>
public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context);
/// <summary>
/// Adds or replaces the charset parameter in a given <paramref name="mediaType"/> with the
/// given <paramref name="encoding"/>.
/// </summary>
/// <param name="mediaType">The <see cref="StringSegment"/> with the media type.</param>
/// <param name="encoding">
/// The <see cref="Encoding"/> to add or replace in the <paramref name="mediaType"/>.
/// </param>
/// <returns>The mediaType with the given encoding.</returns>
protected string GetMediaTypeWithCharset(string mediaType, Encoding encoding)
private string GetMediaTypeWithCharset(string mediaType, Encoding encoding)
{
var mediaTypeEncoding = MediaTypeEncoding.GetEncoding(mediaType);
if (mediaTypeEncoding == encoding)
if (string.Equals(encoding.WebName, Encoding.UTF8.WebName, StringComparison.OrdinalIgnoreCase) &&
OutputMediaTypeCache.ContainsKey(mediaType))
{
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);
return OutputMediaTypeCache[mediaType];
}
return MediaTypeEncoding.ReplaceEncoding(mediaType, encoding);
}
private Encoding MatchAcceptCharacterEncoding(IList<StringWithQualityHeaderValue> acceptCharsetHeaders)
@ -354,15 +362,5 @@ 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}";
}
}
}

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Moq;
@ -101,6 +102,88 @@ namespace Microsoft.AspNet.Mvc.Formatters
Assert.Equal(new StringSegment(expectedContentType), formatterContext.ContentType);
}
[Fact]
public void WriteResponse_GetMediaTypeWithCharsetReturnsMediaTypeFromCache_IfEncodingIsUtf8()
{
// Arrange
var formatter = new TestOutputFormatter();
var formatterContext = new OutputFormatterWriteContext(
new DefaultHttpContext(),
new TestHttpResponseStreamWriterFactory().CreateWriter,
objectType: null,
@object: null)
{
ContentType = new StringSegment("application/json"),
};
formatter.SupportedMediaTypes.Add("application/json");
formatter.SupportedEncodings.Add(Encoding.UTF8);
// Act
formatter.WriteAsync(formatterContext);
var firstContentType = formatterContext.ContentType;
formatterContext.ContentType = new StringSegment("application/json");
formatter.WriteAsync(formatterContext);
var secondContentType = formatterContext.ContentType;
// Assert
Assert.Same(firstContentType.Buffer, secondContentType.Buffer);
}
[Fact]
public void WriteResponse_GetMediaTypeWithCharsetReplacesCharset_IfDifferentThanEncoding()
{
// Arrange
var formatter = new TestOutputFormatter();
var formatterContext = new OutputFormatterWriteContext(
new DefaultHttpContext(),
new TestHttpResponseStreamWriterFactory().CreateWriter,
objectType: null,
@object: null)
{
ContentType = new StringSegment("application/json; charset=utf-7"),
};
formatter.SupportedMediaTypes.Add("application/json");
formatter.SupportedEncodings.Add(Encoding.UTF8);
// Act
formatter.WriteAsync(formatterContext);
// Assert
Assert.Equal(new StringSegment("application/json; charset=utf-8"), formatterContext.ContentType);
}
[Fact]
public void WriteResponse_GetMediaTypeWithCharsetReturnsSameString_IfCharsetEqualToEncoding()
{
// Arrange
var formatter = new TestOutputFormatter();
var contentType = "application/json; charset=utf-16";
var formatterContext = new OutputFormatterWriteContext(
new DefaultHttpContext(),
new TestHttpResponseStreamWriterFactory().CreateWriter,
objectType: null,
@object: null)
{
ContentType = new StringSegment(contentType),
};
formatter.SupportedMediaTypes.Add("application/json");
formatter.SupportedEncodings.Add(Encoding.Unicode);
// Act
formatter.WriteAsync(formatterContext);
// Assert
Assert.Same(contentType, formatterContext.ContentType.Buffer);
}
[Fact]
public void WriteResponseContentHeaders_NoSupportedEncodings_NoEncodingIsSet()
{