NoContentFormatter: Writes 204 to the response status code if the value returned is null.
This commit is contained in:
parent
538e589894
commit
1684d1d322
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the status code to 204 if the content is null.
|
||||||
|
/// </summary>
|
||||||
|
public class NoContentFormatter : IOutputFormatter
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task WriteAsync(OutputFormatterContext context)
|
||||||
|
{
|
||||||
|
var response = context.ActionContext.HttpContext.Response;
|
||||||
|
response.ContentLength = 0;
|
||||||
|
|
||||||
|
// Only set the status code if its not already set.
|
||||||
|
if (response.StatusCode == 0)
|
||||||
|
{
|
||||||
|
response.StatusCode = 204;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<bool>(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
<Compile Include="ActionResults\HttpNotFoundResult.cs" />
|
<Compile Include="ActionResults\HttpNotFoundResult.cs" />
|
||||||
<Compile Include="Formatters\TextPlainFormatter.cs" />
|
<Compile Include="Formatters\TextPlainFormatter.cs" />
|
||||||
<Compile Include="HttpMethodAttribute.cs" />
|
<Compile Include="HttpMethodAttribute.cs" />
|
||||||
|
<Compile Include="Formatters\NoContentFormatter.cs" />
|
||||||
<Compile Include="Internal\DecisionTree\DecisionCriterionValue.cs" />
|
<Compile Include="Internal\DecisionTree\DecisionCriterionValue.cs" />
|
||||||
<Compile Include="Internal\DecisionTree\DecisionCriterionValueEqualityComparer.cs" />
|
<Compile Include="Internal\DecisionTree\DecisionCriterionValueEqualityComparer.cs" />
|
||||||
<Compile Include="Logging\AttributeRouteRouteAsyncValues.cs" />
|
<Compile Include="Logging\AttributeRouteRouteAsyncValues.cs" />
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
options.ModelBinders.Add(new ComplexModelDtoModelBinder());
|
options.ModelBinders.Add(new ComplexModelDtoModelBinder());
|
||||||
|
|
||||||
// Set up default output formatters.
|
// Set up default output formatters.
|
||||||
|
options.OutputFormatters.Add(new NoContentFormatter());
|
||||||
options.OutputFormatters.Add(new TextPlainFormatter());
|
options.OutputFormatters.Add(new TextPlainFormatter());
|
||||||
options.OutputFormatters.Add(new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(),
|
options.OutputFormatters.Add(new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(),
|
||||||
indent: false));
|
indent: false));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||||
|
using Microsoft.AspNet.PipelineCore;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Test
|
||||||
|
{
|
||||||
|
public class NoContentFormatterTests
|
||||||
|
{
|
||||||
|
public static IEnumerable<object[]> OutputFormatterContextValues_CanWriteType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// object value, bool useDeclaredTypeAsString, bool expectedCanwriteResult, bool useNonNullContentType
|
||||||
|
yield return new object[] { "valid value", true, false, true };
|
||||||
|
yield return new object[] { "valid value", false, false, true };
|
||||||
|
yield return new object[] { "", false, false, true };
|
||||||
|
yield return new object[] { "", true, false, true };
|
||||||
|
yield return new object[] { null, true, true, true };
|
||||||
|
yield return new object[] { null, false, true, true };
|
||||||
|
yield return new object[] { null, false, true, false };
|
||||||
|
yield return new object[] { new object(), false, false, true };
|
||||||
|
yield return new object[] { 1232, false, false, true };
|
||||||
|
yield return new object[] { 1232, false, false, false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData("OutputFormatterContextValues_CanWriteType")]
|
||||||
|
public void CanWriteResult_ReturnsTrueOnlyIfTheValueIsNull(object value,
|
||||||
|
bool declaredTypeAsString,
|
||||||
|
bool expectedCanwriteResult,
|
||||||
|
bool useNonNullContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var typeToUse = declaredTypeAsString ? typeof(string) : typeof(object);
|
||||||
|
var formatterContext = new OutputFormatterContext()
|
||||||
|
{
|
||||||
|
Object = value,
|
||||||
|
DeclaredType = typeToUse,
|
||||||
|
ActionContext = null,
|
||||||
|
};
|
||||||
|
var contetType = useNonNullContentType ? MediaTypeHeaderValue.Parse("text/plain") : null;
|
||||||
|
var formatter = new NoContentFormatter();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actualCanWriteResult = formatter.CanWriteResult(formatterContext, contetType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedCanwriteResult, actualCanWriteResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteAsync_WritesTheStatusCode204_IfNotAlreadySet()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var defaultHttpContext = new DefaultHttpContext();
|
||||||
|
|
||||||
|
// Workaround for https://github.com/aspnet/HttpAbstractions/issues/114
|
||||||
|
defaultHttpContext.Response.StatusCode = 0;
|
||||||
|
var formatterContext = new OutputFormatterContext()
|
||||||
|
{
|
||||||
|
Object = null,
|
||||||
|
ActionContext = new ActionContext(defaultHttpContext, new RouteData(), new ActionDescriptor())
|
||||||
|
};
|
||||||
|
|
||||||
|
var formatter = new NoContentFormatter();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await formatter.WriteAsync(formatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(204, defaultHttpContext.Response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteAsync_DoesnNotWriteTheStatusCode204_IfStatusCodeIsSetAlready()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var defaultHttpContext = new DefaultHttpContext();
|
||||||
|
defaultHttpContext.Response.StatusCode = 201;
|
||||||
|
var formatterContext = new OutputFormatterContext()
|
||||||
|
{
|
||||||
|
Object = null,
|
||||||
|
ActionContext = new ActionContext(defaultHttpContext, new RouteData(), new ActionDescriptor())
|
||||||
|
};
|
||||||
|
|
||||||
|
var formatter = new NoContentFormatter();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await formatter.WriteAsync(formatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(201, defaultHttpContext.Response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,8 +30,11 @@
|
||||||
<Compile Include="ActionResults\RedirectResultTest.cs" />
|
<Compile Include="ActionResults\RedirectResultTest.cs" />
|
||||||
<Compile Include="ActionResults\RedirectToActionResultTest.cs" />
|
<Compile Include="ActionResults\RedirectToActionResultTest.cs" />
|
||||||
<Compile Include="ActionResults\RedirectToRouteResultTest.cs" />
|
<Compile Include="ActionResults\RedirectToRouteResultTest.cs" />
|
||||||
|
<Compile Include="ActionResults\RedirectResultTest.cs" />
|
||||||
|
<Compile Include="DefaultActionDiscoveryConventionsActionSelectionTests.cs" />
|
||||||
<Compile Include="AntiXsrf\AntiForgeryOptionsTests.cs" />
|
<Compile Include="AntiXsrf\AntiForgeryOptionsTests.cs" />
|
||||||
<Compile Include="Filters\ProducesAttributeTests.cs" />
|
<Compile Include="Filters\ProducesAttributeTests.cs" />
|
||||||
|
<Compile Include="Formatters\NoContentFormatterTests.cs" />
|
||||||
<Compile Include="Formatters\OutputFormatterTests.cs" />
|
<Compile Include="Formatters\OutputFormatterTests.cs" />
|
||||||
<Compile Include="Formatters\TextPlainFormatterTests.cs" />
|
<Compile Include="Formatters\TextPlainFormatterTests.cs" />
|
||||||
<Compile Include="DefaultViewComponentActivatorTests.cs" />
|
<Compile Include="DefaultViewComponentActivatorTests.cs" />
|
||||||
|
|
@ -58,6 +61,7 @@
|
||||||
<Compile Include="OptionDescriptors\ValueProviderFactoryDescriptorTest.cs" />
|
<Compile Include="OptionDescriptors\ValueProviderFactoryDescriptorTest.cs" />
|
||||||
<Compile Include="OptionDescriptors\ViewEngineDescriptorTest.cs" />
|
<Compile Include="OptionDescriptors\ViewEngineDescriptorTest.cs" />
|
||||||
<Compile Include="OptionDescriptors\ViewEngineDscriptorExtensionsTest.cs" />
|
<Compile Include="OptionDescriptors\ViewEngineDscriptorExtensionsTest.cs" />
|
||||||
|
<Compile Include="MediaTypeWithQualityHeaderValueTests.cs" />
|
||||||
<Compile Include="ParameterBinding\ModelBindingHelperTest.cs" />
|
<Compile Include="ParameterBinding\ModelBindingHelperTest.cs" />
|
||||||
<Compile Include="AntiXsrf\AntiForgeryTokenSerializerTest.cs" />
|
<Compile Include="AntiXsrf\AntiForgeryTokenSerializerTest.cs" />
|
||||||
<Compile Include="AntiXsrf\ITokenProvider.cs" />
|
<Compile Include="AntiXsrf\ITokenProvider.cs" />
|
||||||
|
|
|
||||||
|
|
@ -189,65 +189,5 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
||||||
Assert.Equal(expectedBody, body);
|
Assert.Equal(expectedBody, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[InlineData("ReturnTaskOfString")]
|
|
||||||
[InlineData("ReturnTaskOfObject_StringValue")]
|
|
||||||
[InlineData("ReturnString")]
|
|
||||||
[InlineData("ReturnObject_StringValue")]
|
|
||||||
[InlineData("ReturnString_NullValue")]
|
|
||||||
public async Task TextPlainFormatter_ReturnsTextPlainContentType(string actionName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var server = TestServer.Create(_provider, _app);
|
|
||||||
var client = server.Handler;
|
|
||||||
var expectedContentType = "text/plain;charset=utf-8";
|
|
||||||
var expectedBody = actionName;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = await client.GetAsync("http://localhost/TextPlain/" + actionName);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expectedContentType, result.HttpContext.Response.ContentType);
|
|
||||||
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
|
||||||
Assert.Equal(expectedBody, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
[InlineData("ReturnTaskOfObject_ObjectValue")]
|
|
||||||
[InlineData("ReturnObject_ObjectValue")]
|
|
||||||
[InlineData("ReturnObject_NullValue")]
|
|
||||||
public async Task TextPlainFormatter_DoesNotSelectTextPlainFormatterForNonStringValue(string actionName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var server = TestServer.Create(_provider, _app);
|
|
||||||
var client = server.Handler;
|
|
||||||
var expectedContentType = "application/json;charset=utf-8";
|
|
||||||
var expectedBody = actionName;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = await client.GetAsync("http://localhost/TextPlain/" + actionName);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expectedContentType, result.HttpContext.Response.ContentType);
|
|
||||||
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
[InlineData("ReturnString_NullValue")]
|
|
||||||
public async Task TextPlainFormatter_DoesNotWriteNullValue(string actionName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var server = TestServer.Create(_provider, _app);
|
|
||||||
var client = server.Handler;
|
|
||||||
var expectedContentType = "text/plain;charset=utf-8";
|
|
||||||
string expectedBody = null;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = await client.GetAsync("http://localhost/TextPlain/" + actionName);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expectedContentType, result.HttpContext.Response.ContentType);
|
|
||||||
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
|
||||||
Assert.Equal(expectedBody, body);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="BasicTests.cs" />
|
<Compile Include="BasicTests.cs" />
|
||||||
|
<Compile Include="OutputFormattterTests.cs" />
|
||||||
<Compile Include="ConnegTests.cs" />
|
<Compile Include="ConnegTests.cs" />
|
||||||
<Compile Include="AntiForgeryTests.cs" />
|
<Compile Include="AntiForgeryTests.cs" />
|
||||||
<Compile Include="ModelBindingTests.cs" />
|
<Compile Include="ModelBindingTests.cs" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
// 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;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ConnegWebsite;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class OutputFormatterTests
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _provider = TestHelper.CreateServices("ConnegWebsite");
|
||||||
|
private readonly Action<IBuilder> _app = new Startup().Configure;
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("ReturnTaskOfString")]
|
||||||
|
[InlineData("ReturnTaskOfObject_StringValue")]
|
||||||
|
[InlineData("ReturnString")]
|
||||||
|
[InlineData("ReturnObject_StringValue")]
|
||||||
|
public async Task TextPlainFormatter_ForStringValues_GetsSelectedReturnsTextPlainContentType(string actionName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.Handler;
|
||||||
|
var expectedContentType = "text/plain;charset=utf-8";
|
||||||
|
var expectedBody = actionName;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await client.GetAsync("http://localhost/TextPlain/" + actionName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedContentType, result.HttpContext.Response.ContentType);
|
||||||
|
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
||||||
|
Assert.Equal(expectedBody, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("ReturnTaskOfObject_ObjectValue")]
|
||||||
|
[InlineData("ReturnObject_ObjectValue")]
|
||||||
|
public async Task JsonOutputFormatter_ForNonStringValue_GetsSelected(string actionName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.Handler;
|
||||||
|
var expectedContentType = "application/json;charset=utf-8";
|
||||||
|
var expectedBody = actionName;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await client.GetAsync("http://localhost/TextPlain/" + actionName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedContentType, result.HttpContext.Response.ContentType);
|
||||||
|
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("ReturnTaskOfString_NullValue")]
|
||||||
|
[InlineData("ReturnTaskOfObject_StringValue")]
|
||||||
|
[InlineData("ReturnTaskOfObject_NullValue")]
|
||||||
|
[InlineData("ReturnObject_NullValue")]
|
||||||
|
public async Task NoContentFormatter_ForNullValue_GetsSelectedAndWritesResponse(string actionName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.Handler;
|
||||||
|
string expectedContentType = null;
|
||||||
|
|
||||||
|
// ReadBodyAsString returns empty string instead of null.
|
||||||
|
string expectedBody = "";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await client.GetAsync("http://localhost/NoContent/" + actionName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedContentType, result.HttpContext.Response.ContentType);
|
||||||
|
var body = await result.HttpContext.Response.ReadBodyAsStringAsync();
|
||||||
|
Assert.Equal(expectedBody, body);
|
||||||
|
Assert.Equal(204, result.HttpContext.Response.StatusCode);
|
||||||
|
Assert.Equal(0, result.HttpContext.Response.ContentLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
<Compile Include="Controllers\ProducesContentBaseController.cs" />
|
<Compile Include="Controllers\ProducesContentBaseController.cs" />
|
||||||
<Compile Include="Controllers\ProducesContentOnClassController.cs" />
|
<Compile Include="Controllers\ProducesContentOnClassController.cs" />
|
||||||
<Compile Include="Controllers\HomeController.cs" />
|
<Compile Include="Controllers\HomeController.cs" />
|
||||||
|
<Compile Include="Controllers\NoContentController.cs" />
|
||||||
<Compile Include="Controllers\TextPlainController.cs" />
|
<Compile Include="Controllers\TextPlainController.cs" />
|
||||||
<Compile Include="Models\User.cs" />
|
<Compile Include="Models\User.cs" />
|
||||||
<Compile Include="ContentType.cs" />
|
<Compile Include="ContentType.cs" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ConnegWebsite
|
||||||
|
{
|
||||||
|
public class NoContentController : Controller
|
||||||
|
{
|
||||||
|
public Task<string> ReturnTaskOfString_NullValue()
|
||||||
|
{
|
||||||
|
return Task.FromResult<string>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<object> ReturnTaskOfObject_NullValue()
|
||||||
|
{
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReturnString_NullValue()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ReturnObject_NullValue()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ namespace ConnegWebsite
|
||||||
|
|
||||||
public object ReturnObject_StringValue()
|
public object ReturnObject_StringValue()
|
||||||
{
|
{
|
||||||
return "";
|
return "ReturnObject_StringValue";
|
||||||
}
|
}
|
||||||
|
|
||||||
public object ReturnObject_ObjectValue()
|
public object ReturnObject_ObjectValue()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue