Adding TextPlainFormatter to always handle returning strings as text\plain format.
Conflicts: src/Microsoft.AspNet.Mvc.Common/Encodings.cs src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectContentResultTests.cs test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs
This commit is contained in:
parent
5708f7592b
commit
19f3f78b3e
|
|
@ -16,7 +16,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// <summary>
|
||||
/// Returns UTF16 Encoding which uses littleEndian byte order with BOM and throws on invalid bytes.
|
||||
/// </summary>
|
||||
public static readonly Encoding UTF16EncodingLittleEndian
|
||||
= new UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true);
|
||||
public static readonly Encoding UTF16EncodingLittleEndian = new UnicodeEncoding(bigEndian: false,
|
||||
byteOrderMark: true,
|
||||
throwOnInvalidBytes: true);
|
||||
}
|
||||
}
|
||||
|
|
@ -127,6 +127,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
// Override the content type value even if one already existed.
|
||||
selectedMediaType.Charset = selectedEncoding.WebName;
|
||||
|
||||
context.SelectedContentType = context.SelectedContentType ?? selectedMediaType;
|
||||
var response = context.ActionContext.HttpContext.Response;
|
||||
response.ContentType = selectedMediaType.RawValue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
// 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.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Always writes a string value to the response, regardless of requested content type.
|
||||
/// </summary>
|
||||
public class TextPlainFormatter : OutputFormatter
|
||||
{
|
||||
public TextPlainFormatter()
|
||||
{
|
||||
SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
|
||||
SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/plain"));
|
||||
}
|
||||
|
||||
public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType)
|
||||
{
|
||||
// Ignore the passed in content type, if the object is string
|
||||
// always return it as a text/plain format.
|
||||
if(context.DeclaredType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.Object is string)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override async Task WriteResponseBodyAsync(OutputFormatterContext context)
|
||||
{
|
||||
var valueAsString = (string)context.Object;
|
||||
if (valueAsString == null)
|
||||
{
|
||||
// if the value is null don't write anything.
|
||||
return;
|
||||
}
|
||||
|
||||
var response = context.ActionContext.HttpContext.Response;
|
||||
using (var writer = new StreamWriter(response.Body, context.SelectedEncoding, 1024, leaveOpen: true))
|
||||
{
|
||||
await writer.WriteAsync(valueAsString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@
|
|||
<Compile Include="ActionDescriptorProviderContext.cs" />
|
||||
<Compile Include="ActionDescriptorsCollection.cs" />
|
||||
<Compile Include="ActionResults\HttpNotFoundResult.cs" />
|
||||
<Compile Include="Formatters\TextPlainFormatter.cs" />
|
||||
<Compile Include="OptionDescriptors\DefaultModelBinderProvider.cs" />
|
||||
<Compile Include="OptionDescriptors\DefaultValueProviderFactoryProvider.cs" />
|
||||
<Compile Include="OptionDescriptors\DefaultViewEngineProvider.cs" />
|
||||
|
|
|
|||
|
|
@ -120,7 +120,10 @@ namespace Microsoft.AspNet.Mvc
|
|||
Resources.FormatActionResult_ActionReturnValueCannotBeNull(actualReturnType));
|
||||
}
|
||||
|
||||
return new ObjectResult(actionReturnValue);
|
||||
return new ObjectResult(actionReturnValue)
|
||||
{
|
||||
DeclaredType = actualReturnType
|
||||
};
|
||||
}
|
||||
|
||||
private IFilter[] GetFilters()
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
options.ModelBinders.Add(new ComplexModelDtoModelBinder());
|
||||
|
||||
// Set up default output formatters.
|
||||
options.OutputFormatters.Add(new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(),
|
||||
options.OutputFormatters.Add(new TextPlainFormatter());
|
||||
options.OutputFormatters.Add(new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(),
|
||||
indent: false));
|
||||
|
||||
// Set up ValueProviders
|
||||
|
|
|
|||
|
|
@ -109,7 +109,9 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
|
|||
{
|
||||
// Arrange
|
||||
var expectedContentType = "application/json;charset=utf-8";
|
||||
var input = "testInput";
|
||||
|
||||
// non string value.
|
||||
var input = 123;
|
||||
var httpResponse = GetMockHttpResponse();
|
||||
var actionContext = CreateMockActionContext(httpResponse.Object);
|
||||
|
||||
|
|
@ -125,6 +127,30 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
|
|||
httpResponse.VerifySet(r => r.ContentType = expectedContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ObjectResult_WithSingleContentType_TheContentTypeIsIgnoredIfTheTypeIsString()
|
||||
{
|
||||
// Arrange
|
||||
var contentType = "application/json;charset=utf-8";
|
||||
var expectedContentType = "text/plain;charset=utf-8";
|
||||
|
||||
// string value.
|
||||
var input = "1234";
|
||||
var httpResponse = GetMockHttpResponse();
|
||||
var actionContext = CreateMockActionContext(httpResponse.Object);
|
||||
|
||||
// Set the content type property explicitly to a single value.
|
||||
var result = new ObjectResult(input);
|
||||
result.ContentTypes = new List<MediaTypeHeaderValue>();
|
||||
result.ContentTypes.Add(MediaTypeHeaderValue.Parse(contentType));
|
||||
|
||||
// Act
|
||||
await result.ExecuteResultAsync(actionContext);
|
||||
|
||||
// Assert
|
||||
httpResponse.VerifySet(r => r.ContentType = expectedContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ObjectResult_MultipleContentTypes_PicksFirstFormatterWhichSupportsAnyOfTheContentTypes()
|
||||
{
|
||||
|
|
@ -307,22 +333,21 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
|
|||
httpResponse.VerifySet(r => r.StatusCode = 406);
|
||||
}
|
||||
|
||||
// TODO: Disabling since this scenario is no longer dealt with in object result.
|
||||
// Re-enable once we do.
|
||||
//[Fact]
|
||||
[Fact]
|
||||
public async Task ObjectResult_Execute_CallsContentResult_SetsContent()
|
||||
{
|
||||
// Arrange
|
||||
var expectedContentType = "text/plain";
|
||||
var expectedContentType = "text/plain;charset=utf-8";
|
||||
var input = "testInput";
|
||||
var stream = new MemoryStream();
|
||||
|
||||
var httpResponse = new Mock<HttpResponse>();
|
||||
var tempContentType = string.Empty;
|
||||
httpResponse.SetupProperty<string>(o => o.ContentType);
|
||||
httpResponse.SetupGet(r => r.Body).Returns(stream);
|
||||
|
||||
var actionContext = CreateMockActionContext(httpResponse.Object);
|
||||
var actionContext = CreateMockActionContext(httpResponse.Object,
|
||||
requestAcceptHeader: null,
|
||||
requestContentType: null);
|
||||
|
||||
// Act
|
||||
var result = new ObjectResult(input);
|
||||
|
|
@ -330,6 +355,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
|
|||
|
||||
// Assert
|
||||
httpResponse.VerifySet(r => r.ContentType = expectedContentType);
|
||||
|
||||
// The following verifies the correct Content was written to Body
|
||||
Assert.Equal(input.Length, httpResponse.Object.Body.Length);
|
||||
}
|
||||
|
|
@ -471,7 +497,10 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
|
|||
get
|
||||
{
|
||||
return new List<IOutputFormatter>()
|
||||
{ new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), indent: false) };
|
||||
{
|
||||
new TextPlainFormatter(),
|
||||
new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), indent: false)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void SetResponseContentHeaders_FormatterWithNoEncoding_Throws()
|
||||
public void WriteResponseContentHeaders_FormatterWithNoEncoding_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var testFormatter = new TestOutputFormatter();
|
||||
|
|
@ -96,6 +96,30 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
" output formatter to write content.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteResponseContentHeaders_NoSelectedContentType_SetsOutputFormatterContext()
|
||||
{
|
||||
// Arrange
|
||||
var testFormatter = new DoesNotSetContext();
|
||||
var testContentType = MediaTypeHeaderValue.Parse("application/doesNotSetContext");
|
||||
var formatterContext = new OutputFormatterContext();
|
||||
var mockHttpContext = new Mock<HttpContext>();
|
||||
mockHttpContext.SetupGet(o => o.Request.AcceptCharset)
|
||||
.Returns(string.Empty);
|
||||
mockHttpContext.SetupProperty(o => o.Response.ContentType);
|
||||
var actionContext = new ActionContext(mockHttpContext.Object, new RouteData(), new ActionDescriptor());
|
||||
formatterContext.ActionContext = actionContext;
|
||||
|
||||
// Act
|
||||
testFormatter.WriteResponseContentHeaders(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Encodings.UTF16EncodingLittleEndian.WebName, formatterContext.SelectedEncoding.WebName);
|
||||
Assert.Equal(Encodings.UTF16EncodingLittleEndian, formatterContext.SelectedEncoding);
|
||||
Assert.Equal("application/doesNotSetContext;charset=" + Encodings.UTF16EncodingLittleEndian.WebName,
|
||||
formatterContext.SelectedContentType.RawValue);
|
||||
}
|
||||
|
||||
private class TestOutputFormatter : OutputFormatter
|
||||
{
|
||||
public TestOutputFormatter()
|
||||
|
|
@ -108,5 +132,26 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
private class DoesNotSetContext : OutputFormatter
|
||||
{
|
||||
public DoesNotSetContext()
|
||||
{
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/doesNotSetContext"));
|
||||
SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian);
|
||||
}
|
||||
|
||||
public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType)
|
||||
{
|
||||
// Do not set the selected media Type.
|
||||
// The WriteResponseContentHeader should do it for you.
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Task WriteResponseBodyAsync(OutputFormatterContext context)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
// 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.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class TextPlainFormatterTests
|
||||
{
|
||||
public static IEnumerable<object[]> OutputFormatterContextValues
|
||||
{
|
||||
get
|
||||
{
|
||||
// object value, bool useDeclaredTypeAsString, bool expectedCanWriteResult
|
||||
yield return new object[] { "valid value", true, true };
|
||||
yield return new object[] { "valid value", false, true };
|
||||
yield return new object[] { null, true, true };
|
||||
yield return new object[] { null, false, false };
|
||||
yield return new object[] { new object(), false, false };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("OutputFormatterContextValues")]
|
||||
public void CanWriteResult_ReturnsTrueForStringTypes(object value, bool useDeclaredTypeAsString, bool expectedCanWriteResult)
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextPlainFormatter();
|
||||
var typeToUse = useDeclaredTypeAsString ? typeof(string) : typeof(object);
|
||||
var formatterContext = new OutputFormatterContext()
|
||||
{
|
||||
Object = value,
|
||||
DeclaredType = typeToUse
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = formatter.CanWriteResult(formatterContext, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedCanWriteResult, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteAsync_DoesNotWriteNullStrings()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextPlainFormatter();
|
||||
var formatterContext = new OutputFormatterContext()
|
||||
{
|
||||
Object = null,
|
||||
DeclaredType = typeof(string),
|
||||
};
|
||||
|
||||
var tempMemoryStream = new MemoryStream();
|
||||
var mockHttpContext = new Mock<HttpContext>();
|
||||
mockHttpContext.SetupGet(o => o.Response.Body)
|
||||
.Returns(tempMemoryStream);
|
||||
// Act
|
||||
await formatter.WriteResponseBodyAsync(formatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, tempMemoryStream.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@
|
|||
<Compile Include="ActionResults\RedirectResultTest.cs" />
|
||||
<Compile Include="DefaultActionDiscoveryConventionsActionSelectionTests.cs" />
|
||||
<Compile Include="AntiXsrf\AntiForgeryOptionsTests.cs" />
|
||||
<Compile Include="Formatters\TextPlainFormatterTests.cs" />
|
||||
<Compile Include="OptionDescriptors\DefaultModelBindersProviderTest.cs" />
|
||||
<Compile Include="OptionDescriptors\DefaultValueProviderFactoryProviderTest.cs" />
|
||||
<Compile Include="OptionDescriptors\DefaultViewEngineProviderTest.cs" />
|
||||
|
|
@ -46,7 +47,7 @@
|
|||
<Compile Include="ExpiringFileInfoCacheTest.cs" />
|
||||
<Compile Include="DefaultActionDiscoveryConventionsTests.cs" />
|
||||
<Compile Include="Formatters\OutputFormatterTests.cs" />
|
||||
<Compile Include="MediaTypeWithQualityHeaderValueTests.cs" />
|
||||
<Compile Include="MediaTypeWithQualityHeaderValueTests.cs" />
|
||||
<Compile Include="ParameterBinding\ModelBindingHelperTest.cs" />
|
||||
<Compile Include="ReflectedModelBuilder\ReflectedParameterModelTests.cs" />
|
||||
<Compile Include="ReflectedModelBuilder\ReflectedActionModelTests.cs" />
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
_services = TestHelper.CreateServices("ValueProvidersSite");
|
||||
}
|
||||
|
||||
[Fact(Skip = "Skipped until PR#868 is checked in.")]
|
||||
[Fact]
|
||||
public async Task ValueProviderFactories_AreVisitedInSequentialOrder_ForValueProviders()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal("custom-value-provider-value", body.Trim());
|
||||
}
|
||||
|
||||
[Fact(Skip = "Skipped until PR#868 is checked in.")]
|
||||
[Fact]
|
||||
public async Task ValueProviderFactories_ReturnsValuesFromQueryValueProvider()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal("query-value", body.Trim());
|
||||
}
|
||||
|
||||
[Fact(Skip = "Skipped until PR#868 is checked in.")]
|
||||
[Fact]
|
||||
public async Task ValueProviderFactories_ReturnsValuesFromRouteValueProvider()
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -72,8 +72,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
setup.Setup(mvcOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, mvcOptions.OutputFormatters.Count);
|
||||
Assert.IsType<JsonOutputFormatter>(mvcOptions.OutputFormatters[0].Instance);
|
||||
Assert.Equal(2, mvcOptions.OutputFormatters.Count);
|
||||
Assert.IsType<TextPlainFormatter>(mvcOptions.OutputFormatters[0].Instance);
|
||||
Assert.IsType<JsonOutputFormatter>(mvcOptions.OutputFormatters[1].Instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue