Fix for 1071: Output Formatters should be invoked for writing out the response.

This commit is contained in:
harshgMSFT 2014-09-19 15:48:37 -07:00
parent f076fc7dcf
commit 572ec0175c
8 changed files with 183 additions and 14 deletions

View File

@ -13,11 +13,23 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public class HttpNoContentOutputFormatter : IOutputFormatter
{
/// <summary>
/// Indicates whether to select this formatter if the returned value from the action
/// is null.
/// </summary>
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<MediaTypeHeaderValue> GetSupportedContentTypes(

View File

@ -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<T> types.

View File

@ -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()
{

View File

@ -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<ObjectResult>(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<object[]> CreateActionResult_ReturnsObjectContentResultData

View File

@ -57,10 +57,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Theory]
[InlineData("ReturnTaskOfString_NullValue")]
[InlineData("ReturnTaskOfObject_NullValue")]
[InlineData("ReturnObject_NullValue")]
public async Task NoContentFormatter_ForNullValue_GetsSelectedAndWritesResponse(string actionName)
[InlineData("ReturnTask")]
[InlineData("ReturnVoid")]
public async Task NoContentFormatter_ForVoidAndTaskReturnType_GetsSelectedAndWritesResponse(string actionName)
{
// Arrange
var server = TestServer.Create(_provider, _app);
@ -77,5 +76,50 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
Assert.Equal(0, response.Content.Headers.ContentLength);
}
[Theory]
[InlineData("ReturnTaskOfString_NullValue")]
[InlineData("ReturnTaskOfObject_NullValue")]
[InlineData("ReturnObject_NullValue")]
public async Task NoContentFormatter_ForNullValue_ByDefault_GetsSelectedAndWritesResponse(string actionName)
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/NoContent/" + actionName);
// Assert
Assert.Null(response.Content.Headers.ContentType);
var body = await response.Content.ReadAsStringAsync();
// Response body is empty instead of null.
Assert.Empty(body);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
Assert.Equal(0, response.Content.Headers.ContentLength);
}
[Theory]
[InlineData("ReturnTaskOfString_NullValue")]
[InlineData("ReturnTaskOfObject_NullValue")]
[InlineData("ReturnObject_NullValue")]
public async Task
NoContentFormatter_ForNullValue_AndTreatNullAsNoContentFlagSetToFalse_DoesNotGetSelected(string actionName)
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/NoContentDoNotTreatNullValueAsNoContent/" +
actionName);
// Assert
Assert.Null(response.Content.Headers.ContentType);
var body = await response.Content.ReadAsStringAsync();
// Response body is empty instead of null.
Assert.Empty(body);
Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
}
}
}

View File

@ -34,7 +34,7 @@ namespace BasicWebSite.Controllers
public async Task ActionReturningTask()
{
// TODO: #1077. With HttpResponseMessage, there seems to be a race between the write operation setting the
// header to 200 and NoContentResult returned by the action invoker setting it to 204.
// header to 200 and HttpNoContentOutputFormatter which sets the header to 204.
Context.Response.StatusCode = 204;
await Context.Response.WriteAsync("Hello world");
}

View File

@ -27,5 +27,14 @@ namespace ConnegWebsite
{
return null;
}
public Task ReturnTask()
{
return Task.FromResult<bool>(true);
}
public void ReturnVoid()
{
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
namespace ConnegWebsite
{
public class NoContentDoNotTreatNullValueAsNoContentController : Controller
{
public override void OnActionExecuted(ActionExecutedContext context)
{
var result = context.Result as ObjectResult;
if (result != null)
{
var noContentFormatter = new HttpNoContentOutputFormatter() { TreatNullValueAsNoContent = false };
result.Formatters.Add(noContentFormatter);
}
base.OnActionExecuted(context);
}
public Task<string> ReturnTaskOfString_NullValue()
{
return Task.FromResult<string>(null);
}
public Task<object> ReturnTaskOfObject_NullValue()
{
return Task.FromResult<object>(null);
}
public object ReturnObject_NullValue()
{
return null;
}
public string ReturnString_NullValue()
{
return null;
}
}
}