diff --git a/src/Microsoft.AspNet.Mvc.Common/Encodings.cs b/src/Microsoft.AspNet.Mvc.Common/Encodings.cs
index 5ab65d219a..5c647d80dd 100644
--- a/src/Microsoft.AspNet.Mvc.Common/Encodings.cs
+++ b/src/Microsoft.AspNet.Mvc.Common/Encodings.cs
@@ -16,7 +16,8 @@ namespace Microsoft.AspNet.Mvc
///
/// Returns UTF16 Encoding which uses littleEndian byte order with BOM and throws on invalid bytes.
///
- 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);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs
index f74f46f495..9bd41f033d 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs
@@ -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;
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/TextPlainFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/TextPlainFormatter.cs
new file mode 100644
index 0000000000..d6e51c8030
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/TextPlainFormatter.cs
@@ -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
+{
+ ///
+ /// Always writes a string value to the response, regardless of requested content type.
+ ///
+ 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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
index 209cd370da..7c307edad4 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
+++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
@@ -30,6 +30,7 @@
+
diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs
index 17996810eb..77b59bb781 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.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()
diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs
index 2ed21772a1..84bcfb2947 100644
--- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs
+++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs
@@ -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
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectContentResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectContentResultTests.cs
index f5f8087611..5be62d67c4 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectContentResultTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectContentResultTests.cs
@@ -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();
+ 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();
- var tempContentType = string.Empty;
httpResponse.SetupProperty(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()
- { new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), indent: false) };
+ {
+ new TextPlainFormatter(),
+ new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), indent: false)
+ };
}
}
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs
index 6e46e62ae6..085ac3ca2a 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs
@@ -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();
+ 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);
+ }
+ }
}
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/TextPlainFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/TextPlainFormatterTests.cs
new file mode 100644
index 0000000000..12af356668
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/TextPlainFormatterTests.cs
@@ -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