[Fixes #4050] Throw an exception on CanRead/Write and GetSupportedContentTypes when the list of media types is empty

This commit is contained in:
jacalvar 2016-02-05 19:33:31 -08:00
parent 7337f50112
commit 910f0139f9
6 changed files with 156 additions and 66 deletions

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Core;
namespace Microsoft.AspNetCore.Mvc.Formatters namespace Microsoft.AspNetCore.Mvc.Formatters
{ {
@ -43,6 +44,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <inheritdoc /> /// <inheritdoc />
public virtual bool CanRead(InputFormatterContext context) public virtual bool CanRead(InputFormatterContext context)
{ {
if (SupportedMediaTypes.Count == 0)
{
var message = Resources.FormatFormatter_NoMediaTypes(
GetType().FullName,
nameof(SupportedMediaTypes));
throw new InvalidOperationException(message);
}
if (!CanReadType(context.ModelType)) if (!CanReadType(context.ModelType))
{ {
return false; return false;
@ -111,6 +121,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <inheritdoc /> /// <inheritdoc />
public virtual IReadOnlyList<string> GetSupportedContentTypes(string contentType, Type objectType) public virtual IReadOnlyList<string> GetSupportedContentTypes(string contentType, Type objectType)
{ {
if (SupportedMediaTypes.Count == 0)
{
var message = Resources.FormatFormatter_NoMediaTypes(
GetType().FullName,
nameof(SupportedMediaTypes));
throw new InvalidOperationException(message);
}
if (!CanReadType(objectType)) if (!CanReadType(objectType))
{ {
return null; return null;
@ -119,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
if (contentType == null) if (contentType == null)
{ {
// If contentType is null, then any type we support is valid. // If contentType is null, then any type we support is valid.
return SupportedMediaTypes.Count > 0 ? SupportedMediaTypes : null; return SupportedMediaTypes;
} }
else else
{ {

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Mvc.Formatters namespace Microsoft.AspNetCore.Mvc.Formatters
@ -14,19 +15,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// </summary> /// </summary>
public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider
{ {
/// <summary>
/// Initializes a new instance of the <see cref="OutputFormatter"/> class.
/// </summary>
protected OutputFormatter()
{
SupportedMediaTypes = new MediaTypeCollection();
}
/// <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; } = new MediaTypeCollection();
/// <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.
@ -43,6 +36,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
string contentType, string contentType,
Type objectType) Type objectType)
{ {
if (SupportedMediaTypes.Count == 0)
{
var message = Resources.FormatFormatter_NoMediaTypes(
GetType().FullName,
nameof(SupportedMediaTypes));
throw new InvalidOperationException(message);
}
if (!CanWriteType(objectType)) if (!CanWriteType(objectType))
{ {
return null; return null;
@ -51,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
if (contentType == null) if (contentType == null)
{ {
// If contentType is null, then any type we support is valid. // If contentType is null, then any type we support is valid.
return SupportedMediaTypes.Count > 0 ? SupportedMediaTypes : null; return SupportedMediaTypes;
} }
else else
{ {
@ -87,6 +89,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (SupportedMediaTypes.Count == 0)
{
var message = Resources.FormatFormatter_NoMediaTypes(
GetType().FullName,
nameof(SupportedMediaTypes));
throw new InvalidOperationException(message);
}
if (!CanWriteType(context.ObjectType)) if (!CanWriteType(context.ObjectType))
{ {
return false; return false;
@ -96,15 +107,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{ {
// If the desired content type is set to null, then the current formatter can write anything // If the desired content type is set to null, then the current formatter can write anything
// it wants. // it wants.
if (SupportedMediaTypes.Count > 0) context.ContentType = new StringSegment(SupportedMediaTypes[0]);
{ return true;
context.ContentType = new StringSegment(SupportedMediaTypes[0]);
return true;
}
else
{
return false;
}
} }
else else
{ {
@ -160,4 +164,4 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <returns>A task which can write the response body.</returns> /// <returns>A task which can write the response body.</returns>
public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context); public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context);
} }
} }

View File

@ -1146,6 +1146,22 @@ namespace Microsoft.AspNetCore.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("TextOutpurFormatter_WriteResponseBodyAsynNotSupported"), p0, p1, p2); return string.Format(CultureInfo.CurrentCulture, GetString("TextOutpurFormatter_WriteResponseBodyAsynNotSupported"), p0, p1, p2);
} }
/// <summary>
/// No media types found in '{0}.{1}'. Add at least one media type to the list of supported media types.
/// </summary>
internal static string Formatter_NoMediaTypes
{
get { return GetString("Formatter_NoMediaTypes"); }
}
/// <summary>
/// No media types found in '{0}.{1}'. Add at least one media type to the list of supported media types.
/// </summary>
internal static string FormatFormatter_NoMediaTypes(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Formatter_NoMediaTypes"), p0, p1);
}
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
Version 2.0 Version 2.0
The primary goals of this format is to allow a simple XML format The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes various data types are done through the TypeConverter classes
associated with the data types. associated with the data types.
Example: Example:
... ado.net/XML headers & schema ... ... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader> <resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader> <resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment> <comment>This is a comment</comment>
</data> </data>
There are any number of "resheader" rows that contain simple There are any number of "resheader" rows that contain simple
name/value pairs. name/value pairs.
Each data row contains a name, and value. The row also contains a Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture. text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the Classes that don't support this are serialized and stored with the
mimetype set. mimetype set.
The mimetype is used for serialized objects, and tells the The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly: extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below. read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64 mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64 mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64 mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter : using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
--> -->
@ -340,4 +340,7 @@
<data name="TextOutpurFormatter_WriteResponseBodyAsynNotSupported" xml:space="preserve"> <data name="TextOutpurFormatter_WriteResponseBodyAsynNotSupported" xml:space="preserve">
<value>'{0}' is not supported by '{1}'. Use '{2}' instead.</value> <value>'{0}' is not supported by '{1}'. Use '{2}' instead.</value>
</data> </data>
<data name="Formatter_NoMediaTypes" xml:space="preserve">
<value>No media types found in '{0}.{1}'. Add at least one media type to the list of supported media types.</value>
</data>
</root> </root>

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Http.Internal;
@ -382,6 +383,41 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Assert.Collection(results, c => Assert.Equal("text/xml", c)); Assert.Collection(results, c => Assert.Equal("text/xml", c));
} }
[Fact]
public void CanRead_ThrowsInvalidOperationException_IfMediaTypesListIsEmpty()
{
// Arrange
var formatter = new BadConfigurationFormatter();
var context = new InputFormatterContext(
new DefaultHttpContext(),
"",
new ModelStateDictionary(),
new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)),
(s, e) => new StreamReader(s, e));
// Act & Assert
Assert.Throws<InvalidOperationException>(() => formatter.CanRead(context));
}
[Fact]
public void GetSupportedContentTypes_ThrowsInvalidOperationException_IfMediaTypesListIsEmpty()
{
// Arrange
var formatter = new BadConfigurationFormatter();
// Act & Assert
Assert.Throws<InvalidOperationException>(
() => formatter.GetSupportedContentTypes("application/json", typeof(object)));
}
private class BadConfigurationFormatter : InputFormatter
{
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
throw new NotImplementedException();
}
}
private class TestFormatter : InputFormatter private class TestFormatter : InputFormatter
{ {
public IList<Type> SupportedTypes { get; } = new List<Type>(); public IList<Type> SupportedTypes { get; } = new List<Type>();

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -138,24 +139,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Assert.Null(contentTypes); Assert.Null(contentTypes);
} }
[Fact]
public void GetSupportedContentTypes_ReturnsAllContentTypes_ReturnsNullWithNoSupportedContentTypes()
{
// Arrange
var formatter = new TestOutputFormatter();
// Intentionally empty
formatter.SupportedMediaTypes.Clear();
// Act
var contentTypes = formatter.GetSupportedContentTypes(
contentType: null,
objectType: typeof(int));
// Assert
Assert.Null(contentTypes);
}
private class TypeSpecificFormatter : OutputFormatter private class TypeSpecificFormatter : OutputFormatter
{ {
public List<Type> SupportedTypes { get; } = new List<Type>(); public List<Type> SupportedTypes { get; } = new List<Type>();
@ -171,6 +154,35 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
} }
} }
[Fact]
public void CanWrite_ThrowsInvalidOperationException_IfMediaTypesListIsEmpty()
{
// Arrange
var formatter = new TestOutputFormatter();
formatter.SupportedMediaTypes.Clear();
var context = new OutputFormatterWriteContext(
new DefaultHttpContext(),
(s, e) => new StreamWriter(s, e),
typeof(object),
new object());
// Act & Assert
Assert.Throws<InvalidOperationException>(() => formatter.CanWriteResult(context));
}
[Fact]
public void GetSupportedContentTypes_ThrowsInvalidOperationException_IfMediaTypesListIsEmpty()
{
// Arrange
var formatter = new TestOutputFormatter();
formatter.SupportedMediaTypes.Clear();
// Act & Assert
Assert.Throws<InvalidOperationException>(
() => formatter.GetSupportedContentTypes("application/json", typeof(object)));
}
private class TestOutputFormatter : OutputFormatter private class TestOutputFormatter : OutputFormatter
{ {
public TestOutputFormatter() public TestOutputFormatter()