diff --git a/src/Microsoft.AspNet.Mvc.Common/Encodings.cs b/src/Microsoft.AspNet.Mvc.Common/Encodings.cs
index 7879febce1..714a78cd4f 100644
--- a/src/Microsoft.AspNet.Mvc.Common/Encodings.cs
+++ b/src/Microsoft.AspNet.Mvc.Common/Encodings.cs
@@ -8,9 +8,16 @@ namespace Microsoft.AspNet.Mvc
internal static class Encodings
{
///
- /// Returns UTF8 Encoding without BOM and throws on invalid bytes
+ /// Returns UTF8 Encoding without BOM and throws on invalid bytes.
///
public static readonly Encoding UTF8EncodingWithoutBOM
= new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
+
+ ///
+ /// Returns UTF16 Encoding which uses littleEndian byte order with BOM and throws on invalid bytes.
+ ///
+ public static readonly Encoding UnicodeEncodingWithBOM = new UnicodeEncoding(bigEndian: false,
+ byteOrderMark: true,
+ throwOnInvalidBytes: true);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectContentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs
similarity index 90%
rename from src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectContentResult.cs
rename to src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs
index eb95a3d317..c22fa073ab 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectContentResult.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs
@@ -5,11 +5,11 @@ using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
- public class ObjectContentResult : ActionResult
+ public class ObjectResult : ActionResult
{
public object Value { get; set; }
- public ObjectContentResult(object value)
+ public ObjectResult(object value)
{
Value = value;
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs
new file mode 100644
index 0000000000..238ff7e86c
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs
@@ -0,0 +1,109 @@
+// 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.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.Mvc
+{
+ public class JsonOutputFormatter : OutputFormatter
+ {
+ private readonly JsonSerializerSettings _settings;
+ private readonly bool _indent;
+
+ public JsonOutputFormatter([NotNull] JsonSerializerSettings settings, bool indent)
+ {
+ _settings = settings;
+ _indent = indent;
+ SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
+ SupportedEncodings.Add(Encodings.UnicodeEncodingWithBOM);
+ SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
+ SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/json"));
+ }
+
+ public static JsonSerializerSettings CreateDefaultSettings()
+ {
+ return new JsonSerializerSettings()
+ {
+ MissingMemberHandling = MissingMemberHandling.Ignore,
+
+ // Do not change this setting
+ // Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types.
+ TypeNameHandling = TypeNameHandling.None
+ };
+ }
+
+ public void WriteObject([NotNull] TextWriter writer, object value)
+ {
+ using (var jsonWriter = CreateJsonWriter(writer))
+ {
+ var jsonSerializer = CreateJsonSerializer();
+ jsonSerializer.Serialize(jsonWriter, value);
+
+ // We're explicitly calling flush here to simplify the debugging experience because the
+ // underlying TextWriter might be long-lived. If this method ends up being called repeatedly
+ // for a request, we should revisit.
+ jsonWriter.Flush();
+ }
+ }
+
+ private JsonWriter CreateJsonWriter(TextWriter writer)
+ {
+ var jsonWriter = new JsonTextWriter(writer);
+ if (_indent)
+ {
+ jsonWriter.Formatting = Formatting.Indented;
+ }
+
+ jsonWriter.CloseOutput = false;
+
+ return jsonWriter;
+ }
+
+ private JsonSerializer CreateJsonSerializer()
+ {
+ var jsonSerializer = JsonSerializer.Create(_settings);
+ return jsonSerializer;
+ }
+
+ public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType)
+ {
+ return SupportedMediaTypes.Any(supportedMediaType =>
+ contentType.RawValue.Equals(supportedMediaType.RawValue,
+ StringComparison.OrdinalIgnoreCase));
+ }
+
+ public override Task WriteAsync(OutputFormatterContext context,
+ CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var response = context.HttpContext.Response;
+
+ // The content type including the encoding should have been set already.
+ // In case it was not present, a default will be selected.
+ var selectedEncoding = SelectCharacterEncoding(MediaTypeHeaderValue.Parse(response.ContentType));
+ using (var writer = new StreamWriter(response.Body, selectedEncoding))
+ {
+ using (var jsonWriter = CreateJsonWriter(writer))
+ {
+ var jsonSerializer = CreateJsonSerializer();
+ jsonSerializer.Serialize(jsonWriter, context.ObjectResult.Value);
+
+ // We're explicitly calling flush here to simplify the debugging experience because the
+ // underlying TextWriter might be long-lived. If this method ends up being called repeatedly
+ // for a request, we should revisit.
+ jsonWriter.Flush();
+ }
+ }
+
+ return Task.FromResult(true);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs
new file mode 100644
index 0000000000..2a4c7bb891
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs
@@ -0,0 +1,103 @@
+// 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.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Mvc.Core;
+using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// Writes an object to the output stream.
+ ///
+ public abstract class OutputFormatter
+ {
+ ///
+ /// Gets the mutable collection of character encodings supported by
+ /// this instance. The encodings are
+ /// used when writing the data.
+ ///
+ public List SupportedEncodings { get; private set; }
+
+ ///
+ /// Gets the mutable collection of elements supported by
+ /// this instance.
+ ///
+ public List SupportedMediaTypes { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected OutputFormatter()
+ {
+ SupportedEncodings = new List();
+ SupportedMediaTypes = new List();
+ }
+
+ ///
+ /// Determines the best amongst the supported encodings
+ /// for reading or writing an HTTP entity body based on the provided .
+ ///
+ /// The content type header provided as part of the request or response.
+ /// The to use when reading the request or writing the response.
+ public virtual Encoding SelectCharacterEncoding(MediaTypeHeaderValue contentTypeHeader)
+ {
+ Encoding encoding = null;
+ if (contentTypeHeader != null)
+ {
+ // Find encoding based on content type charset parameter
+ var charset = contentTypeHeader.Charset;
+ if (!String.IsNullOrWhiteSpace(charset))
+ {
+ encoding = SupportedEncodings.FirstOrDefault(
+ supportedEncoding =>
+ charset.Equals(supportedEncoding.WebName,
+ StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ if (encoding == null)
+ {
+ // We didn't find a character encoding match based on the content headers.
+ // Instead we try getting the default character encoding.
+ if (SupportedEncodings.Count > 0)
+ {
+ encoding = SupportedEncodings[0];
+ }
+ }
+
+ if (encoding == null)
+ {
+ // No supported encoding was found so there is no way for us to start writing.
+ throw new InvalidOperationException(Resources.FormatOutputFormatterNoEncoding(GetType().FullName));
+ }
+
+ return encoding;
+ }
+
+ ///
+ /// Determines whether this can serialize
+ /// an object of the specified type.
+ ///
+ /// The formatter context associated with the call
+ /// The desired contentType on the response.
+ /// True if this is able to serialize the object
+ /// represent by 's ObjectResult and supports the passed in
+ /// .
+ /// False otherwise.
+ public abstract bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType);
+
+ ///
+ /// Writes given to the HttpResponse body stream.
+ ///
+ /// The token to monitor for cancellation requests.
+ /// A Task that serializes the value to the 's response message.
+ public abstract Task WriteAsync(OutputFormatterContext context, CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatterContext.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatterContext.cs
new file mode 100644
index 0000000000..217b908417
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatterContext.cs
@@ -0,0 +1,17 @@
+// 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 Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Mvc
+{
+ public class OutputFormatterContext
+ {
+ public ObjectResult ObjectResult { get; set; }
+
+ public Type DeclaredType { get; set; }
+
+ public HttpContext HttpContext { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/JsonOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/JsonOutputFormatter.cs
deleted file mode 100644
index a5f7d1eea2..0000000000
--- a/src/Microsoft.AspNet.Mvc.Core/JsonOutputFormatter.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-// 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.IO;
-using Newtonsoft.Json;
-
-namespace Microsoft.AspNet.Mvc
-{
- public class JsonOutputFormatter
- {
- private readonly JsonSerializerSettings _settings;
- private readonly bool _indent;
-
- public JsonOutputFormatter([NotNull] JsonSerializerSettings settings, bool indent)
- {
- _settings = settings;
- _indent = indent;
- }
-
- public static JsonSerializerSettings CreateDefaultSettings()
- {
- return new JsonSerializerSettings()
- {
- MissingMemberHandling = MissingMemberHandling.Ignore,
-
- // Do not change this setting
- // Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types.
- TypeNameHandling = TypeNameHandling.None
- };
- }
-
- public void WriteObject([NotNull] TextWriter writer, object value)
- {
- using (var jsonWriter = CreateJsonWriter(writer))
- {
- var jsonSerializer = CreateJsonSerializer();
- jsonSerializer.Serialize(jsonWriter, value);
-
- // We're explicitly calling flush here to simplify the debugging experience because the
- // underlying TextWriter might be long-lived. If this method ends up being called repeatedly
- // for a request, we should revisit.
- jsonWriter.Flush();
- }
- }
-
- private JsonWriter CreateJsonWriter([NotNull] TextWriter writer)
- {
- var jsonWriter = new JsonTextWriter(writer);
- if (_indent)
- {
- jsonWriter.Formatting = Formatting.Indented;
- }
-
- jsonWriter.CloseOutput = false;
-
- return jsonWriter;
- }
-
- private JsonSerializer CreateJsonSerializer()
- {
- var jsonSerializer = JsonSerializer.Create(_settings);
- return jsonSerializer;
- }
- }
-}
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 da3a641a4c..34b61069f0 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
+++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
@@ -18,7 +18,9 @@
-
+
+ Designer
+
@@ -30,6 +32,9 @@
+
+
+
@@ -47,7 +52,7 @@
-
+
@@ -142,7 +147,6 @@
-
diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
index 28e8b6f189..864be77022 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
@@ -1050,6 +1050,22 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("TypeMustDeriveFromType"), p0, p1);
}
+ ///
+ /// No encoding found for output formatter '{0}'. There must be at least one supported encoding registered in order for the output formatter to write content.
+ ///
+ internal static string OutputFormatterNoEncoding
+ {
+ get { return GetString("OutputFormatterNoEncoding"); }
+ }
+
+ ///
+ /// No encoding found for output formatter '{0}'. There must be at least one supported encoding registered in order for the output formatter to write content.
+ ///
+ internal static string FormatOutputFormatterNoEncoding(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("OutputFormatterNoEncoding"), p0);
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs
index a2c14c7fa9..17996810eb 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs
@@ -120,7 +120,7 @@ namespace Microsoft.AspNet.Mvc
Resources.FormatActionResult_ActionReturnValueCannotBeNull(actualReturnType));
}
- return new ObjectContentResult(actionReturnValue);
+ return new ObjectResult(actionReturnValue);
}
private IFilter[] GetFilters()
diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
index 63b76fcaa7..c73c9886e1 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
@@ -312,4 +312,7 @@
The type '{0}' must derive from '{1}'.
+
+ No encoding found for output formatter '{0}'. There must be at least one supported encoding registered in order for the output formatter to write content.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/project.json b/src/Microsoft.AspNet.Mvc.Core/project.json
index d448274e9d..45e48450fe 100644
--- a/src/Microsoft.AspNet.Mvc.Core/project.json
+++ b/src/Microsoft.AspNet.Mvc.Core/project.json
@@ -6,6 +6,7 @@
"dependencies": {
"Microsoft.AspNet.FileSystems": "1.0.0-*",
"Microsoft.AspNet.Http": "1.0.0-*",
+ "Microsoft.AspNet.Mvc.HeaderValueAbstractions": "1.0.0-*",
"Microsoft.AspNet.Mvc.Common": "",
"Microsoft.AspNet.Mvc.ModelBinding": "",
"Microsoft.AspNet.Routing": "1.0.0-*",
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectContentResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectContentResultTests.cs
index cd48ac9ae6..30bd79d967 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectContentResultTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectContentResultTests.cs
@@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
var actionContext = CreateMockActionContext();
// Act
- var result = new ObjectContentResult(input);
+ var result = new ObjectResult(input);
// Assert
Assert.Equal(input, result.Value);
@@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
var actionContext = CreateMockActionContext(httpResponse.Object);
// Act
- var result = new ObjectContentResult(input);
+ var result = new ObjectResult(input);
await result.ExecuteResultAsync(actionContext);
// Assert
@@ -70,7 +70,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
}
// Act
- var result = new ObjectContentResult(nonStringValue);
+ var result = new ObjectResult(nonStringValue);
await result.ExecuteResultAsync(actionContext);
// Assert
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs
new file mode 100644
index 0000000000..a8e85086b3
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs
@@ -0,0 +1,43 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.Test
+{
+ public class OutputFormatterTests
+ {
+ [Fact]
+ public void SelectCharacterEncoding_FormatterWithNoEncoding_Throws()
+ {
+ // Arrange
+ var testFormatter = new TestFormatter();
+ var testContentType = MediaTypeHeaderValue.Parse("text/invalid");
+
+ // Act & Assert
+ var ex = Assert.Throws(() => testFormatter.SelectCharacterEncoding(testContentType));
+ Assert.Equal("No encoding found for output formatter "+
+ "'Microsoft.AspNet.Mvc.Test.OutputFormatterTests+TestFormatter'." +
+ " There must be at least one supported encoding registered in order for the" +
+ " output formatter to write content.", ex.Message);
+ }
+
+ private class TestFormatter : OutputFormatter
+ {
+ public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Task WriteAsync(OutputFormatterContext context, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj
index 1152907613..5490f9bc70 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj
@@ -33,6 +33,7 @@
+
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs
index 6d4f8c56dc..cc9a477b5d 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionInvokerTest.cs
@@ -1261,7 +1261,7 @@ namespace Microsoft.AspNet.Mvc
var actualResult = ReflectedActionInvoker.CreateActionResult(type, input);
// Assert
- var contentResult = Assert.IsType(actualResult);
+ var contentResult = Assert.IsType(actualResult);
Assert.Same(input, contentResult.Value);
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/project.json b/test/Microsoft.AspNet.Mvc.Core.Test/project.json
index e614971c26..36d78ccc11 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/project.json
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/project.json
@@ -4,6 +4,7 @@
},
"dependencies": {
"Microsoft.AspNet.Http": "1.0.0-*",
+ "Microsoft.AspNet.Mvc.HeaderValueAbstractions": "1.0.0-*",
"Microsoft.AspNet.Mvc" : "",
"Microsoft.AspNet.Mvc.Core" : "",
"Microsoft.AspNet.Mvc.ModelBinding": "",