[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.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Core;
namespace Microsoft.AspNetCore.Mvc.Formatters
{
@ -43,6 +44,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <inheritdoc />
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))
{
return false;
@ -111,6 +121,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <inheritdoc />
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))
{
return null;
@ -119,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
if (contentType == null)
{
// If contentType is null, then any type we support is valid.
return SupportedMediaTypes.Count > 0 ? SupportedMediaTypes : null;
return SupportedMediaTypes;
}
else
{

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Mvc.Formatters
@ -14,19 +15,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// </summary>
public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider
{
/// <summary>
/// Initializes a new instance of the <see cref="OutputFormatter"/> class.
/// </summary>
protected OutputFormatter()
{
SupportedMediaTypes = new MediaTypeCollection();
}
/// <summary>
/// Gets the mutable collection of media type elements supported by
/// this <see cref="OutputFormatter"/>.
/// </summary>
public MediaTypeCollection SupportedMediaTypes { get; }
public MediaTypeCollection SupportedMediaTypes { get; } = new MediaTypeCollection();
/// <summary>
/// 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,
Type objectType)
{
if (SupportedMediaTypes.Count == 0)
{
var message = Resources.FormatFormatter_NoMediaTypes(
GetType().FullName,
nameof(SupportedMediaTypes));
throw new InvalidOperationException(message);
}
if (!CanWriteType(objectType))
{
return null;
@ -51,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
if (contentType == null)
{
// If contentType is null, then any type we support is valid.
return SupportedMediaTypes.Count > 0 ? SupportedMediaTypes : null;
return SupportedMediaTypes;
}
else
{
@ -87,6 +89,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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))
{
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
// it wants.
if (SupportedMediaTypes.Count > 0)
{
context.ContentType = new StringSegment(SupportedMediaTypes[0]);
return true;
}
else
{
return false;
}
context.ContentType = new StringSegment(SupportedMediaTypes[0]);
return true;
}
else
{
@ -160,4 +164,4 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <returns>A task which can write the response body.</returns>
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);
}
/// <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)
{
var value = _resourceManager.GetString(name);

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</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>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
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
: and then encoded with base64 encoding.
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
: and then encoded with base64 encoding.
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
: and then encoded with base64 encoding.
-->
@ -340,4 +340,7 @@
<data name="TextOutpurFormatter_WriteResponseBodyAsynNotSupported" xml:space="preserve">
<value>'{0}' is not supported by '{1}'. Use '{2}' instead.</value>
</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>

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Internal;
@ -382,6 +383,41 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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
{
public IList<Type> SupportedTypes { get; } = new List<Type>();

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
@ -138,24 +139,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
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
{
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
{
public TestOutputFormatter()