diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/IOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/IOutputFormatter.cs index 4b4d8863a4..6e07e7123b 100644 --- a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/IOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/IOutputFormatter.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters { @@ -16,18 +15,14 @@ namespace Microsoft.AspNet.Mvc.Formatters /// an object of the specified type. /// /// The formatter context associated with the call. - /// The desired contentType on the response. - /// True if this supports the passed in - /// and is able to serialize the object - /// represent by 's Object property. - /// False otherwise. - bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType); + /// Returns true if the formatter can write the response; false otherwise. + bool CanWriteResult(OutputFormatterCanWriteContext context); /// /// Writes the object represented by 's Object property. /// /// The formatter context associated with the call. /// A Task that serializes the value to the 's response message. - Task WriteAsync(OutputFormatterContext context); + Task WriteAsync(OutputFormatterWriteContext context); } } diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs new file mode 100644 index 0000000000..4a2f905d38 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.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 System; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNet.Mvc.Formatters +{ + /// + /// A context object for . + /// + public abstract class OutputFormatterCanWriteContext + { + /// + /// Gets or sets the of the content type to write to the response. + /// + /// + /// An can set this value when its + /// method is called, + /// and expect to see the same value provided in + /// + /// + public virtual MediaTypeHeaderValue ContentType { get; set; } + + /// + /// Gets or sets a value indicating that content-negotiation could not find a formatter based on the + /// information on the . + /// + public virtual bool? FailedContentNegotiation { get; set; } + + /// + /// Gets or sets the object to write to the response. + /// + public virtual object Object { get; protected set; } + + /// + /// Gets or sets the of the object to write to the response. + /// + public virtual Type ObjectType { get; protected set; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterContext.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterContext.cs deleted file mode 100644 index e5953e866d..0000000000 --- a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterContext.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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.Text; -using Microsoft.AspNet.Http; -using Microsoft.Net.Http.Headers; - -namespace Microsoft.AspNet.Mvc.Formatters -{ - /// - /// Represents information used by a formatter for participating in - /// output content negotiation and in writing out the response. - /// - public class OutputFormatterContext - { - /// - /// The return value of the action method. - /// - public object Object { get; set; } - - /// - /// The declared return type of the action. - /// - public Type DeclaredType { get; set; } - - /// - /// Gets or sets the context associated with the current operation. - /// - public HttpContext HttpContext { get; set; } - - /// - /// The encoding which is chosen by the selected formatter. - /// - public Encoding SelectedEncoding { get; set; } - - /// - /// The content type which is chosen by the selected formatter. - /// - public MediaTypeHeaderValue SelectedContentType { get; set; } - - - /// - /// Gets or sets a flag to indicate that content-negotiation could not find a formatter based on the - /// information on the . - /// - public bool? FailedContentNegotiation { get; set; } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterWriteContext.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterWriteContext.cs new file mode 100644 index 0000000000..d78d913e7c --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterWriteContext.cs @@ -0,0 +1,37 @@ +// 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.AspNet.Http; + +namespace Microsoft.AspNet.Mvc.Formatters +{ + /// + /// A context object for . + /// + public class OutputFormatterWriteContext : OutputFormatterCanWriteContext + { + /// + /// Creates a new . + /// + /// The for the current request. + /// The of the object to write to the response. + /// The object to write to the response. + public OutputFormatterWriteContext(HttpContext httpContext, Type objectType, object @object) + { + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + HttpContext = httpContext; + ObjectType = objectType; + Object = @object; + } + + /// + /// Gets or sets the context associated with the current operation. + /// + public virtual HttpContext HttpContext { get; protected set; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNet.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index 6563daf407..35fd9d8ff1 100644 --- a/src/Microsoft.AspNet.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -129,7 +129,6 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer var formats = GetResponseFormats( action, responseMetadataAttributes, - declaredReturnType, runtimeReturnType); foreach (var format in formats) @@ -306,8 +305,7 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer private IReadOnlyList GetResponseFormats( ControllerActionDescriptor action, IApiResponseMetadataProvider[] responseMetadataAttributes, - Type declaredType, - Type runtimeType) + Type type) { var results = new List(); @@ -334,10 +332,7 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer var responseFormatMetadataProvider = formatter as IApiResponseFormatMetadataProvider; if (responseFormatMetadataProvider != null) { - var supportedTypes = responseFormatMetadataProvider.GetSupportedContentTypes( - declaredType, - runtimeType, - contentType); + var supportedTypes = responseFormatMetadataProvider.GetSupportedContentTypes(contentType, type); if (supportedTypes != null) { diff --git a/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs index ed91b2edb3..8d21e10b93 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs @@ -11,25 +11,25 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer /// Provides metadata information about the response format to an IApiDescriptionProvider. /// /// - /// An should implement this interface to expose metadata information + /// An should implement this interface to expose metadata information /// to an IApiDescriptionProvider. /// public interface IApiResponseFormatMetadataProvider { /// - /// Gets a filtered list of content types which are supported by the + /// Gets a filtered list of content types which are supported by the /// for the and . /// - /// The declared type for which the supported content types are desired. - /// The runtime type for which the supported content types are desired. /// /// The content type for which the supported content types are desired, or null if any content /// type can be used. /// - /// Content types which are supported by the . + /// + /// The for which the supported content types are desired. + /// + /// Content types which are supported by the . IReadOnlyList GetSupportedContentTypes( - Type declaredType, - Type runtimeType, - MediaTypeHeaderValue contentType); + MediaTypeHeaderValue contentType, + Type objectType); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs index aff430578c..b23d4ffd1f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters { @@ -18,12 +17,13 @@ namespace Microsoft.AspNet.Mvc.Formatters /// public bool TreatNullValueAsNoContent { get; set; } = true; - public bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + /// + public bool CanWriteResult(OutputFormatterCanWriteContext context) { // ignore the contentType and just look at the content. // This formatter will be selected if the content is null. // We check for Task as a user can directly create an ObjectContentResult with the unwrapped type. - if (context.DeclaredType == typeof(void) || context.DeclaredType == typeof(Task)) + if (context.ObjectType == typeof(void) || context.ObjectType == typeof(Task)) { return true; } @@ -31,7 +31,8 @@ namespace Microsoft.AspNet.Mvc.Formatters return TreatNullValueAsNoContent && context.Object == null; } - public Task WriteAsync(OutputFormatterContext context) + /// + public Task WriteAsync(OutputFormatterWriteContext context) { var response = context.HttpContext.Response; response.ContentLength = 0; diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNotAcceptableOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNotAcceptableOutputFormatter.cs index c8d37710f1..3c8643e556 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNotAcceptableOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNotAcceptableOutputFormatter.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters { @@ -13,13 +12,13 @@ namespace Microsoft.AspNet.Mvc.Formatters public class HttpNotAcceptableOutputFormatter : IOutputFormatter { /// - public bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + public bool CanWriteResult(OutputFormatterCanWriteContext context) { return context.FailedContentNegotiation ?? false; } /// - public Task WriteAsync(OutputFormatterContext context) + public Task WriteAsync(OutputFormatterWriteContext context) { var response = context.HttpContext.Response; response.StatusCode = StatusCodes.Status406NotAcceptable; diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs index 0b6c886c7a..967b524fcf 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs @@ -49,21 +49,19 @@ namespace Microsoft.AspNet.Mvc.Formatters /// /// Returns a value indicating whether or not the given type can be written by this serializer. /// - /// The declared type. - /// The runtime type. + /// The object type. /// true if the type can be written, otherwise false. - protected virtual bool CanWriteType(Type declaredType, Type runtimeType) + protected virtual bool CanWriteType(Type type) { return true; } /// public virtual IReadOnlyList GetSupportedContentTypes( - Type declaredType, - Type runtimeType, - MediaTypeHeaderValue contentType) + MediaTypeHeaderValue contentType, + Type objectType) { - if (!CanWriteType(declaredType, runtimeType)) + if (!CanWriteType(objectType)) { return null; } @@ -103,7 +101,7 @@ namespace Microsoft.AspNet.Mvc.Formatters /// The formatter context associated with the call. /// /// The to use when reading the request or writing the response. - public virtual Encoding SelectCharacterEncoding(OutputFormatterContext context) + public virtual Encoding SelectCharacterEncoding(OutputFormatterWriteContext context) { if (context == null) { @@ -112,95 +110,97 @@ namespace Microsoft.AspNet.Mvc.Formatters var request = context.HttpContext.Request; var encoding = MatchAcceptCharacterEncoding(request.GetTypedHeaders().AcceptCharset); - if (encoding == null) + if (encoding != null) { - // Match based on request acceptHeader. - MediaTypeHeaderValue requestContentType = null; - if (MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType) && - !string.IsNullOrEmpty(requestContentType.Charset)) + return encoding; + } + + var charset = context.ContentType?.Charset; + if (charset != null) + { + for (var i = 0; i < SupportedEncodings.Count; i++) { - var requestCharset = requestContentType.Charset; - encoding = SupportedEncodings.FirstOrDefault( - supportedEncoding => requestCharset.Equals(supportedEncoding.WebName)); + if (string.Equals(charset, SupportedEncodings[i].WebName, StringComparison.OrdinalIgnoreCase)) + { + // This is supported. + return context.ContentType.Encoding; + } } } - encoding = encoding ?? SupportedEncodings.FirstOrDefault(); - return encoding; + // A formatter for a non-text media-type won't have any supported encodings. + return SupportedEncodings.Count > 0 ? SupportedEncodings[0] : null; } /// - public virtual bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + public virtual bool CanWriteResult(OutputFormatterCanWriteContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } - - var runtimeType = context.Object == null ? null : context.Object.GetType(); - if (!CanWriteType(context.DeclaredType, runtimeType)) + + if (!CanWriteType(context.ObjectType)) { return false; } - - MediaTypeHeaderValue mediaType = null; - if (contentType == null) + + if (context.ContentType == null) { - // If the desired content type is set to null, the current formatter is free to choose the - // response media type. - mediaType = SupportedMediaTypes.FirstOrDefault(); + // 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]; + return true; + } + else + { + return false; + } } else { // Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*" // requested and formatter supports "text/plain". contentType is typically what we got in an Accept // header. - mediaType = SupportedMediaTypes.FirstOrDefault( - supportedMediaType => supportedMediaType.IsSubsetOf(contentType)); - } - - if (mediaType != null) - { - context.SelectedContentType = mediaType; - return true; + for (var i = 0; i < SupportedMediaTypes.Count; i++) + { + var mediaType = SupportedMediaTypes[i]; + if (mediaType.IsSubsetOf(context.ContentType)) + { + context.ContentType = mediaType; + return true; + } + } } return false; } /// - public Task WriteAsync(OutputFormatterContext context) + public Task WriteAsync(OutputFormatterWriteContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } - WriteResponseHeaders(context); - return WriteResponseBodyAsync(context); - } - - /// - /// Sets the headers on object. - /// - /// The formatter context associated with the call. - public virtual void WriteResponseHeaders(OutputFormatterContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - var selectedMediaType = context.SelectedContentType; - - // If content type is not set then set it based on supported media types. - selectedMediaType = selectedMediaType ?? SupportedMediaTypes.FirstOrDefault(); + var selectedMediaType = context.ContentType; if (selectedMediaType == null) { - throw new InvalidOperationException(Resources.FormatOutputFormatterNoMediaType(GetType().FullName)); + // If content type is not set then set it based on supported media types. + if (SupportedEncodings.Count > 0) + { + selectedMediaType = SupportedMediaTypes[0]; + } + else + { + throw new InvalidOperationException(Resources.FormatOutputFormatterNoMediaType(GetType().FullName)); + } } - // Copy the media type as we don't want it to affect the next request + // 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 @@ -215,16 +215,29 @@ namespace Microsoft.AspNet.Mvc.Formatters var selectedEncoding = SelectCharacterEncoding(context); if (selectedEncoding != null) { - context.SelectedEncoding = selectedEncoding; - // Override the content type value even if one already existed. - selectedMediaType.Charset = selectedEncoding.WebName; + selectedMediaType.Encoding = selectedEncoding; } - context.SelectedContentType = context.SelectedContentType ?? selectedMediaType; + context.ContentType = selectedMediaType; + + WriteResponseHeaders(context); + return WriteResponseBodyAsync(context); + } + + /// + /// Sets the headers on object. + /// + /// The formatter context associated with the call. + public virtual void WriteResponseHeaders(OutputFormatterWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } var response = context.HttpContext.Response; - response.ContentType = selectedMediaType.ToString(); + response.ContentType = context.ContentType?.ToString(); } /// @@ -232,7 +245,7 @@ namespace Microsoft.AspNet.Mvc.Formatters /// /// The formatter context associated with the call. /// A task which can write the response body. - public abstract Task WriteResponseBodyAsync(OutputFormatterContext context); + public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context); private Encoding MatchAcceptCharacterEncoding(IList acceptCharsetHeaders) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/StreamOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/StreamOutputFormatter.cs index 471e6d403d..e1a3e911bd 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/StreamOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/StreamOutputFormatter.cs @@ -5,7 +5,6 @@ using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNet.Http.Features; -using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.Formatters { @@ -15,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.Formatters public class StreamOutputFormatter : IOutputFormatter { /// - public bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + public bool CanWriteResult(OutputFormatterCanWriteContext context) { if (context == null) { @@ -25,7 +24,6 @@ namespace Microsoft.AspNet.Mvc.Formatters // Ignore the passed in content type, if the object is a Stream. if (context.Object is Stream) { - context.SelectedContentType = contentType; return true; } @@ -33,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.Formatters } /// - public async Task WriteAsync(OutputFormatterContext context) + public async Task WriteAsync(OutputFormatterWriteContext context) { if (context == null) { @@ -44,9 +42,9 @@ namespace Microsoft.AspNet.Mvc.Formatters { var response = context.HttpContext.Response; - if (context.SelectedContentType != null) + if (context.ContentType != null) { - response.ContentType = context.SelectedContentType.ToString(); + response.ContentType = context.ContentType.ToString(); } var bufferingFeature = context.HttpContext.Features.Get(); diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs index 27a22e77f3..b230809d50 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Formatters SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/plain").CopyAsReadOnly()); } - public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + public override bool CanWriteResult(OutputFormatterCanWriteContext context) { if (context == null) { @@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // Ignore the passed in content type, if the object is string // always return it as a text/plain format. - if (context.DeclaredType == typeof(string)) + if (context.ObjectType == typeof(string)) { return true; } @@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.Formatters return false; } - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { if (context == null) { @@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.Formatters var response = context.HttpContext.Response; - return response.WriteAsync(valueAsString, context.SelectedEncoding); + return response.WriteAsync(valueAsString, context.ContentType?.Encoding ?? Encoding.UTF8); } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs index bc2d7886b6..ff445e46fc 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs @@ -103,13 +103,13 @@ namespace Microsoft.AspNet.Mvc.Infrastructure formatters = GetDefaultFormatters(); } - var formatterContext = new OutputFormatterContext() + var objectType = result.DeclaredType; + if (objectType == null || objectType == typeof(object)) { - DeclaredType = result.DeclaredType, - HttpContext = context.HttpContext, - Object = result.Value, + objectType = result.Value?.GetType(); }; + var formatterContext = new OutputFormatterWriteContext(context.HttpContext, objectType, result.Value); var selectedFormatter = SelectFormatter(formatterContext, result.ContentTypes, formatters); if (selectedFormatter == null) { @@ -124,7 +124,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure "Selected output formatter '{OutputFormatter}' and content type " + "'{ContentType}' to write the response.", selectedFormatter.GetType().FullName, - formatterContext.SelectedContentType); + formatterContext.ContentType); result.OnFormatting(context); return selectedFormatter.WriteAsync(formatterContext); @@ -133,7 +133,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure /// /// Selects the to write the response. /// - /// The . + /// The . /// /// The list of content types provided by . /// @@ -144,7 +144,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure /// The selected or null if no formatter can write the response. /// protected virtual IOutputFormatter SelectFormatter( - OutputFormatterContext formatterContext, + OutputFormatterWriteContext formatterContext, IList contentTypes, IEnumerable formatters) { @@ -249,7 +249,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure /// Selects the to write the response. The first formatter which /// can write the response should be chosen without any consideration for content type. /// - /// The . + /// The . /// /// The list of instances to consider. /// @@ -257,7 +257,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure /// The selected or null if no formatter can write the response. /// protected virtual IOutputFormatter SelectFormatterNotUsingAcceptHeaders( - OutputFormatterContext formatterContext, + OutputFormatterWriteContext formatterContext, IEnumerable formatters) { if (formatterContext == null) @@ -272,7 +272,8 @@ namespace Microsoft.AspNet.Mvc.Infrastructure foreach (var formatter in formatters) { - if (formatter.CanWriteResult(formatterContext, contentType: null)) + formatterContext.ContentType = null; + if (formatter.CanWriteResult(formatterContext)) { return formatter; } @@ -285,7 +286,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure /// Selects the to write the response based on the content type values /// present in . /// - /// The . + /// The . /// /// The list of instances to consider. /// @@ -296,7 +297,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure /// The selected or null if no formatter can write the response. /// protected virtual IOutputFormatter SelectFormatterUsingSortedAcceptHeaders( - OutputFormatterContext formatterContext, + OutputFormatterWriteContext formatterContext, IEnumerable formatters, IEnumerable sortedAcceptHeaders) { @@ -314,29 +315,27 @@ namespace Microsoft.AspNet.Mvc.Infrastructure { throw new ArgumentNullException(nameof(sortedAcceptHeaders)); } - - IOutputFormatter selectedFormatter = null; + foreach (var contentType in sortedAcceptHeaders) { - // Loop through each of the formatters and see if any one will support this - // mediaType Value. - selectedFormatter = formatters.FirstOrDefault( - formatter => formatter.CanWriteResult(formatterContext, contentType)); - if (selectedFormatter != null) + foreach (var formatter in formatters) { - // we found our match. - break; + formatterContext.ContentType = contentType; + if (formatter.CanWriteResult(formatterContext)) + { + return formatter; + } } } - return selectedFormatter; + return null; } /// /// Selects the to write the response based on the content type values /// present in . /// - /// The . + /// The . /// /// The list of instances to consider. /// @@ -347,7 +346,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure /// The selected or null if no formatter can write the response. /// protected virtual IOutputFormatter SelectFormatterUsingAnyAcceptableContentType( - OutputFormatterContext formatterContext, + OutputFormatterWriteContext formatterContext, IEnumerable formatters, IEnumerable acceptableContentTypes) { @@ -366,15 +365,23 @@ namespace Microsoft.AspNet.Mvc.Infrastructure throw new ArgumentNullException(nameof(acceptableContentTypes)); } - var selectedFormatter = formatters.FirstOrDefault( - formatter => acceptableContentTypes.Any( - contentType => formatter.CanWriteResult(formatterContext, contentType))); + foreach (var formatter in formatters) + { + foreach (var contentType in acceptableContentTypes) + { + formatterContext.ContentType = contentType; + if (formatter.CanWriteResult(formatterContext)) + { + return formatter; + } + } + } - return selectedFormatter; + return null; } private IEnumerable GetSortedAcceptHeaderMediaTypes( - OutputFormatterContext formatterContext) + OutputFormatterWriteContext formatterContext) { var request = formatterContext.HttpContext.Request; var incomingAcceptHeaderMediaTypes = request.GetTypedHeaders().Accept ?? new MediaTypeHeaderValue[] { }; diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs index 5983539333..5706c2c3f7 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs @@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.Formatters return JsonSerializer.Create(SerializerSettings); } - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { if (context == null) { @@ -106,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Formatters } var response = context.HttpContext.Response; - var selectedEncoding = context.SelectedEncoding; + var selectedEncoding = context.ContentType?.Encoding ?? Encoding.UTF8; using (var writer = new HttpResponseStreamWriter(response.Body, selectedEncoding)) { diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs index cef971d825..1f91044824 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using System.Xml; using Microsoft.AspNet.Mvc.Formatters.Xml; using Microsoft.AspNet.Mvc.Formatters.Xml.Internal; +using Microsoft.AspNet.Mvc.Internal; namespace Microsoft.AspNet.Mvc.Formatters { @@ -87,25 +88,6 @@ namespace Microsoft.AspNet.Mvc.Formatters } } - /// - /// Gets the type of the object to be serialized. - /// - /// The declared type. - /// The runtime type. - /// The type of the object to be serialized. - protected virtual Type ResolveType(Type declaredType, Type runtimeType) - { - if (declaredType == null || declaredType == typeof(object)) - { - if (runtimeType != null) - { - return runtimeType; - } - } - - return declaredType; - } - /// /// Gets the type to be serialized. /// @@ -113,16 +95,21 @@ namespace Microsoft.AspNet.Mvc.Formatters /// The original or wrapped type provided by any s. protected virtual Type GetSerializableType(Type type) { - var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( - new WrapperProviderContext(type, isSerialization: true)); + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext( + type, + isSerialization: true)); return wrapperProvider?.WrappingType ?? type; } /// - protected override bool CanWriteType(Type declaredType, Type runtimeType) + protected override bool CanWriteType(Type type) { - var type = ResolveType(declaredType, runtimeType); if (type == null) { return false; @@ -185,41 +172,37 @@ namespace Microsoft.AspNet.Mvc.Formatters } /// - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } - var tempWriterSettings = WriterSettings.Clone(); - tempWriterSettings.Encoding = context.SelectedEncoding; + var writerSettings = WriterSettings.Clone(); + writerSettings.Encoding = context.ContentType?.Encoding ?? Encoding.UTF8; - using (var xmlWriter = CreateXmlWriter(context.HttpContext.Response.Body, tempWriterSettings)) + var value = context.Object; + + using (var xmlWriter = CreateXmlWriter(context.HttpContext.Response.Body, writerSettings)) { - var obj = context.Object; - var runtimeType = obj?.GetType(); - - var resolvedType = ResolveType(context.DeclaredType, runtimeType); - - var wrappingType = GetSerializableType(resolvedType); + var wrappingType = GetSerializableType(context.ObjectType); // Wrap the object only if there is a wrapping type. - if (wrappingType != null && wrappingType != resolvedType) + if (wrappingType != null && wrappingType != context.ObjectType) { - var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( - new WrapperProviderContext( - declaredType: resolvedType, - isSerialization: true)); + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext( + declaredType: context.ObjectType, + isSerialization: true)); - obj = wrapperProvider.Wrap(obj); + value = wrapperProvider.Wrap(value); } var dataContractSerializer = GetCachedSerializer(wrappingType); - dataContractSerializer.WriteObject(xmlWriter, obj); + dataContractSerializer.WriteObject(xmlWriter, value); } - return Task.FromResult(true); + return TaskCache.CompletedTask; } /// diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs index a6e822eeb4..6e4c08d5c5 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs @@ -11,6 +11,7 @@ using System.Xml; using System.Xml.Serialization; using Microsoft.AspNet.Mvc.Formatters.Xml; using Microsoft.AspNet.Mvc.Formatters.Xml.Internal; +using Microsoft.AspNet.Mvc.Internal; namespace Microsoft.AspNet.Mvc.Formatters { @@ -66,25 +67,6 @@ namespace Microsoft.AspNet.Mvc.Formatters /// public XmlWriterSettings WriterSettings { get; } - /// - /// Gets the type of the object to be serialized. - /// - /// The declared type of the object. - /// The runtime type of the object - /// A type that needs to be serialized. - protected virtual Type ResolveType(Type declaredType, Type runtimeType) - { - if (declaredType == null || declaredType == typeof(object)) - { - if (runtimeType != null) - { - return runtimeType; - } - } - - return declaredType; - } - /// /// Gets the type to be serialized. /// @@ -92,16 +74,21 @@ namespace Microsoft.AspNet.Mvc.Formatters /// The original or wrapped type provided by any . protected virtual Type GetSerializableType(Type type) { - var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( - new WrapperProviderContext(type, isSerialization: true)); + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext( + type, + isSerialization: true)); return wrapperProvider?.WrappingType ?? type; } /// - protected override bool CanWriteType(Type declaredType, Type runtimeType) + protected override bool CanWriteType(Type type) { - var type = ResolveType(declaredType, runtimeType); if (type == null) { return false; @@ -160,7 +147,7 @@ namespace Microsoft.AspNet.Mvc.Formatters } /// - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { if (context == null) { @@ -169,34 +156,30 @@ namespace Microsoft.AspNet.Mvc.Formatters var response = context.HttpContext.Response; - var tempWriterSettings = WriterSettings.Clone(); - tempWriterSettings.Encoding = context.SelectedEncoding; + var writerSettings = WriterSettings.Clone(); + writerSettings.Encoding = context.ContentType.Encoding ?? Encoding.UTF8; - using (var xmlWriter = CreateXmlWriter(context.HttpContext.Response.Body, tempWriterSettings)) + var value = context.Object; + + using (var xmlWriter = CreateXmlWriter(context.HttpContext.Response.Body, writerSettings)) { - var obj = context.Object; - var runtimeType = obj?.GetType(); - - var resolvedType = ResolveType(context.DeclaredType, runtimeType); - - var wrappingType = GetSerializableType(resolvedType); + var wrappingType = GetSerializableType(context.ObjectType); // Wrap the object only if there is a wrapping type. - if (wrappingType != null && wrappingType != resolvedType) + if (wrappingType != null && wrappingType != context.ObjectType) { - var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( - new WrapperProviderContext( - declaredType: resolvedType, - isSerialization: true)); + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext( + declaredType: wrappingType, + isSerialization: true)); - obj = wrapperProvider.Wrap(obj); + value = wrapperProvider.Wrap(value); } var xmlSerializer = GetCachedSerializer(wrappingType); - xmlSerializer.Serialize(xmlWriter, obj); + xmlSerializer.Serialize(xmlWriter, value); } - return Task.FromResult(true); + return TaskCache.CompletedTask; } /// diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs index 35d0aa52dd..cea1ebdd7b 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs @@ -14,12 +14,12 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { public class HttpResponseMessageOutputFormatter : IOutputFormatter { - public bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + public bool CanWriteResult(OutputFormatterCanWriteContext context) { return context.Object is HttpResponseMessage; } - public async Task WriteAsync(OutputFormatterContext context) + public async Task WriteAsync(OutputFormatterWriteContext context) { var response = context.HttpContext.Response; diff --git a/test/Microsoft.AspNet.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNet.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index 6428aceb5a..5c22fcc4eb 100644 --- a/test/Microsoft.AspNet.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -1360,24 +1360,24 @@ namespace Microsoft.AspNet.Mvc.Description { public List SupportedTypes { get; } = new List(); - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { throw new NotImplementedException(); } - protected override bool CanWriteType(Type declaredType, Type actualType) + protected override bool CanWriteType(Type type) { if (SupportedTypes.Count == 0) { return true; } - else if ((actualType ?? declaredType) == null) + else if (type == null) { return false; } else { - return SupportedTypes.Contains(actualType ?? declaredType); + return SupportedTypes.Contains(type); } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Controllers/ControllerActionInvokerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Controllers/ControllerActionInvokerTest.cs index 302bb1a239..62e852c536 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Controllers/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Controllers/ControllerActionInvokerTest.cs @@ -1988,12 +1988,12 @@ namespace Microsoft.AspNet.Mvc.Controllers var formatter = new Mock(); formatter - .Setup(f => f.CanWriteResult(It.IsAny(), It.IsAny())) + .Setup(f => f.CanWriteResult(It.IsAny())) .Returns(true); formatter - .Setup(f => f.WriteAsync(It.IsAny())) - .Returns(async c => + .Setup(f => f.WriteAsync(It.IsAny())) + .Returns(async c => { await c.HttpContext.Response.WriteAsync(c.Object.ToString()); }); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs index cf0ad2e578..8e1f3d49a0 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.Formatters private class TestOutputFormatter : OutputFormatter { - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { throw new NotImplementedException(); } @@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.Formatters private class AnotherTestOutputFormatter : OutputFormatter { - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { throw new NotImplementedException(); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/HttpNotAcceptableOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/HttpNotAcceptableOutputFormatterTest.cs index 9260fec2c8..f355b79924 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/HttpNotAcceptableOutputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/HttpNotAcceptableOutputFormatterTest.cs @@ -13,18 +13,18 @@ namespace Microsoft.AspNet.Mvc.Formatters [Theory] [InlineData(false)] [InlineData(null)] - public void CanWriteResult_ReturnsFalse_WhenConnegHasntFailed(bool? connegFailedValue) + public void CanWriteResult_ReturnsFalse_WhenConnegHasntFailed(bool? failedContentNegotiation) { // Arrange var formatter = new HttpNotAcceptableOutputFormatter(); - var context = new OutputFormatterContext() + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null) { - FailedContentNegotiation = connegFailedValue, + FailedContentNegotiation = failedContentNegotiation, }; // Act - var result = formatter.CanWriteResult(context, contentType: null); + var result = formatter.CanWriteResult(context); // Assert Assert.False(result); @@ -36,13 +36,13 @@ namespace Microsoft.AspNet.Mvc.Formatters // Arrange var formatter = new HttpNotAcceptableOutputFormatter(); - var context = new OutputFormatterContext() + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null) { FailedContentNegotiation = true, }; // Act - var result = formatter.CanWriteResult(context, contentType: null); + var result = formatter.CanWriteResult(context); // Assert Assert.True(result); @@ -54,10 +54,7 @@ namespace Microsoft.AspNet.Mvc.Formatters // Arrange var formatter = new HttpNotAcceptableOutputFormatter(); - var context = new OutputFormatterContext() - { - HttpContext = new DefaultHttpContext(), - }; + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null); // Act await formatter.WriteAsync(context); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs index 19445584ff..a43377e867 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs @@ -40,18 +40,18 @@ namespace Microsoft.AspNet.Mvc.Formatters bool useNonNullContentType) { // Arrange - var typeToUse = declaredTypeAsString ? typeof(string) : typeof(object); - var formatterContext = new OutputFormatterContext() - { - Object = value, - DeclaredType = typeToUse, - HttpContext = null, - }; + var type = declaredTypeAsString ? typeof(string) : typeof(object); var contentType = useNonNullContentType ? MediaTypeHeaderValue.Parse("text/plain") : null; + + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), type, value) + { + ContentType = contentType, + }; + var formatter = new HttpNoContentOutputFormatter(); // Act - var result = formatter.CanWriteResult(formatterContext, contentType); + var result = formatter.CanWriteResult(context); // Assert Assert.Equal(expected, result); @@ -63,17 +63,18 @@ namespace Microsoft.AspNet.Mvc.Formatters public void CanWriteResult_ReturnsTrue_IfReturnTypeIsVoidOrTask(Type declaredType) { // Arrange - var formatterContext = new OutputFormatterContext() + var context = new OutputFormatterWriteContext( + new DefaultHttpContext(), + declaredType, + "Something non null.") { - Object = "Something non null.", - DeclaredType = declaredType, - HttpContext = null, + ContentType = MediaTypeHeaderValue.Parse("text/plain"), }; - var contentType = MediaTypeHeaderValue.Parse("text/plain"); + var formatter = new HttpNoContentOutputFormatter(); // Act - var result = formatter.CanWriteResult(formatterContext, contentType); + var result = formatter.CanWriteResult(context); // Assert Assert.True(result); @@ -89,21 +90,21 @@ namespace Microsoft.AspNet.Mvc.Formatters bool expected) { // Arrange - var formatterContext = new OutputFormatterContext() + var context = new OutputFormatterWriteContext( + new DefaultHttpContext(), + typeof(string), + value) { - Object = value, - DeclaredType = typeof(string), - HttpContext = null, + ContentType = MediaTypeHeaderValue.Parse("text/plain"), }; - var contentType = MediaTypeHeaderValue.Parse("text/plain"); var formatter = new HttpNoContentOutputFormatter() { TreatNullValueAsNoContent = treatNullValueAsNoContent }; // Act - var result = formatter.CanWriteResult(formatterContext, contentType); + var result = formatter.CanWriteResult(context); // Assert Assert.Equal(expected, result); @@ -113,20 +114,15 @@ namespace Microsoft.AspNet.Mvc.Formatters public async Task WriteAsync_WritesTheStatusCode204() { // Arrange - var httpContext = new DefaultHttpContext(); - - var formatterContext = new OutputFormatterContext() - { - HttpContext = httpContext, - }; + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), typeof(string), @object: null); var formatter = new HttpNoContentOutputFormatter(); // Act - await formatter.WriteAsync(formatterContext); + await formatter.WriteAsync(context); // Assert - Assert.Equal(StatusCodes.Status204NoContent, httpContext.Response.StatusCode); + Assert.Equal(StatusCodes.Status204NoContent, context.HttpContext.Response.StatusCode); } [Fact] @@ -136,15 +132,12 @@ namespace Microsoft.AspNet.Mvc.Formatters var httpContext = new DefaultHttpContext(); httpContext.Response.StatusCode = StatusCodes.Status201Created; - var formatterContext = new OutputFormatterContext() - { - HttpContext = httpContext, - }; + var context = new OutputFormatterWriteContext(httpContext, typeof(string), @object: null); var formatter = new HttpNoContentOutputFormatter(); // Act - await formatter.WriteAsync(formatterContext); + await formatter.WriteAsync(context); // Assert Assert.Equal(StatusCodes.Status201Created, httpContext.Response.StatusCode); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs index f5f58a8712..f025646edb 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs @@ -20,22 +20,16 @@ namespace Microsoft.AspNet.Mvc.Formatters get { // string acceptEncodings, string requestEncoding, string[] supportedEncodings, string expectedEncoding - yield return new object[] { "", null, 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[] { "", new string[] { "utf-8", "utf-16" }, "utf-8" }; - yield return new object[] { "utf-8", null, new string[] { "utf-8", "utf-16" }, "utf-8" }; - yield return new object[] { "utf-16", "utf-8", new string[] { "utf-8", "utf-16" }, "utf-16" }; - yield return new object[] { "utf-16; q=0.5", "utf-8", new string[] { "utf-8", "utf-16" }, "utf-16" }; + 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", null, new string[] { "utf-8", "utf-16" }, "utf-8" }; - yield return new object[] { "utf-8; q=0.0", "utf-16", new string[] { "utf-8", "utf-16" }, "utf-16" }; - yield return new object[] - { "utf-8; q=0.0, utf-16; q=0.0", "utf-16", new string[] { "utf-8", "utf-16" }, "utf-16" }; - yield return new object[] - { "utf-8; q=0.0, utf-16; q=0.0", null, new string[] { "utf-8", "utf-16" }, "utf-8" }; + 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", null, new string[] { "utf-8", "utf-16" }, "utf-8" }; - yield return new object[] { "*; q=0.0", "utf-16", new string[] { "utf-8", "utf-16" }, "utf-16" }; + yield return new object[] { "*; q=0.0", new string[] { "utf-8", "utf-16" }, "utf-8" }; } } @@ -43,16 +37,15 @@ namespace Microsoft.AspNet.Mvc.Formatters [MemberData(nameof(SelectResponseCharacterEncodingData))] public void SelectResponseCharacterEncoding_SelectsEncoding( string acceptCharsetHeaders, - string requestEncoding, string[] supportedEncodings, string expectedEncoding) { // Arrange - var mockHttpContext = new Mock(); + var httpContext = new Mock(); var httpRequest = new DefaultHttpContext().Request; - httpRequest.Headers["Accept-Charset"] = acceptCharsetHeaders; - httpRequest.ContentType = "application/acceptCharset;charset=" + requestEncoding; - mockHttpContext.SetupGet(o => o.Request).Returns(httpRequest); + 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) @@ -60,15 +53,13 @@ namespace Microsoft.AspNet.Mvc.Formatters formatter.SupportedEncodings.Add(Encoding.GetEncoding(supportedEncoding)); } - var formatterContext = new OutputFormatterContext() + var context = new OutputFormatterWriteContext(httpContext.Object, typeof(string), "someValue") { - Object = "someValue", - HttpContext = mockHttpContext.Object, - DeclaredType = typeof(string) + ContentType = MediaTypeHeaderValue.Parse(httpRequest.Headers[HeaderNames.Accept]), }; // Act - var actualEncoding = formatter.SelectCharacterEncoding(formatterContext); + var actualEncoding = formatter.SelectCharacterEncoding(context); // Assert Assert.Equal(Encoding.GetEncoding(expectedEncoding), actualEncoding); @@ -86,43 +77,20 @@ namespace Microsoft.AspNet.Mvc.Formatters formatter.SupportedMediaTypes.Clear(); formatter.SupportedMediaTypes.Add(testContentType); - var formatterContext = new OutputFormatterContext() + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null) { - HttpContext = new DefaultHttpContext(), + ContentType = testContentType, }; // Act - formatter.WriteResponseHeaders(formatterContext); + formatter.WriteResponseHeaders(context); // Assert - Assert.Null(formatterContext.SelectedEncoding); - Assert.Equal(testContentType, formatterContext.SelectedContentType); + Assert.Null(context.ContentType.Encoding); + Assert.Equal(testContentType, context.ContentType); // If we had set an encoding, it would be part of the content type header - Assert.Equal(testContentType, formatterContext.HttpContext.Response.GetTypedHeaders().ContentType); - } - - [Fact] - public void WriteResponseContentHeaders_NoSelectedContentType_SetsOutputFormatterContext() - { - // Arrange - var testFormatter = new DoesNotSetContext(); - var testContentType = MediaTypeHeaderValue.Parse("application/doesNotSetContext"); - var formatterContext = new OutputFormatterContext(); - var mockHttpContext = new Mock(); - var httpRequest = new DefaultHttpContext().Request; - mockHttpContext.SetupGet(o => o.Request).Returns(httpRequest); - mockHttpContext.SetupProperty(o => o.Response.ContentType); - formatterContext.HttpContext = mockHttpContext.Object; - - // Act - testFormatter.WriteResponseHeaders(formatterContext); - - // Assert - Assert.Equal(Encoding.Unicode.WebName, formatterContext.SelectedEncoding.WebName); - Assert.Equal(Encoding.Unicode, formatterContext.SelectedEncoding); - Assert.Equal("application/doesNotSetContext; charset=" + Encoding.Unicode.WebName, - formatterContext.SelectedContentType.ToString()); + Assert.Equal(testContentType, context.HttpContext.Response.GetTypedHeaders().ContentType); } [Fact] @@ -133,31 +101,31 @@ namespace Microsoft.AspNet.Mvc.Formatters formatter.SupportedMediaTypes.Clear(); var mediaType = new MediaTypeHeaderValue("image/png"); formatter.SupportedMediaTypes.Add(mediaType); - var formatterContext = new OutputFormatterContext(); - formatterContext.HttpContext = new DefaultHttpContext(); + + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null); // Act - await formatter.WriteAsync(formatterContext); + await formatter.WriteAsync(context); // Assert - Assert.NotSame(mediaType, formatterContext.SelectedContentType); + Assert.NotSame(mediaType, context.ContentType); Assert.Null(mediaType.Charset); - Assert.Equal("image/png; charset=utf-8", formatterContext.SelectedContentType.ToString()); + Assert.Equal("image/png; charset=utf-8", context.ContentType.ToString()); } [Fact] public void CanWriteResult_ForNullContentType_UsesFirstEntryInSupportedContentTypes() { // Arrange - var context = new OutputFormatterContext(); + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null); var formatter = new TestOutputFormatter(); // Act - var result = formatter.CanWriteResult(context, null); + var result = formatter.CanWriteResult(context); // Assert Assert.True(result); - Assert.Equal(formatter.SupportedMediaTypes[0].ToString(), context.SelectedContentType.ToString()); + Assert.Equal(formatter.SupportedMediaTypes[0].ToString(), context.ContentType.ToString()); } [Fact] @@ -165,16 +133,11 @@ namespace Microsoft.AspNet.Mvc.Formatters { // Arrange var formatter = new TypeSpecificFormatter(); - formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json")); - formatter.SupportedTypes.Add(typeof(int)); // Act - var contentTypes = formatter.GetSupportedContentTypes( - declaredType: typeof(string), - runtimeType: typeof(string), - contentType: null); + var contentTypes = formatter.GetSupportedContentTypes(contentType: null, objectType: typeof(string)); // Assert Assert.Null(contentTypes); @@ -184,18 +147,17 @@ namespace Microsoft.AspNet.Mvc.Formatters public void CanWrite_ReturnsFalse_ForUnsupportedType() { // Arrange - var context = new OutputFormatterContext(); - context.DeclaredType = typeof(string); - context.Object = "Hello, world!"; - var formatter = new TypeSpecificFormatter(); - formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json")); - formatter.SupportedTypes.Add(typeof(int)); + var context = new OutputFormatterWriteContext(new DefaultHttpContext(),typeof(string), "Hello, world!") + { + ContentType = formatter.SupportedMediaTypes[0], + }; + // Act - var result = formatter.CanWriteResult(context, formatter.SupportedMediaTypes[0]); + var result = formatter.CanWriteResult(context); // Assert Assert.False(result); @@ -206,13 +168,12 @@ namespace Microsoft.AspNet.Mvc.Formatters { // Arrange var formatter = new TestOutputFormatter(); - formatter.SupportedMediaTypes.Clear(); formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json")); formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml")); // Act - var contentTypes = formatter.GetSupportedContentTypes(typeof(int), typeof(int), contentType: null); + var contentTypes = formatter.GetSupportedContentTypes(contentType: null, objectType: typeof(string)); // Assert Assert.Equal(2, contentTypes.Count); @@ -232,9 +193,8 @@ namespace Microsoft.AspNet.Mvc.Formatters // Act var contentTypes = formatter.GetSupportedContentTypes( - typeof(int), - typeof(int), - contentType: MediaTypeHeaderValue.Parse("application/*")); + MediaTypeHeaderValue.Parse("application/*"), + typeof(int)); // Assert var contentType = Assert.Single(contentTypes); @@ -253,9 +213,8 @@ namespace Microsoft.AspNet.Mvc.Formatters // Act var contentTypes = formatter.GetSupportedContentTypes( - typeof(int), - typeof(int), - contentType: MediaTypeHeaderValue.Parse("application/xml")); + MediaTypeHeaderValue.Parse("application/xml"), + typeof(int)); // Assert Assert.Null(contentTypes); @@ -271,10 +230,7 @@ namespace Microsoft.AspNet.Mvc.Formatters formatter.SupportedMediaTypes.Clear(); // Act - var contentTypes = formatter.GetSupportedContentTypes( - typeof(int), - typeof(int), - contentType: null); + var contentTypes = formatter.GetSupportedContentTypes(contentType: null, objectType: typeof(int)); // Assert Assert.Null(contentTypes); @@ -284,12 +240,12 @@ namespace Microsoft.AspNet.Mvc.Formatters { public List SupportedTypes { get; } = new List(); - protected override bool CanWriteType(Type declaredType, Type runtimeType) + protected override bool CanWriteType(Type type) { - return SupportedTypes.Contains(declaredType ?? runtimeType); + return SupportedTypes.Contains(type); } - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { throw new NotImplementedException(); } @@ -302,7 +258,7 @@ namespace Microsoft.AspNet.Mvc.Formatters SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/acceptCharset")); } - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { return Task.FromResult(true); } @@ -316,14 +272,14 @@ namespace Microsoft.AspNet.Mvc.Formatters SupportedEncodings.Add(Encoding.Unicode); } - public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + 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(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { return Task.FromResult(true); } @@ -337,7 +293,7 @@ namespace Microsoft.AspNet.Mvc.Formatters SupportedEncodings.Add(Encoding.UTF8); } - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { return Task.FromResult(true); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs index 53aeb48d3b..7f529e653e 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs @@ -17,46 +17,40 @@ namespace Microsoft.AspNet.Mvc.Formatters [Theory] [InlineData(typeof(Stream), "text/plain")] [InlineData(typeof(Stream), null)] - [InlineData(typeof(object), "text/plain")] - [InlineData(typeof(object), null)] - [InlineData(typeof(IActionResult), "text/plain")] - [InlineData(typeof(IActionResult), null)] - public void CanWriteResult_ReturnsTrue_ForStreams(Type declaredType, string contentType) + public void CanWriteResult_ReturnsTrue_ForStreams(Type type, string contentType) { // Arrange var formatter = new StreamOutputFormatter(); var contentTypeHeader = contentType == null ? null : new MediaTypeHeaderValue(contentType); - var formatterContext = new OutputFormatterContext() + + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), type, new MemoryStream()) { - DeclaredType = declaredType, - Object = new MemoryStream() + ContentType = contentTypeHeader, }; // Act - var canWrite = formatter.CanWriteResult(formatterContext, contentTypeHeader); + var canWrite = formatter.CanWriteResult(context); // Assert Assert.True(canWrite); } [Theory] - [InlineData(typeof(object), "text/plain")] - [InlineData(typeof(object), null)] [InlineData(typeof(SimplePOCO), "text/plain")] [InlineData(typeof(SimplePOCO), null)] - public void CanWriteResult_OnlyActsOnStreams_IgnoringContentType(Type declaredType, string contentType) + public void CanWriteResult_OnlyActsOnStreams_IgnoringContentType(Type type, string contentType) { // Arrange var formatter = new StreamOutputFormatter(); var contentTypeHeader = contentType == null ? null : new MediaTypeHeaderValue(contentType); - var formatterContext = new OutputFormatterContext() + + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), type, new SimplePOCO()) { - DeclaredType = declaredType, - Object = new SimplePOCO() + ContentType = contentTypeHeader, }; // Act - var canWrite = formatter.CanWriteResult(formatterContext, contentTypeHeader); + var canWrite = formatter.CanWriteResult(context); // Assert Assert.False(canWrite); @@ -70,56 +64,39 @@ namespace Microsoft.AspNet.Mvc.Formatters { // Arrange var formatter = new StreamOutputFormatter(); - var context = new OutputFormatterContext(); - var contentType = new MediaTypeHeaderValue("text/plain"); + var @object = type != null ? Activator.CreateInstance(type) : null; - context.Object = type != null ? Activator.CreateInstance(type) : null; + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), type, @object); // Act - var result = formatter.CanWriteResult(context, contentType); + var result = formatter.CanWriteResult(context); // Assert Assert.False(result); - Assert.Null(context.SelectedContentType); - } - - [Fact] - public void CanWriteResult_SetsContentType() - { - // Arrange - var formatter = new StreamOutputFormatter(); - var contentType = new MediaTypeHeaderValue("text/plain"); - var context = new OutputFormatterContext(); - context.Object = new MemoryStream(); - - // Act - var result = formatter.CanWriteResult(context, contentType); - - // Assert - Assert.True(result); - Assert.Same(contentType, context.SelectedContentType); } [Fact] public async Task DisablesResponseBuffering_IfBufferingFeatureAvailable() { // Arrange - var expected = Encoding.UTF8.GetBytes("Test data"); var formatter = new StreamOutputFormatter(); + + var expected = Encoding.UTF8.GetBytes("Test data"); + var httpContext = new DefaultHttpContext(); - var ms = new MemoryStream(); - httpContext.Response.Body = ms; + var body = new MemoryStream(); + httpContext.Response.Body = body; + var bufferingFeature = new TestBufferingFeature(); httpContext.Features.Set(bufferingFeature); - var context = new OutputFormatterContext(); - context.Object = new MemoryStream(expected); - context.HttpContext = httpContext; + + var context = new OutputFormatterWriteContext(httpContext, typeof(Stream), new MemoryStream(expected)); // Act await formatter.WriteAsync(context); // Assert - Assert.Equal(expected, ms.ToArray()); + Assert.Equal(expected, body.ToArray()); Assert.True(bufferingFeature.DisableResponseBufferingInvoked); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs index f0161fd30e..a710922e31 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Internal; using Moq; using Xunit; @@ -28,19 +29,18 @@ namespace Microsoft.AspNet.Mvc.Formatters [Theory] [MemberData(nameof(OutputFormatterContextValues))] - public void CanWriteResult_ReturnsTrueForStringTypes(object value, bool useDeclaredTypeAsString, bool expectedCanWriteResult) + public void CanWriteResult_ReturnsTrueForStringTypes( + object value, + bool useDeclaredTypeAsString, + bool expectedCanWriteResult) { // Arrange var formatter = new StringOutputFormatter(); - var typeToUse = useDeclaredTypeAsString ? typeof(string) : typeof(object); - var formatterContext = new OutputFormatterContext() - { - Object = value, - DeclaredType = typeToUse - }; + var type = useDeclaredTypeAsString ? typeof(string) : typeof(object); + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), type, value); // Act - var result = formatter.CanWriteResult(formatterContext, null); + var result = formatter.CanWriteResult(context); // Assert Assert.Equal(expectedCanWriteResult, result); @@ -55,20 +55,14 @@ namespace Microsoft.AspNet.Mvc.Formatters var response = new Mock(); response.SetupProperty(o => o.ContentLength); response.SetupGet(r => r.Body).Returns(memoryStream); - var mockHttpContext = new Mock(); - mockHttpContext.Setup(o => o.Response).Returns(response.Object); + var httpContext = new Mock(); + httpContext.Setup(o => o.Response).Returns(response.Object); var formatter = new StringOutputFormatter(); - var formatterContext = new OutputFormatterContext() - { - Object = null, - DeclaredType = typeof(string), - HttpContext = mockHttpContext.Object, - SelectedEncoding = encoding - }; + var context = new OutputFormatterWriteContext(httpContext.Object, typeof(string), @object: null); // Act - await formatter.WriteResponseBodyAsync(formatterContext); + await formatter.WriteResponseBodyAsync(context); // Assert Assert.Equal(0, memoryStream.Length); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs index 3736df1d27..21ec92c7df 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs @@ -31,10 +31,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure new TestJsonOutputFormatter(), // This will be chosen based on the accept header }; - var context = new OutputFormatterContext() - { - HttpContext = new DefaultHttpContext(), - }; + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null); context.HttpContext.Request.Headers[HeaderNames.Accept] = "application/json"; // Act @@ -45,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Assert Assert.Same(formatters[1], formatter); - Assert.Equal(new MediaTypeHeaderValue("application/json"), context.SelectedContentType); + Assert.Equal(new MediaTypeHeaderValue("application/json"), context.ContentType); } [Fact] @@ -60,10 +57,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure new TestJsonOutputFormatter(), // This will be chosen based on the content type }; - var context = new OutputFormatterContext() - { - HttpContext = new DefaultHttpContext(), - }; + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null); context.HttpContext.Request.Headers[HeaderNames.Accept] = "application/xml"; // This will not be used // Act @@ -74,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Assert Assert.Same(formatters[1], formatter); - Assert.Equal(new MediaTypeHeaderValue("application/json"), context.SelectedContentType); + Assert.Equal(new MediaTypeHeaderValue("application/json"), context.ContentType); } [Fact] @@ -88,10 +82,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure new TestXmlOutputFormatter(), }; - var context = new OutputFormatterContext() - { - HttpContext = new DefaultHttpContext(), - }; + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null); context.HttpContext.Request.Headers[HeaderNames.Accept] = "application/xml"; // This will not be used // Act @@ -158,10 +149,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure new TestJsonOutputFormatter(), }; - var context = new OutputFormatterContext() - { - HttpContext = new DefaultHttpContext(), - }; + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null); context.HttpContext.Request.Headers[HeaderNames.Accept] = acceptHeader; // Act @@ -172,7 +160,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Assert Assert.Same(formatters[1], formatter); - Assert.Equal(new MediaTypeHeaderValue(expectedContentType), context.SelectedContentType); + Assert.Equal(new MediaTypeHeaderValue(expectedContentType), context.ContentType); } [Fact] @@ -188,10 +176,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure new TestXmlOutputFormatter(), }; - var context = new OutputFormatterContext() - { - HttpContext = new DefaultHttpContext(), - }; + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null); // Act var formatter = executor.SelectFormatter( @@ -201,7 +186,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Assert Assert.Same(formatters[1], formatter); - Assert.Equal(new MediaTypeHeaderValue("application/json"), context.SelectedContentType); + Assert.Equal(new MediaTypeHeaderValue("application/json"), context.ContentType); Assert.Null(context.FailedContentNegotiation); } @@ -217,10 +202,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure new TestJsonOutputFormatter(), }; - var context = new OutputFormatterContext() - { - HttpContext = new DefaultHttpContext(), - }; + var context = new OutputFormatterWriteContext(new DefaultHttpContext(), objectType: null, @object: null); context.HttpContext.Request.Headers[HeaderNames.Accept] = "text/custom, application/custom"; // Act @@ -231,7 +213,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure // Assert Assert.Same(formatters[0], formatter); - Assert.Equal(new MediaTypeHeaderValue("application/xml"), context.SelectedContentType); + Assert.Equal(new MediaTypeHeaderValue("application/xml"), context.ContentType); Assert.True(context.FailedContentNegotiation); } @@ -437,12 +419,12 @@ namespace Microsoft.AspNet.Mvc.Infrastructure private class CannotWriteFormatter : IOutputFormatter { - public virtual bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + public virtual bool CanWriteResult(OutputFormatterCanWriteContext context) { return false; } - public virtual Task WriteAsync(OutputFormatterContext context) + public virtual Task WriteAsync(OutputFormatterWriteContext context) { throw new NotImplementedException(); } @@ -458,7 +440,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure SupportedEncodings.Add(Encoding.UTF8); } - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { return Task.FromResult(0); } @@ -474,7 +456,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure SupportedEncodings.Add(Encoding.UTF8); } - public override Task WriteResponseBodyAsync(OutputFormatterContext context) + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { return Task.FromResult(0); } @@ -491,7 +473,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure } public new IOutputFormatter SelectFormatter( - OutputFormatterContext formatterContext, + OutputFormatterWriteContext formatterContext, IList contentTypes, IEnumerable formatters) { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ObjectResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ObjectResultTests.cs index af395781da..103d95085d 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ObjectResultTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ObjectResultTests.cs @@ -74,12 +74,12 @@ namespace Microsoft.AspNet.Mvc private class NoOpOutputFormatter : IOutputFormatter { - public bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + public bool CanWriteResult(OutputFormatterCanWriteContext context) { return true; } - public Task WriteAsync(OutputFormatterContext context) + public Task WriteAsync(OutputFormatterWriteContext context) { return Task.FromResult(0); } diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs index 01fe079c13..3155b0461e 100644 --- a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs @@ -165,19 +165,20 @@ namespace Microsoft.AspNet.Mvc.Formatters // Arrange var formatter = new JsonOutputFormatter(); var formattedContent = "\"" + content + "\""; - var mediaType = string.Format("application/json; charset={0}", encodingAsString); + var mediaType = MediaTypeHeaderValue.Parse(string.Format("application/json; charset={0}", encodingAsString)); var encoding = CreateOrGetSupportedEncoding(formatter, encodingAsString, isDefaultEncoding); var expectedData = encoding.GetBytes(formattedContent); var body = new MemoryStream(); - var actionContext = GetActionContext(MediaTypeHeaderValue.Parse(mediaType), body); - var outputFormatterContext = new OutputFormatterContext + var actionContext = GetActionContext(mediaType, body); + + var outputFormatterContext = new OutputFormatterWriteContext( + actionContext.HttpContext, + typeof(string), + content) { - Object = content, - DeclaredType = typeof(string), - HttpContext = actionContext.HttpContext, - SelectedEncoding = encoding + ContentType = mediaType, }; // Act @@ -208,7 +209,7 @@ namespace Microsoft.AspNet.Mvc.Formatters return encoding; } - private static OutputFormatterContext GetOutputFormatterContext( + private static OutputFormatterWriteContext GetOutputFormatterContext( object outputValue, Type outputType, string contentType = "application/xml; charset=utf-8", @@ -217,12 +218,9 @@ namespace Microsoft.AspNet.Mvc.Formatters var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType); var actionContext = GetActionContext(mediaTypeHeaderValue, responseStream); - return new OutputFormatterContext + return new OutputFormatterWriteContext(actionContext.HttpContext, outputType, outputValue) { - Object = outputValue, - DeclaredType = outputType, - HttpContext = actionContext.HttpContext, - SelectedEncoding = Encoding.GetEncoding(mediaTypeHeaderValue.Charset) + ContentType = mediaTypeHeaderValue, }; } diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs index dd8294fb33..3f7b03c8db 100644 --- a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs @@ -100,7 +100,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml { // Arrange var formatter = new XmlDataContractSerializerOutputFormatter(); - var outputFormatterContext = GetOutputFormatterContext(input, typeof(object)); + var outputFormatterContext = GetOutputFormatterContext(input, input.GetType()); // Act await formatter.WriteAsync(outputFormatterContext); @@ -121,11 +121,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml // Arrange var input = new DummyClass { SampleInt = 10 }; var formatter = new TestXmlDataContractSerializerOutputFormatter(); + var context = GetOutputFormatterContext(input, typeof(DummyClass)); + context.ContentType = MediaTypeHeaderValue.Parse("application/xml"); // Act - formatter.CanWriteResult(context, MediaTypeHeaderValue.Parse("application/xml")); - formatter.CanWriteResult(context, MediaTypeHeaderValue.Parse("application/xml")); + formatter.CanWriteResult(context); + formatter.CanWriteResult(context); // Assert Assert.Equal(1, formatter.createSerializerCalledCount); @@ -363,9 +365,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml { yield return new object[] { null, typeof(string), true }; yield return new object[] { null, null, false }; - yield return new object[] { new DummyClass { SampleInt = 5 }, null, true }; - yield return new object[] { new DummyClass { SampleInt = 5 }, typeof(object), true }; - yield return new object[] { null, typeof(object), true }; + yield return new object[] { new DummyClass { SampleInt = 5 }, typeof(DummyClass), true }; yield return new object[] { new Dictionary { { "Hello", "world" } }, typeof(object), true }; yield return new object[] { @@ -382,9 +382,10 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml // Arrange var formatter = new XmlDataContractSerializerOutputFormatter(); var outputFormatterContext = GetOutputFormatterContext(input, declaredType); + outputFormatterContext.ContentType = MediaTypeHeaderValue.Parse("application/xml"); // Act - var result = formatter.CanWriteResult(outputFormatterContext, MediaTypeHeaderValue.Parse("application/xml")); + var result = formatter.CanWriteResult(outputFormatterContext); // Assert Assert.Equal(expectedOutput, result); @@ -394,12 +395,9 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml { get { - yield return new object[] { typeof(DummyClass), typeof(DummyClass), "application/xml" }; - yield return new object[] { typeof(DummyClass), typeof(object), "application/xml" }; - yield return new object[] { null, typeof(DummyClass), "application/xml" }; - yield return new object[] { typeof(DummyClass), null, "application/xml" }; - yield return new object[] { typeof(object), null, "application/xml" }; - yield return new object[] { null, null, null }; + yield return new object[] { typeof(DummyClass), "application/xml" }; + yield return new object[] { typeof(object), "application/xml" }; + yield return new object[] { null, null }; } } @@ -407,17 +405,15 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml // Mono issue - https://github.com/aspnet/External/issues/18 [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [MemberData(nameof(TypesForGetSupportedContentTypes))] - public void GetSupportedContentTypes_ReturnsSupportedTypes( - Type declaredType, - Type runtimeType, - object expectedOutput) + public void GetSupportedContentTypes_ReturnsSupportedTypes(Type type, object expectedOutput) { // Arrange var formatter = new XmlDataContractSerializerOutputFormatter(); // Act var result = formatter.GetSupportedContentTypes( - declaredType, runtimeType, MediaTypeHeaderValue.Parse("application/xml")); + MediaTypeHeaderValue.Parse("application/xml"), + type); // Assert if (expectedOutput != null) @@ -598,17 +594,12 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml XmlAssert.Equal(expectedOutput, content); } - private OutputFormatterContext GetOutputFormatterContext( + private OutputFormatterWriteContext GetOutputFormatterContext( object outputValue, Type outputType, string contentType = "application/xml; charset=utf-8") { - return new OutputFormatterContext - { - Object = outputValue, - DeclaredType = outputType, - HttpContext = GetHttpContext(contentType) - }; + return new OutputFormatterWriteContext(GetHttpContext(contentType), outputType, outputValue); } private static HttpContext GetHttpContext(string contentType) diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs index 40045f7c48..405afe8225 100644 --- a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml { // Arrange var formatter = new XmlSerializerOutputFormatter(); - var outputFormatterContext = GetOutputFormatterContext(input, typeof(object)); + var outputFormatterContext = GetOutputFormatterContext(input, input.GetType()); // Act await formatter.WriteAsync(outputFormatterContext); @@ -74,11 +74,13 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml // Arrange var input = new DummyClass { SampleInt = 10 }; var formatter = new TestXmlSerializerOutputFormatter(); + var context = GetOutputFormatterContext(input, typeof(DummyClass)); + context.ContentType = MediaTypeHeaderValue.Parse("application/xml"); // Act - formatter.CanWriteResult(context, MediaTypeHeaderValue.Parse("application/xml")); - formatter.CanWriteResult(context, MediaTypeHeaderValue.Parse("application/xml")); + formatter.CanWriteResult(context); + formatter.CanWriteResult(context); // Assert Assert.Equal(1, formatter.createSerializerCalledCount); @@ -280,11 +282,8 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml { yield return new object[] { null, typeof(string), true }; yield return new object[] { null, null, false }; - yield return new object[] { new DummyClass { SampleInt = 5 }, null, true }; - yield return new object[] { new DummyClass { SampleInt = 5 }, typeof(object), true }; + yield return new object[] { new DummyClass { SampleInt = 5 }, typeof(DummyClass), true }; yield return new object[] { null, typeof(object), true }; - yield return new object[] { - new Dictionary { { "Hello", "world" } }, typeof(object), false }; yield return new object[] { new Dictionary { { "Hello", "world" } }, typeof(Dictionary), false }; yield return new object[] { @@ -301,9 +300,10 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml // Arrange var formatter = new XmlSerializerOutputFormatter(); var outputFormatterContext = GetOutputFormatterContext(input, declaredType); + outputFormatterContext.ContentType = MediaTypeHeaderValue.Parse("application/xml"); // Act - var result = formatter.CanWriteResult(outputFormatterContext, MediaTypeHeaderValue.Parse("application/xml")); + var result = formatter.CanWriteResult(outputFormatterContext); // Assert Assert.Equal(expectedOutput, result); @@ -328,25 +328,21 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml { get { - yield return new object[] { typeof(DummyClass), typeof(DummyClass), "application/xml" }; - yield return new object[] { typeof(DummyClass), typeof(object), "application/xml" }; - yield return new object[] { null, typeof(DummyClass), "application/xml" }; - yield return new object[] { typeof(DummyClass), null, "application/xml" }; - yield return new object[] { typeof(object), null, "application/xml" }; - yield return new object[] { null, null, null }; + yield return new object[] { typeof(DummyClass), "application/xml" }; + yield return new object[] { typeof(object), "application/xml" }; + yield return new object[] { null, null }; } } [Theory] [MemberData(nameof(TypesForGetSupportedContentTypes))] - public void XmlSerializer_GetSupportedContentTypes_Returns_SupportedTypes(Type declaredType, Type runtimeType, object expectedOutput) + public void XmlSerializer_GetSupportedContentTypes_Returns_SupportedTypes(Type type, object expectedOutput) { // Arrange var formatter = new XmlSerializerOutputFormatter(); // Act - var result = formatter.GetSupportedContentTypes( - declaredType, runtimeType, MediaTypeHeaderValue.Parse("application/xml")); + var result = formatter.GetSupportedContentTypes(MediaTypeHeaderValue.Parse("application/xml"), type); // Assert if (expectedOutput != null) @@ -359,17 +355,12 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml } } - private OutputFormatterContext GetOutputFormatterContext( + private OutputFormatterWriteContext GetOutputFormatterContext( object outputValue, Type outputType, string contentType = "application/xml; charset=utf-8") { - return new OutputFormatterContext - { - Object = outputValue, - DeclaredType = outputType, - HttpContext = GetHttpContext(contentType) - }; + return new OutputFormatterWriteContext(GetHttpContext(contentType), outputType, outputValue); } private static HttpContext GetHttpContext(string contentType) diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs index 5a46a01a32..f60149c2d1 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs @@ -146,17 +146,12 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShimTest Assert.NotNull(httpContext.Response.ContentLength); } - private OutputFormatterContext GetOutputFormatterContext( + private OutputFormatterWriteContext GetOutputFormatterContext( object outputValue, Type outputType, HttpContext httpContext) { - return new OutputFormatterContext - { - Object = outputValue, - DeclaredType = outputType, - HttpContext = httpContext, - }; + return new OutputFormatterWriteContext(httpContext, outputType, outputValue); } } } diff --git a/test/WebSites/ContentNegotiationWebSite/CustomFormatter.cs b/test/WebSites/ContentNegotiationWebSite/CustomFormatter.cs index 6f2f1d55df..9ad316d094 100644 --- a/test/WebSites/ContentNegotiationWebSite/CustomFormatter.cs +++ b/test/WebSites/ContentNegotiationWebSite/CustomFormatter.cs @@ -20,9 +20,9 @@ namespace ContentNegotiationWebSite SupportedEncodings.Add(Encoding.GetEncoding("utf-8")); } - public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + public override bool CanWriteResult(OutputFormatterCanWriteContext context) { - if (base.CanWriteResult(context, contentType)) + if (base.CanWriteResult(context)) { var actionReturnString = context.Object as string; if (actionReturnString != null) @@ -33,7 +33,7 @@ namespace ContentNegotiationWebSite return false; } - public override async Task WriteResponseBodyAsync(OutputFormatterContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { var response = context.HttpContext.Response; response.ContentType = ContentType + ";charset=utf-8"; diff --git a/test/WebSites/ContentNegotiationWebSite/PlainTextFormatter.cs b/test/WebSites/ContentNegotiationWebSite/PlainTextFormatter.cs index a50c88acfe..b8e328de7e 100644 --- a/test/WebSites/ContentNegotiationWebSite/PlainTextFormatter.cs +++ b/test/WebSites/ContentNegotiationWebSite/PlainTextFormatter.cs @@ -17,9 +17,9 @@ namespace ContentNegotiationWebSite SupportedEncodings.Add(Encoding.GetEncoding("utf-8")); } - public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + public override bool CanWriteResult(OutputFormatterCanWriteContext context) { - if (base.CanWriteResult(context, contentType)) + if (base.CanWriteResult(context)) { var actionReturnString = context.Object as string; if (actionReturnString != null) @@ -31,7 +31,7 @@ namespace ContentNegotiationWebSite return false; } - public override async Task WriteResponseBodyAsync(OutputFormatterContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { var response = context.HttpContext.Response; response.ContentType = "text/plain;charset=utf-8"; diff --git a/test/WebSites/ContentNegotiationWebSite/VCardFormatter_V3.cs b/test/WebSites/ContentNegotiationWebSite/VCardFormatter_V3.cs index 1a39a84138..3f5d30ac88 100644 --- a/test/WebSites/ContentNegotiationWebSite/VCardFormatter_V3.cs +++ b/test/WebSites/ContentNegotiationWebSite/VCardFormatter_V3.cs @@ -23,12 +23,12 @@ namespace ContentNegotiationWebSite SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard;version=v3.0")); } - protected override bool CanWriteType(Type declaredType, Type runtimeType) + protected override bool CanWriteType(Type type) { - return typeof(Contact).GetTypeInfo().IsAssignableFrom(runtimeType.GetTypeInfo()); + return typeof(Contact).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()); } - public override async Task WriteResponseBodyAsync(OutputFormatterContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { var contact = (Contact)context.Object; @@ -38,7 +38,9 @@ namespace ContentNegotiationWebSite builder.AppendLine(); builder.AppendLine("END:VCARD"); - await context.HttpContext.Response.WriteAsync(builder.ToString(), context.SelectedEncoding); + await context.HttpContext.Response.WriteAsync( + builder.ToString(), + context.ContentType?.Encoding ?? Encoding.UTF8); } } } \ No newline at end of file diff --git a/test/WebSites/ContentNegotiationWebSite/VCardFormatter_V4.cs b/test/WebSites/ContentNegotiationWebSite/VCardFormatter_V4.cs index 0d57854ef1..bf7392bb51 100644 --- a/test/WebSites/ContentNegotiationWebSite/VCardFormatter_V4.cs +++ b/test/WebSites/ContentNegotiationWebSite/VCardFormatter_V4.cs @@ -24,12 +24,12 @@ namespace ContentNegotiationWebSite SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard;version=v4.0")); } - protected override bool CanWriteType(Type declaredType, Type runtimeType) + protected override bool CanWriteType(Type type) { - return typeof(Contact).GetTypeInfo().IsAssignableFrom(runtimeType.GetTypeInfo()); + return typeof(Contact).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()); } - public override async Task WriteResponseBodyAsync(OutputFormatterContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { var contact = (Contact)context.Object; @@ -41,7 +41,9 @@ namespace ContentNegotiationWebSite builder.AppendLine(); builder.AppendLine("END:VCARD"); - await context.HttpContext.Response.WriteAsync(builder.ToString(), context.SelectedEncoding); + await context.HttpContext.Response.WriteAsync( + builder.ToString(), + context.ContentType?.Encoding ?? Encoding.UTF8); } } } \ No newline at end of file diff --git a/test/WebSites/FormatFilterWebSite/CustomFormatter.cs b/test/WebSites/FormatFilterWebSite/CustomFormatter.cs index 540523bee3..57708c6be1 100644 --- a/test/WebSites/FormatFilterWebSite/CustomFormatter.cs +++ b/test/WebSites/FormatFilterWebSite/CustomFormatter.cs @@ -4,7 +4,6 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; -using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Formatters; using Microsoft.Net.Http.Headers; @@ -21,22 +20,20 @@ namespace FormatFilterWebSite SupportedEncodings.Add(Encoding.GetEncoding("utf-8")); } - public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + public override bool CanWriteResult(OutputFormatterCanWriteContext context) { - if (base.CanWriteResult(context, contentType)) + if (base.CanWriteResult(context)) { var actionReturn = context.Object as Product; if (actionReturn != null) { - var response = context.HttpContext.Response; - context.SelectedContentType = contentType; return true; } } return false; } - public override async Task WriteResponseBodyAsync(OutputFormatterContext context) + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { var response = context.HttpContext.Response; await response.WriteAsync(context.Object.ToString());