diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs
index f3b7f693a6..8e3c92e767 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs
@@ -13,11 +13,23 @@ namespace Microsoft.AspNet.Mvc
///
public class HttpNoContentOutputFormatter : IOutputFormatter
{
+ ///
+ /// Indicates whether to select this formatter if the returned value from the action
+ /// is null.
+ ///
+ public bool TreatNullValueAsNoContent { get; set; } = true;
+
public bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType)
{
// ignore the contentType and just look at the content.
// This formatter will be selected if the content is null.
- return context.Object == 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))
+ {
+ return true;
+ }
+
+ return TreatNullValueAsNoContent && context.Object == null;
}
public IReadOnlyList GetSupportedContentTypes(
diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs
index 178010cc95..491c938ec2 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs
@@ -80,7 +80,11 @@ namespace Microsoft.AspNet.Mvc
if (declaredReturnType == typeof(void) ||
declaredReturnType == typeof(Task))
{
- return new NoContentResult();
+ return new ObjectResult(null)
+ {
+ // Treat the declared type as void, which is the unwrapped type for Task.
+ DeclaredType = typeof(void)
+ };
}
// Unwrap potential Task types.
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs
index 0210e3fb55..808bd22fc7 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs
@@ -37,10 +37,10 @@ namespace Microsoft.AspNet.Mvc.Test
[Theory]
[MemberData(nameof(OutputFormatterContextValues_CanWriteType))]
- public void CanWriteResult_ReturnsTrueOnlyIfTheValueIsNull(object value,
- bool declaredTypeAsString,
- bool expectedCanwriteResult,
- bool useNonNullContentType)
+ public void CanWriteResult_ByDefault_ReturnsTrue_IfTheValueIsNull(object value,
+ bool declaredTypeAsString,
+ bool expectedCanwriteResult,
+ bool useNonNullContentType)
{
// Arrange
var typeToUse = declaredTypeAsString ? typeof(string) : typeof(object);
@@ -60,6 +60,58 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Equal(expectedCanwriteResult, actualCanWriteResult);
}
+ [Theory]
+ [InlineData(typeof(void))]
+ [InlineData(typeof(Task))]
+ public void CanWriteResult_ReturnsTrue_IfReturnTypeIsVoidOrTask(Type declaredType)
+ {
+ // Arrange
+ var formatterContext = new OutputFormatterContext()
+ {
+ Object = "Something non null.",
+ DeclaredType = declaredType,
+ ActionContext = null,
+ };
+ var contetType = MediaTypeHeaderValue.Parse("text/plain");
+ var formatter = new HttpNoContentOutputFormatter();
+
+ // Act
+ var actualCanWriteResult = formatter.CanWriteResult(formatterContext, contetType);
+
+ // Assert
+ Assert.True(actualCanWriteResult);
+ }
+
+ [Theory]
+ [InlineData(null, true, true)]
+ [InlineData(null, false, false)]
+ [InlineData("some value", true, false)]
+ public void
+ CanWriteResult_ReturnsTrue_IfReturnValueIsNullAndTreatNullValueAsNoContentIsNotSet(string value,
+ bool treatNullValueAsNoContent,
+ bool expectedCanwriteResult)
+ {
+ // Arrange
+ var formatterContext = new OutputFormatterContext()
+ {
+ Object = value,
+ DeclaredType = typeof(string),
+ ActionContext = null,
+ };
+
+ var contetType = MediaTypeHeaderValue.Parse("text/plain");
+ var formatter = new HttpNoContentOutputFormatter()
+ {
+ TreatNullValueAsNoContent = treatNullValueAsNoContent
+ };
+
+ // Act
+ var actualCanWriteResult = formatter.CanWriteResult(formatterContext, contetType);
+
+ // Assert
+ Assert.Equal(expectedCanwriteResult, actualCanWriteResult);
+ }
+
[Fact]
public async Task WriteAsync_WritesTheStatusCode204()
{
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs
index 70d85031c3..1211895697 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs
@@ -1234,13 +1234,17 @@ namespace Microsoft.AspNet.Mvc
[Theory]
[InlineData(typeof(void))]
[InlineData(typeof(Task))]
- public void CreateActionResult_Types_ReturnsNoContentResultForTaskAndVoidReturnTypes(Type type)
+ public void CreateActionResult_Types_ReturnsObjectResultForTaskAndVoidReturnTypes(Type type)
{
// Arrange & Act
- var result = ReflectedActionInvoker.CreateActionResult(type, null).GetType();
+ var result = ReflectedActionInvoker.CreateActionResult(type, null);
// Assert
- Assert.Equal(typeof(NoContentResult), (result));
+ var objectResult = Assert.IsType(result);
+
+ // Since we unwrap the Task type to void, the expected type will always be void.
+ Assert.Equal(typeof(void), objectResult.DeclaredType);
+ Assert.Null(objectResult.Value);
}
public static IEnumerable