[Fixes #4051] Split OutputFormatter into OutputFormatter and TextOutputFormatter
This commit is contained in:
parent
63354e25a8
commit
65858b8d8b
|
|
@ -9,7 +9,7 @@ using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace FormatFilterSample.Web
|
namespace FormatFilterSample.Web
|
||||||
{
|
{
|
||||||
public class CustomFormatter : OutputFormatter
|
public class CustomFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public CustomFormatter(string contentType)
|
public CustomFormatter(string contentType)
|
||||||
{
|
{
|
||||||
|
|
@ -38,7 +38,7 @@ namespace FormatFilterSample.Web
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
var response = context.HttpContext.Response;
|
var response = context.HttpContext.Response;
|
||||||
await response.WriteAsync(context.Object.ToString());
|
await response.WriteAsync(context.Object.ToString());
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
|
|
@ -18,52 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider
|
public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider
|
||||||
{
|
{
|
||||||
private IDictionary<string, string> _outputMediaTypeCache;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="OutputFormatter"/> class.
|
/// Initializes a new instance of the <see cref="OutputFormatter"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected OutputFormatter()
|
protected OutputFormatter()
|
||||||
{
|
{
|
||||||
SupportedEncodings = new List<Encoding>();
|
|
||||||
SupportedMediaTypes = new MediaTypeCollection();
|
SupportedMediaTypes = new MediaTypeCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the mutable collection of character encodings supported by
|
|
||||||
/// this <see cref="OutputFormatter"/>. The encodings are
|
|
||||||
/// used when writing the data.
|
|
||||||
/// </summary>
|
|
||||||
public IList<Encoding> SupportedEncodings { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the mutable collection of media type elements supported by
|
/// Gets the mutable collection of media type elements supported by
|
||||||
/// this <see cref="OutputFormatter"/>.
|
/// this <see cref="OutputFormatter"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MediaTypeCollection SupportedMediaTypes { get; }
|
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, MediaType.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>
|
/// <summary>
|
||||||
/// Returns a value indicating whether or not the given type can be written by this serializer.
|
/// Returns a value indicating whether or not the given type can be written by this serializer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -115,49 +79,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines the best <see cref="Encoding"/> amongst the supported encodings
|
|
||||||
/// for reading or writing an HTTP entity body based on the provided <paramref name="contentTypeHeader"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The formatter context associated with the call.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>The <see cref="Encoding"/> to use when reading the request or writing the response.</returns>
|
|
||||||
public virtual Encoding SelectCharacterEncoding(OutputFormatterWriteContext context)
|
|
||||||
{
|
|
||||||
if (context == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = context.HttpContext.Request;
|
|
||||||
var encoding = MatchAcceptCharacterEncoding(request.GetTypedHeaders().AcceptCharset);
|
|
||||||
if (encoding != null)
|
|
||||||
{
|
|
||||||
return encoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.ContentType.HasValue)
|
|
||||||
{
|
|
||||||
var parsedContentType = new MediaType(context.ContentType);
|
|
||||||
var contentTypeCharset = parsedContentType.Charset;
|
|
||||||
if (contentTypeCharset.HasValue)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < SupportedEncodings.Count; i++)
|
|
||||||
{
|
|
||||||
var supportedEncoding = SupportedEncodings[i];
|
|
||||||
if (contentTypeCharset.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
// This is supported.
|
|
||||||
return SupportedEncodings[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A formatter for a non-text media-type won't have any supported encodings.
|
|
||||||
return SupportedEncodings.Count > 0 ? SupportedEncodings[0] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual bool CanWriteResult(OutputFormatterCanWriteContext context)
|
public virtual bool CanWriteResult(OutputFormatterCanWriteContext context)
|
||||||
{
|
{
|
||||||
|
|
@ -206,46 +127,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task WriteAsync(OutputFormatterWriteContext context)
|
public virtual Task WriteAsync(OutputFormatterWriteContext context)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedMediaType = context.ContentType;
|
|
||||||
if (!selectedMediaType.HasValue)
|
|
||||||
{
|
|
||||||
// If content type is not set then set it based on supported media types.
|
|
||||||
if (SupportedEncodings.Count > 0)
|
|
||||||
{
|
|
||||||
selectedMediaType = new StringSegment(SupportedMediaTypes[0]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.FormatOutputFormatterNoMediaType(GetType().FullName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
// The default implementation of SelectCharacterEncoding will read from the list of SupportedEncodings
|
|
||||||
// and will always choose a default encoding if any are supported. So, the only cases where the
|
|
||||||
// selectedEncoding can be null are:
|
|
||||||
//
|
|
||||||
// 1). No supported encodings - we assume this is a non-text format
|
|
||||||
// 2). Custom implementation of SelectCharacterEncoding - trust the user and give them what they want.
|
|
||||||
var selectedEncoding = SelectCharacterEncoding(context);
|
|
||||||
if (selectedEncoding != null)
|
|
||||||
{
|
|
||||||
// Override the content type value even if one already existed.
|
|
||||||
var mediaTypeWithCharset = GetMediaTypeWithCharset(selectedMediaType.Value, selectedEncoding);
|
|
||||||
selectedMediaType = new StringSegment(mediaTypeWithCharset);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.ContentType = selectedMediaType;
|
|
||||||
|
|
||||||
WriteResponseHeaders(context);
|
WriteResponseHeaders(context);
|
||||||
return WriteResponseBodyAsync(context);
|
return WriteResponseBodyAsync(context);
|
||||||
}
|
}
|
||||||
|
|
@ -271,100 +159,5 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
/// <param name="context">The formatter context associated with the call.</param>
|
/// <param name="context">The formatter context associated with the call.</param>
|
||||||
/// <returns>A task which can write the response body.</returns>
|
/// <returns>A task which can write the response body.</returns>
|
||||||
public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context);
|
public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context);
|
||||||
|
|
||||||
private string GetMediaTypeWithCharset(string mediaType, Encoding encoding)
|
|
||||||
{
|
|
||||||
if (string.Equals(encoding.WebName, Encoding.UTF8.WebName, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
OutputMediaTypeCache.ContainsKey(mediaType))
|
|
||||||
{
|
|
||||||
return OutputMediaTypeCache[mediaType];
|
|
||||||
}
|
|
||||||
|
|
||||||
return MediaType.ReplaceEncoding(mediaType, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Encoding MatchAcceptCharacterEncoding(IList<StringWithQualityHeaderValue> acceptCharsetHeaders)
|
|
||||||
{
|
|
||||||
if (acceptCharsetHeaders != null && acceptCharsetHeaders.Count > 0)
|
|
||||||
{
|
|
||||||
var acceptValues = Sort(acceptCharsetHeaders);
|
|
||||||
for (var i = 0; i < acceptValues.Count; i++)
|
|
||||||
{
|
|
||||||
var charset = acceptValues[i].Value;
|
|
||||||
if (!string.IsNullOrEmpty(charset))
|
|
||||||
{
|
|
||||||
for (var j = 0; j < SupportedEncodings.Count; j++)
|
|
||||||
{
|
|
||||||
var encoding = SupportedEncodings[j];
|
|
||||||
if (charset.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
charset.Equals("*", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return encoding;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// There's no allocation-free way to sort an IList and we may have to filter anyway,
|
|
||||||
// so we're going to have to live with the copy + insertion sort.
|
|
||||||
private IList<StringWithQualityHeaderValue> Sort(IList<StringWithQualityHeaderValue> 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<StringWithQualityHeaderValue>();
|
|
||||||
for (var i = 0; i < values.Count; i++)
|
|
||||||
{
|
|
||||||
var value = values[i];
|
|
||||||
if (value.Quality == HeaderQuality.NoMatch)
|
|
||||||
{
|
|
||||||
// Exclude this one
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Doing an insertion sort.
|
|
||||||
var position = sorted.BinarySearch(value, StringWithQualityHeaderValueComparer.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Always writes a string value to the response, regardless of requested content type.
|
/// Always writes a string value to the response, regardless of requested content type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StringOutputFormatter : OutputFormatter
|
public class StringOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public StringOutputFormatter()
|
public StringOutputFormatter()
|
||||||
{
|
{
|
||||||
|
|
@ -46,13 +46,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding encoding)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (encoding == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(encoding));
|
||||||
|
}
|
||||||
|
|
||||||
var valueAsString = (string)context.Object;
|
var valueAsString = (string)context.Object;
|
||||||
if (string.IsNullOrEmpty(valueAsString))
|
if (string.IsNullOrEmpty(valueAsString))
|
||||||
{
|
{
|
||||||
|
|
@ -60,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = context.HttpContext.Response;
|
var response = context.HttpContext.Response;
|
||||||
return response.WriteAsync(valueAsString, MediaType.GetEncoding(response.ContentType) ?? Encoding.UTF8);
|
return response.WriteAsync(valueAsString, encoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
// 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.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an object in a given text format to the output stream.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class TextOutputFormatter : OutputFormatter
|
||||||
|
{
|
||||||
|
private IDictionary<string, string> _outputMediaTypeCache;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TextOutputFormatter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
protected TextOutputFormatter()
|
||||||
|
{
|
||||||
|
SupportedEncodings = new List<Encoding>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the mutable collection of character encodings supported by
|
||||||
|
/// this <see cref="TextOutputFormatter"/>. The encodings are
|
||||||
|
/// used when writing the data.
|
||||||
|
/// </summary>
|
||||||
|
public IList<Encoding> SupportedEncodings { get; }
|
||||||
|
|
||||||
|
private IDictionary<string, string> OutputMediaTypeCache
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_outputMediaTypeCache == null)
|
||||||
|
{
|
||||||
|
var cache = new Dictionary<string, string>();
|
||||||
|
foreach (var mediaType in SupportedMediaTypes)
|
||||||
|
{
|
||||||
|
cache.Add(mediaType, MediaType.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>
|
||||||
|
/// Determines the best <see cref="Encoding"/> amongst the supported encodings
|
||||||
|
/// for reading or writing an HTTP entity body based on the provided <paramref name="contentTypeHeader"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The formatter context associated with the call.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The <see cref="Encoding"/> to use when reading the request or writing the response.</returns>
|
||||||
|
public virtual Encoding SelectCharacterEncoding(OutputFormatterWriteContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SupportedEncodings.Count == 0)
|
||||||
|
{
|
||||||
|
var message = Resources.FormatTextOutputFormatter_SupportedEncodingsMustNotBeEmpty(
|
||||||
|
nameof(SupportedEncodings));
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = context.HttpContext.Request;
|
||||||
|
var encoding = MatchAcceptCharacterEncoding(request.GetTypedHeaders().AcceptCharset);
|
||||||
|
if (encoding != null)
|
||||||
|
{
|
||||||
|
return encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.ContentType.HasValue)
|
||||||
|
{
|
||||||
|
var parsedContentType = new MediaType(context.ContentType);
|
||||||
|
var contentTypeCharset = parsedContentType.Charset;
|
||||||
|
if (contentTypeCharset.HasValue)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < SupportedEncodings.Count; i++)
|
||||||
|
{
|
||||||
|
var supportedEncoding = SupportedEncodings[i];
|
||||||
|
if (contentTypeCharset.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// This is supported.
|
||||||
|
return SupportedEncodings[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SupportedEncodings[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task WriteAsync(OutputFormatterWriteContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedMediaType = context.ContentType;
|
||||||
|
if (!selectedMediaType.HasValue)
|
||||||
|
{
|
||||||
|
// If content type is not set then set it based on supported media types.
|
||||||
|
if (SupportedEncodings.Count > 0)
|
||||||
|
{
|
||||||
|
selectedMediaType = new StringSegment(SupportedMediaTypes[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatOutputFormatterNoMediaType(GetType().FullName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedEncoding = SelectCharacterEncoding(context);
|
||||||
|
if (selectedEncoding != null)
|
||||||
|
{
|
||||||
|
// Override the content type value even if one already existed.
|
||||||
|
var mediaTypeWithCharset = GetMediaTypeWithCharset(selectedMediaType.Value, selectedEncoding);
|
||||||
|
selectedMediaType = new StringSegment(mediaTypeWithCharset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var response = context.HttpContext.Response;
|
||||||
|
response.StatusCode = StatusCodes.Status406NotAcceptable;
|
||||||
|
return TaskCache.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.ContentType = selectedMediaType;
|
||||||
|
|
||||||
|
WriteResponseHeaders(context);
|
||||||
|
return WriteResponseBodyAsync(context, selectedEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public sealed override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
||||||
|
{
|
||||||
|
var message = Resources.FormatTextOutpurFormatter_WriteResponseBodyAsynNotSupported(
|
||||||
|
$"{nameof(WriteResponseBodyAsync)}({nameof(OutputFormatterWriteContext)})",
|
||||||
|
nameof(TextOutputFormatter),
|
||||||
|
$"{nameof(WriteResponseBodyAsync)}({nameof(OutputFormatterWriteContext)},{nameof(Encoding)})");
|
||||||
|
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the response body.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The formatter context associated with the call.</param>
|
||||||
|
/// <param name="selectedEncoding">The <see cref="Encoding"/> that should be used to write the response.</param>
|
||||||
|
/// <returns>A task which can write the response body.</returns>
|
||||||
|
public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding);
|
||||||
|
|
||||||
|
private string GetMediaTypeWithCharset(string mediaType, Encoding encoding)
|
||||||
|
{
|
||||||
|
if (string.Equals(encoding.WebName, Encoding.UTF8.WebName, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
OutputMediaTypeCache.ContainsKey(mediaType))
|
||||||
|
{
|
||||||
|
return OutputMediaTypeCache[mediaType];
|
||||||
|
}
|
||||||
|
|
||||||
|
return MediaType.ReplaceEncoding(mediaType, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Encoding MatchAcceptCharacterEncoding(IList<StringWithQualityHeaderValue> acceptCharsetHeaders)
|
||||||
|
{
|
||||||
|
if (acceptCharsetHeaders != null && acceptCharsetHeaders.Count > 0)
|
||||||
|
{
|
||||||
|
var acceptValues = Sort(acceptCharsetHeaders);
|
||||||
|
for (var i = 0; i < acceptValues.Count; i++)
|
||||||
|
{
|
||||||
|
var charset = acceptValues[i].Value;
|
||||||
|
if (!string.IsNullOrEmpty(charset))
|
||||||
|
{
|
||||||
|
for (var j = 0; j < SupportedEncodings.Count; j++)
|
||||||
|
{
|
||||||
|
var encoding = SupportedEncodings[j];
|
||||||
|
if (charset.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
charset.Equals("*", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return encoding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's no allocation-free way to sort an IList and we may have to filter anyway,
|
||||||
|
// so we're going to have to live with the copy + insertion sort.
|
||||||
|
private IList<StringWithQualityHeaderValue> Sort(IList<StringWithQualityHeaderValue> 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<StringWithQualityHeaderValue>();
|
||||||
|
for (var i = 0; i < values.Count; i++)
|
||||||
|
{
|
||||||
|
var value = values[i];
|
||||||
|
if (value.Quality == HeaderQuality.NoMatch)
|
||||||
|
{
|
||||||
|
// Exclude this one
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Doing an insertion sort.
|
||||||
|
var position = sorted.BinarySearch(value, StringWithQualityHeaderValueComparer.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1114,6 +1114,38 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("TextInputFormatter_SupportedEncodingsMustNotBeEmpty"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("TextInputFormatter_SupportedEncodingsMustNotBeEmpty"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of '{0}' must not be empty. Add at least one supported encoding.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TextOutputFormatter_SupportedEncodingsMustNotBeEmpty
|
||||||
|
{
|
||||||
|
get { return GetString("TextOutputFormatter_SupportedEncodingsMustNotBeEmpty"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of '{0}' must not be empty. Add at least one supported encoding.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTextOutputFormatter_SupportedEncodingsMustNotBeEmpty(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TextOutputFormatter_SupportedEncodingsMustNotBeEmpty"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}' is not supported by '{1}'. Use '{2}' instead.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TextOutpurFormatter_WriteResponseBodyAsynNotSupported
|
||||||
|
{
|
||||||
|
get { return GetString("TextOutpurFormatter_WriteResponseBodyAsynNotSupported"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// '{0}' is not supported by '{1}'. Use '{2}' instead.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTextOutpurFormatter_WriteResponseBodyAsynNotSupported(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TextOutpurFormatter_WriteResponseBodyAsynNotSupported"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -334,4 +334,10 @@
|
||||||
<data name="TextInputFormatter_SupportedEncodingsMustNotBeEmpty" xml:space="preserve">
|
<data name="TextInputFormatter_SupportedEncodingsMustNotBeEmpty" xml:space="preserve">
|
||||||
<value>The list of '{0}' must not be empty. Add at least one supported encoding.</value>
|
<value>The list of '{0}' must not be empty. Add at least one supported encoding.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TextOutputFormatter_SupportedEncodingsMustNotBeEmpty" xml:space="preserve">
|
||||||
|
<value>The list of '{0}' must not be empty. Add at least one supported encoding.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TextOutpurFormatter_WriteResponseBodyAsynNotSupported" xml:space="preserve">
|
||||||
|
<value>'{0}' is not supported by '{1}'. Use '{2}' instead.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An output formatter that specializes in writing JSON content.
|
/// An output formatter that specializes in writing JSON content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonOutputFormatter : OutputFormatter
|
public class JsonOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
private readonly IArrayPool<char> _charPool;
|
private readonly IArrayPool<char> _charPool;
|
||||||
|
|
||||||
|
|
@ -83,6 +83,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the given <paramref name="value"/> as JSON using the given
|
||||||
|
/// <paramref name="writer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The <see cref="TextWriter"/> used to write the <paramref name="value"/></param>
|
||||||
|
/// <param name="value">The value to write as JSON.</param>
|
||||||
public void WriteObject(TextWriter writer, object value)
|
public void WriteObject(TextWriter writer, object value)
|
||||||
{
|
{
|
||||||
if (writer == null)
|
if (writer == null)
|
||||||
|
|
@ -132,16 +138,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
return _serializer;
|
return _serializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
/// <inheritdoc />
|
||||||
|
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = context.HttpContext.Response;
|
if (selectedEncoding == null)
|
||||||
var selectedEncoding = MediaType.GetEncoding(context.ContentType) ?? Encoding.UTF8;
|
{
|
||||||
|
throw new ArgumentNullException(nameof(selectedEncoding));
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = context.HttpContext.Response;
|
||||||
using (var writer = context.WriterFactory(response.Body, selectedEncoding))
|
using (var writer = context.WriterFactory(response.Body, selectedEncoding))
|
||||||
{
|
{
|
||||||
WriteObject(writer, context.Object);
|
WriteObject(writer, context.Object);
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
|
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
|
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
|
|
@ -20,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
/// This class handles serialization of objects
|
/// This class handles serialization of objects
|
||||||
/// to XML using <see cref="DataContractSerializer"/>
|
/// to XML using <see cref="DataContractSerializer"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class XmlDataContractSerializerOutputFormatter : OutputFormatter
|
public class XmlDataContractSerializerOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
private DataContractSerializerSettings _serializerSettings;
|
private DataContractSerializerSettings _serializerSettings;
|
||||||
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
|
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
|
||||||
|
|
@ -180,15 +178,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedEncoding == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(selectedEncoding));
|
||||||
|
}
|
||||||
|
|
||||||
var writerSettings = WriterSettings.Clone();
|
var writerSettings = WriterSettings.Clone();
|
||||||
writerSettings.Encoding = MediaType.GetEncoding(context.ContentType) ?? Encoding.UTF8;
|
writerSettings.Encoding = selectedEncoding;
|
||||||
|
|
||||||
// Wrap the object only if there is a wrapping type.
|
// Wrap the object only if there is a wrapping type.
|
||||||
var value = context.Object;
|
var value = context.Object;
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ using System.Xml;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
|
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
|
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
|
|
@ -20,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
/// This class handles serialization of objects
|
/// This class handles serialization of objects
|
||||||
/// to XML using <see cref="XmlSerializer"/>
|
/// to XML using <see cref="XmlSerializer"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class XmlSerializerOutputFormatter : OutputFormatter
|
public class XmlSerializerOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
|
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
|
||||||
|
|
||||||
|
|
@ -155,17 +153,22 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedEncoding == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(selectedEncoding));
|
||||||
|
}
|
||||||
|
|
||||||
var response = context.HttpContext.Response;
|
var response = context.HttpContext.Response;
|
||||||
|
|
||||||
var writerSettings = WriterSettings.Clone();
|
var writerSettings = WriterSettings.Clone();
|
||||||
writerSettings.Encoding = MediaType.GetEncoding(context.ContentType) ?? Encoding.UTF8;
|
writerSettings.Encoding = selectedEncoding;
|
||||||
|
|
||||||
// Wrap the object only if there is a wrapping type.
|
// Wrap the object only if there is a wrapping type.
|
||||||
var value = context.Object;
|
var value = context.Object;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -28,17 +29,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
Assert.IsType(typeof(AnotherTestOutputFormatter), formatter);
|
Assert.IsType(typeof(AnotherTestOutputFormatter), formatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestOutputFormatter : OutputFormatter
|
private class TestOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AnotherTestOutputFormatter : OutputFormatter
|
private class AnotherTestOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,228 +18,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
public class OutputFormatterTests
|
public class OutputFormatterTests
|
||||||
{
|
{
|
||||||
public static IEnumerable<object[]> SelectResponseCharacterEncodingData
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
// string acceptEncodings, string requestEncoding, string[] supportedEncodings, string expectedEncoding
|
|
||||||
yield return new object[] { "", new string[] { "utf-8", "utf-16" }, "utf-8" };
|
|
||||||
|
|
||||||
yield return new object[] { "utf-8", new string[] { "utf-8", "utf-16" }, "utf-8" };
|
|
||||||
yield return new object[] { "utf-16", new string[] { "utf-8", "utf-16" }, "utf-16" };
|
|
||||||
yield return new object[] { "utf-16; q=0.5", new string[] { "utf-8", "utf-16" }, "utf-16" };
|
|
||||||
|
|
||||||
yield return new object[] { "utf-8; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" };
|
|
||||||
yield return new object[] { "utf-8; q=0.0, utf-16; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" };
|
|
||||||
|
|
||||||
yield return new object[] { "*; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(SelectResponseCharacterEncodingData))]
|
|
||||||
public void SelectResponseCharacterEncoding_SelectsEncoding(
|
|
||||||
string acceptCharsetHeaders,
|
|
||||||
string[] supportedEncodings,
|
|
||||||
string expectedEncoding)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var httpContext = new Mock<HttpContext>();
|
|
||||||
var httpRequest = new DefaultHttpContext().Request;
|
|
||||||
httpRequest.Headers[HeaderNames.AcceptCharset] = acceptCharsetHeaders;
|
|
||||||
httpRequest.Headers[HeaderNames.Accept] = "application/acceptCharset";
|
|
||||||
httpContext.SetupGet(o => o.Request).Returns(httpRequest);
|
|
||||||
|
|
||||||
var formatter = new TestOutputFormatter();
|
|
||||||
foreach (string supportedEncoding in supportedEncodings)
|
|
||||||
{
|
|
||||||
formatter.SupportedEncodings.Add(Encoding.GetEncoding(supportedEncoding));
|
|
||||||
}
|
|
||||||
|
|
||||||
var context = new OutputFormatterWriteContext(
|
|
||||||
httpContext.Object,
|
|
||||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
|
||||||
typeof(string),
|
|
||||||
"someValue")
|
|
||||||
{
|
|
||||||
ContentType = new StringSegment(httpRequest.Headers[HeaderNames.Accept]),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var actualEncoding = formatter.SelectCharacterEncoding(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(Encoding.GetEncoding(expectedEncoding), actualEncoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("application/json; charset=utf-16", "application/json; charset=utf-32")]
|
|
||||||
[InlineData("application/json; charset=utf-16; format=indent", "application/json; charset=utf-32; format=indent")]
|
|
||||||
public void WriteResponse_OverridesCharset_IfDifferentFromContentTypeCharset(
|
|
||||||
string contentType,
|
|
||||||
string expectedContentType)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var formatter = new Mock<OutputFormatter>();
|
|
||||||
|
|
||||||
formatter
|
|
||||||
.Setup(f => f.SelectCharacterEncoding(It.IsAny<OutputFormatterWriteContext>()))
|
|
||||||
.Returns(Encoding.UTF32);
|
|
||||||
|
|
||||||
var formatterContext = new OutputFormatterWriteContext(
|
|
||||||
new DefaultHttpContext(),
|
|
||||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
|
||||||
objectType: null,
|
|
||||||
@object: null)
|
|
||||||
{
|
|
||||||
ContentType = new StringSegment(contentType),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
formatter.Object.WriteAsync(formatterContext);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(new StringSegment(expectedContentType), formatterContext.ContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var formatter = new TestOutputFormatter();
|
|
||||||
|
|
||||||
var testContentType = new StringSegment("text/json");
|
|
||||||
|
|
||||||
formatter.SupportedEncodings.Clear();
|
|
||||||
formatter.SupportedMediaTypes.Clear();
|
|
||||||
formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
|
|
||||||
|
|
||||||
var context = new OutputFormatterWriteContext(
|
|
||||||
new DefaultHttpContext(),
|
|
||||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
|
||||||
objectType: null,
|
|
||||||
@object: null)
|
|
||||||
{
|
|
||||||
ContentType = testContentType,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
formatter.WriteResponseHeaders(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
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(MediaTypeHeaderValue.Parse(testContentType.Value), context.HttpContext.Response.GetTypedHeaders().ContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WriteResponseHeaders_ClonesMediaType()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var formatter = new PngImageFormatter();
|
|
||||||
formatter.SupportedMediaTypes.Clear();
|
|
||||||
var mediaType = new MediaTypeHeaderValue("image/png");
|
|
||||||
formatter.SupportedMediaTypes.Add(mediaType);
|
|
||||||
|
|
||||||
var context = new OutputFormatterWriteContext(
|
|
||||||
new DefaultHttpContext(),
|
|
||||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
|
||||||
objectType: null,
|
|
||||||
@object: null);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await formatter.WriteAsync(context);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotSame(mediaType, context.ContentType);
|
|
||||||
Assert.Null(mediaType.Charset);
|
|
||||||
Assert.Equal("image/png; charset=utf-8", context.ContentType.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanWriteResult_ForNullContentType_UsesFirstEntryInSupportedContentTypes()
|
public void CanWriteResult_ForNullContentType_UsesFirstEntryInSupportedContentTypes()
|
||||||
{
|
{
|
||||||
|
|
@ -397,42 +175,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
public TestOutputFormatter()
|
public TestOutputFormatter()
|
||||||
{
|
{
|
||||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/acceptCharset"));
|
SupportedMediaTypes.Add("application/acceptCharset");
|
||||||
}
|
|
||||||
|
|
||||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
|
||||||
{
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DoesNotSetContext : OutputFormatter
|
|
||||||
{
|
|
||||||
public DoesNotSetContext()
|
|
||||||
{
|
|
||||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/doesNotSetContext"));
|
|
||||||
SupportedEncodings.Add(Encoding.Unicode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWriteResult(OutputFormatterCanWriteContext context)
|
|
||||||
{
|
|
||||||
// Do not set the selected media Type.
|
|
||||||
// The WriteResponseHeaders should do it for you.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
|
||||||
{
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PngImageFormatter : OutputFormatter
|
|
||||||
{
|
|
||||||
public PngImageFormatter()
|
|
||||||
{
|
|
||||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("image/png"));
|
|
||||||
SupportedEncodings.Add(Encoding.UTF8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
Encoding encoding = Encoding.UTF8;
|
Encoding encoding = Encoding.UTF8;
|
||||||
var memoryStream = new MemoryStream();
|
var memoryStream = new MemoryStream();
|
||||||
var response = new Mock<HttpResponse>();
|
var response = new Mock<HttpResponse>();
|
||||||
response.SetupProperty<long?>(o => o.ContentLength);
|
response.SetupProperty(o => o.ContentLength);
|
||||||
response.SetupGet(r => r.Body).Returns(memoryStream);
|
response.SetupGet(r => r.Body).Returns(memoryStream);
|
||||||
var httpContext = new Mock<HttpContext>();
|
var httpContext = new Mock<HttpContext>();
|
||||||
httpContext.Setup(o => o.Response).Returns(response.Object);
|
httpContext.Setup(o => o.Response).Returns(response.Object);
|
||||||
|
|
@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
@object: null);
|
@object: null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await formatter.WriteResponseBodyAsync(context);
|
await formatter.WriteResponseBodyAsync(context, encoding);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(0, memoryStream.Length);
|
Assert.Equal(0, memoryStream.Length);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,271 @@
|
||||||
|
// 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.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
|
{
|
||||||
|
public class TextOutputFormatterTests
|
||||||
|
{
|
||||||
|
public static IEnumerable<object[]> SelectResponseCharacterEncodingData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// string acceptEncodings, string requestEncoding, string[] supportedEncodings, string expectedEncoding
|
||||||
|
yield return new object[] { "", new string[] { "utf-8", "utf-16" }, "utf-8" };
|
||||||
|
|
||||||
|
yield return new object[] { "utf-8", new string[] { "utf-8", "utf-16" }, "utf-8" };
|
||||||
|
yield return new object[] { "utf-16", new string[] { "utf-8", "utf-16" }, "utf-16" };
|
||||||
|
yield return new object[] { "utf-16; q=0.5", new string[] { "utf-8", "utf-16" }, "utf-16" };
|
||||||
|
|
||||||
|
yield return new object[] { "utf-8; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" };
|
||||||
|
yield return new object[] { "utf-8; q=0.0, utf-16; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" };
|
||||||
|
|
||||||
|
yield return new object[] { "*; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(SelectResponseCharacterEncodingData))]
|
||||||
|
public void SelectResponseCharacterEncoding_SelectsEncoding(
|
||||||
|
string acceptCharsetHeaders,
|
||||||
|
string[] supportedEncodings,
|
||||||
|
string expectedEncoding)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new Mock<HttpContext>();
|
||||||
|
var httpRequest = new DefaultHttpContext().Request;
|
||||||
|
httpRequest.Headers[HeaderNames.AcceptCharset] = acceptCharsetHeaders;
|
||||||
|
httpRequest.Headers[HeaderNames.Accept] = "application/acceptCharset";
|
||||||
|
httpContext.SetupGet(o => o.Request).Returns(httpRequest);
|
||||||
|
|
||||||
|
var formatter = new TestOutputFormatter();
|
||||||
|
foreach (string supportedEncoding in supportedEncodings)
|
||||||
|
{
|
||||||
|
formatter.SupportedEncodings.Add(Encoding.GetEncoding(supportedEncoding));
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = new OutputFormatterWriteContext(
|
||||||
|
httpContext.Object,
|
||||||
|
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||||
|
typeof(string),
|
||||||
|
"someValue")
|
||||||
|
{
|
||||||
|
ContentType = new StringSegment(httpRequest.Headers[HeaderNames.Accept]),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actualEncoding = formatter.SelectCharacterEncoding(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
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 OverrideEncodingFormatter(Encoding.UTF32);
|
||||||
|
|
||||||
|
var formatterContext = new OutputFormatterWriteContext(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||||
|
objectType: null,
|
||||||
|
@object: null)
|
||||||
|
{
|
||||||
|
ContentType = new StringSegment(contentType),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
formatter.WriteAsync(formatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new TestOutputFormatter();
|
||||||
|
|
||||||
|
var testContentType = new StringSegment("text/json");
|
||||||
|
|
||||||
|
formatter.SupportedEncodings.Clear();
|
||||||
|
formatter.SupportedMediaTypes.Clear();
|
||||||
|
formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
|
||||||
|
|
||||||
|
var context = new OutputFormatterWriteContext(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||||
|
objectType: null,
|
||||||
|
@object: null)
|
||||||
|
{
|
||||||
|
ContentType = testContentType,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
formatter.WriteResponseHeaders(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
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(MediaTypeHeaderValue.Parse(testContentType.Value), context.HttpContext.Response.GetTypedHeaders().ContentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteAsync_ReturnsNotAcceptable_IfSelectCharacterEncodingReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = new OverrideEncodingFormatter(encoding: null);
|
||||||
|
|
||||||
|
var testContentType = new StringSegment("text/json");
|
||||||
|
formatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
|
||||||
|
|
||||||
|
var context = new OutputFormatterWriteContext(
|
||||||
|
new DefaultHttpContext(),
|
||||||
|
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||||
|
objectType: null,
|
||||||
|
@object: null)
|
||||||
|
{
|
||||||
|
ContentType = testContentType,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await formatter.WriteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(StatusCodes.Status406NotAcceptable, context.HttpContext.Response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestOutputFormatter : TextOutputFormatter
|
||||||
|
{
|
||||||
|
public TestOutputFormatter()
|
||||||
|
{
|
||||||
|
SupportedMediaTypes.Add("application/acceptCharset");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OverrideEncodingFormatter : TextOutputFormatter
|
||||||
|
{
|
||||||
|
private readonly Encoding _encoding;
|
||||||
|
|
||||||
|
public OverrideEncodingFormatter(Encoding encoding)
|
||||||
|
{
|
||||||
|
_encoding = encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Encoding SelectCharacterEncoding(OutputFormatterWriteContext context)
|
||||||
|
{
|
||||||
|
return _encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -520,7 +520,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestJsonOutputFormatter : OutputFormatter
|
private class TestJsonOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public TestJsonOutputFormatter()
|
public TestJsonOutputFormatter()
|
||||||
{
|
{
|
||||||
|
|
@ -530,13 +530,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
SupportedEncodings.Add(Encoding.UTF8);
|
SupportedEncodings.Add(Encoding.UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestXmlOutputFormatter : OutputFormatter
|
private class TestXmlOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public TestXmlOutputFormatter()
|
public TestXmlOutputFormatter()
|
||||||
{
|
{
|
||||||
|
|
@ -546,13 +546,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
SupportedEncodings.Add(Encoding.UTF8);
|
SupportedEncodings.Add(Encoding.UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestStringOutputFormatter : OutputFormatter
|
private class TestStringOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public TestStringOutputFormatter()
|
public TestStringOutputFormatter()
|
||||||
{
|
{
|
||||||
|
|
@ -561,7 +561,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
SupportedEncodings.Add(Encoding.UTF8);
|
SupportedEncodings.Add(Encoding.UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
var outputFormatterContext = GetOutputFormatterContext(person, typeof(User));
|
var outputFormatterContext = GetOutputFormatterContext(person, typeof(User));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext);
|
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.UTF8);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var body = outputFormatterContext.HttpContext.Response.Body;
|
var body = outputFormatterContext.HttpContext.Response.Body;
|
||||||
|
|
@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
|
|
||||||
// This will create a serializer - which gets cached.
|
// This will create a serializer - which gets cached.
|
||||||
var outputFormatterContext1 = GetOutputFormatterContext(person, typeof(User));
|
var outputFormatterContext1 = GetOutputFormatterContext(person, typeof(User));
|
||||||
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext1);
|
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext1, Encoding.UTF8);
|
||||||
|
|
||||||
// These changes should have no effect.
|
// These changes should have no effect.
|
||||||
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
||||||
|
|
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
var outputFormatterContext2 = GetOutputFormatterContext(person, typeof(User));
|
var outputFormatterContext2 = GetOutputFormatterContext(person, typeof(User));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext2);
|
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext2, Encoding.UTF8);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var body = outputFormatterContext2.HttpContext.Response.Body;
|
var body = outputFormatterContext2.HttpContext.Response.Body;
|
||||||
|
|
@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
|
|
||||||
// This will create a serializer - which gets cached.
|
// This will create a serializer - which gets cached.
|
||||||
var outputFormatterContext1 = GetOutputFormatterContext(person, typeof(User));
|
var outputFormatterContext1 = GetOutputFormatterContext(person, typeof(User));
|
||||||
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext1);
|
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext1, Encoding.UTF8);
|
||||||
|
|
||||||
// This results in a new serializer being created.
|
// This results in a new serializer being created.
|
||||||
jsonFormatter.SerializerSettings = new JsonSerializerSettings()
|
jsonFormatter.SerializerSettings = new JsonSerializerSettings()
|
||||||
|
|
@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
var outputFormatterContext2 = GetOutputFormatterContext(person, typeof(User));
|
var outputFormatterContext2 = GetOutputFormatterContext(person, typeof(User));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext2);
|
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext2, Encoding.UTF8);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var body = outputFormatterContext2.HttpContext.Response.Body;
|
var body = outputFormatterContext2.HttpContext.Response.Body;
|
||||||
|
|
@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
var outputFormatterContext = GetOutputFormatterContext(person, typeof(User));
|
var outputFormatterContext = GetOutputFormatterContext(person, typeof(User));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext);
|
await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.UTF8);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var body = outputFormatterContext.HttpContext.Response.Body;
|
var body = outputFormatterContext.HttpContext.Response.Body;
|
||||||
|
|
@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
memStream);
|
memStream);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await formatter.WriteResponseBodyAsync(outputFormatterContext);
|
await formatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.UTF8);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
memStream.Position = 0;
|
memStream.Position = 0;
|
||||||
|
|
@ -267,7 +267,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await formatter.WriteResponseBodyAsync(outputFormatterContext);
|
await formatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.GetEncoding(encodingAsString));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var actualData = body.ToArray();
|
var actualData = body.ToArray();
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace BasicWebSite.Formatters
|
namespace BasicWebSite.Formatters
|
||||||
{
|
{
|
||||||
public class CustomFormatter : OutputFormatter
|
public class CustomFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public string ContentType { get; private set; }
|
public string ContentType { get; private set; }
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace BasicWebSite.Formatters
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
var response = context.HttpContext.Response;
|
var response = context.HttpContext.Response;
|
||||||
response.ContentType = ContentType + ";charset=utf-8";
|
response.ContentType = ContentType + ";charset=utf-8";
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace BasicWebSite.Formatters
|
namespace BasicWebSite.Formatters
|
||||||
{
|
{
|
||||||
public class PlainTextFormatter : OutputFormatter
|
public class PlainTextFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public PlainTextFormatter()
|
public PlainTextFormatter()
|
||||||
{
|
{
|
||||||
|
|
@ -31,7 +31,7 @@ namespace BasicWebSite.Formatters
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
var response = context.HttpContext.Response;
|
var response = context.HttpContext.Response;
|
||||||
response.ContentType = "text/plain;charset=utf-8";
|
response.ContentType = "text/plain;charset=utf-8";
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace BasicWebSite.Formatters
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides contact information of a person through VCard format.
|
/// Provides contact information of a person through VCard format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class VCardFormatter_V3 : OutputFormatter
|
public class VCardFormatter_V3 : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public VCardFormatter_V3()
|
public VCardFormatter_V3()
|
||||||
{
|
{
|
||||||
|
|
@ -28,7 +28,7 @@ namespace BasicWebSite.Formatters
|
||||||
return typeof(Contact).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo());
|
return typeof(Contact).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
var contact = (Contact)context.Object;
|
var contact = (Contact)context.Object;
|
||||||
|
|
||||||
|
|
@ -38,8 +38,6 @@ namespace BasicWebSite.Formatters
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
builder.AppendLine("END:VCARD");
|
builder.AppendLine("END:VCARD");
|
||||||
|
|
||||||
var selectedEncoding = new MediaType(context.ContentType).Encoding ?? Encoding.UTF8;
|
|
||||||
|
|
||||||
await context.HttpContext.Response.WriteAsync(
|
await context.HttpContext.Response.WriteAsync(
|
||||||
builder.ToString(),
|
builder.ToString(),
|
||||||
selectedEncoding);
|
selectedEncoding);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace BasicWebSite.Formatters
|
||||||
/// Provides contact information of a person through VCard format.
|
/// Provides contact information of a person through VCard format.
|
||||||
/// In version 4.0 of VCard format, Gender is a supported property.
|
/// In version 4.0 of VCard format, Gender is a supported property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class VCardFormatter_V4 : OutputFormatter
|
public class VCardFormatter_V4 : TextOutputFormatter
|
||||||
{
|
{
|
||||||
public VCardFormatter_V4()
|
public VCardFormatter_V4()
|
||||||
{
|
{
|
||||||
|
|
@ -29,7 +29,7 @@ namespace BasicWebSite.Formatters
|
||||||
return typeof(Contact).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo());
|
return typeof(Contact).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
|
||||||
{
|
{
|
||||||
var contact = (Contact)context.Object;
|
var contact = (Contact)context.Object;
|
||||||
|
|
||||||
|
|
@ -41,8 +41,6 @@ namespace BasicWebSite.Formatters
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
builder.AppendLine("END:VCARD");
|
builder.AppendLine("END:VCARD");
|
||||||
|
|
||||||
var selectedEncoding = new MediaType(context.ContentType).Encoding ?? Encoding.UTF8;
|
|
||||||
|
|
||||||
await context.HttpContext.Response.WriteAsync(
|
await context.HttpContext.Response.WriteAsync(
|
||||||
builder.ToString(),
|
builder.ToString(),
|
||||||
selectedEncoding);
|
selectedEncoding);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue