From e985c2252876abcb80069755c1335371216ac379 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Wed, 8 Oct 2014 08:21:50 -0700 Subject: [PATCH] [Fixes #1216] Provide a property on JsonOutputFormatter to set serializer settings [Fixes #1221] Rename OutputFormatter's WriteResponseContentHeaders to WriteResponseHeaders [Fixes #932] Setting Json Serializer Settings --- .../Formatters/JsonInputFormatter.cs | 2 +- .../Formatters/JsonOutputFormatter.cs | 40 +++--- .../Formatters/OutputFormatter.cs | 6 +- .../ViewComponents/JsonViewComponentResult.cs | 25 +--- src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs | 3 +- src/Microsoft.AspNet.Mvc/MvcServices.cs | 3 +- .../ActionResults/ObjectResultTests.cs | 18 +-- .../ControllerActionInvokerTest.cs | 4 +- .../Formatters/JsonInputFormatterTest.cs | 83 +++++++++++- .../Formatters/JsonOutputFormatterTests.cs | 128 ++++++++++++++++++ .../Formatters/OutputFormatterTests.cs | 4 +- .../JsonResultTest.cs | 9 +- .../BasicTests.cs | 24 ++++ .../ConnegTests.cs | 1 + .../JsonOutputFormatterTests.cs | 43 ++++++ test/WebSites/ApiExplorerWebSite/Startup.cs | 4 +- .../BasicWebSite/Components/JsonTextInView.cs | 18 +++ .../Controllers/HomeController.cs | 5 + test/WebSites/BasicWebSite/Models/Person.cs | 11 ++ .../Views/Home/JsonTextInView.cshtml | 1 + .../FallbackOnTypeBasedMatchController.cs | 4 +- .../Controllers/NormalController.cs | 8 +- .../Controllers/JsonFormattterController.cs | 29 ++++ 23 files changed, 391 insertions(+), 82 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonOutputFormatterTests.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/JsonOutputFormatterTests.cs create mode 100644 test/WebSites/BasicWebSite/Components/JsonTextInView.cs create mode 100644 test/WebSites/BasicWebSite/Models/Person.cs create mode 100644 test/WebSites/BasicWebSite/Views/Home/JsonTextInView.cshtml create mode 100644 test/WebSites/FormatterWebSite/Controllers/JsonFormattterController.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs index f5802887fd..b2c4744aed 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs @@ -58,7 +58,7 @@ namespace Microsoft.AspNet.Mvc { if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } _jsonSerializerSettings = value; diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs index 94e59a9731..1a017f92d5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs @@ -1,6 +1,7 @@ // 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.Threading.Tasks; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; @@ -10,29 +11,33 @@ namespace Microsoft.AspNet.Mvc { public class JsonOutputFormatter : OutputFormatter { - private readonly JsonSerializerSettings _settings; - private readonly bool _indent; - - public JsonOutputFormatter([NotNull] JsonSerializerSettings settings, bool indent) + private JsonSerializerSettings _serializerSettings; + + public JsonOutputFormatter() { - _settings = settings; - _indent = indent; SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM); SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json")); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/json")); + + _serializerSettings = new JsonSerializerSettings(); } - - public static JsonSerializerSettings CreateDefaultSettings() + + /// + /// Gets or sets the used to configure the . + /// + public JsonSerializerSettings SerializerSettings { - return new JsonSerializerSettings() + get { return _serializerSettings; } + set { - MissingMemberHandling = MissingMemberHandling.Ignore, + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } - // Do not change this setting - // Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types. - TypeNameHandling = TypeNameHandling.None - }; + _serializerSettings = value; + } } public void WriteObject([NotNull] TextWriter writer, object value) @@ -47,11 +52,6 @@ namespace Microsoft.AspNet.Mvc private JsonWriter CreateJsonWriter(TextWriter writer) { var jsonWriter = new JsonTextWriter(writer); - if (_indent) - { - jsonWriter.Formatting = Formatting.Indented; - } - jsonWriter.CloseOutput = false; return jsonWriter; @@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc private JsonSerializer CreateJsonSerializer() { - var jsonSerializer = JsonSerializer.Create(_settings); + var jsonSerializer = JsonSerializer.Create(_serializerSettings); return jsonSerializer; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs index 213a193738..1ec9c9de19 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs @@ -157,15 +157,15 @@ namespace Microsoft.AspNet.Mvc /// public async Task WriteAsync([NotNull] OutputFormatterContext context) { - WriteResponseContentHeaders(context); + WriteResponseHeaders(context); await WriteResponseBodyAsync(context); } /// - /// Sets the content-type headers with charset value to the HttpResponse. + /// Sets the headers on object. /// /// The formatter context associated with the call. - public virtual void WriteResponseContentHeaders([NotNull] OutputFormatterContext context) + public virtual void WriteResponseHeaders([NotNull] OutputFormatterContext context) { var selectedMediaType = context.SelectedContentType; diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/JsonViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/JsonViewComponentResult.cs index 143c5c45be..d3b7ad3c2b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/JsonViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/JsonViewComponentResult.cs @@ -9,39 +9,16 @@ namespace Microsoft.AspNet.Mvc { public class JsonViewComponentResult : IViewComponentResult { - private JsonSerializerSettings _jsonSerializerSettings; - public JsonViewComponentResult([NotNull] object data) { Data = data; - _jsonSerializerSettings = JsonOutputFormatter.CreateDefaultSettings(); } public object Data { get; private set; } - public JsonSerializerSettings SerializerSettings - { - get { return _jsonSerializerSettings; } - - set - { - if (value == null) - { - throw new ArgumentNullException("value"); - } - - _jsonSerializerSettings = value; - } - } - - /// - /// Gets or sets a value indicating whether to indent elements when writing data. - /// - public bool Indent { get; set; } - public void Execute([NotNull] ViewComponentContext context) { - var formatter = new JsonOutputFormatter(SerializerSettings, Indent); + var formatter = new JsonOutputFormatter(); formatter.WriteObject(context.Writer, Data); } diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs index 53ff1123e0..1b9c367aff 100644 --- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs @@ -36,8 +36,7 @@ namespace Microsoft.AspNet.Mvc // Set up default output formatters. options.OutputFormatters.Add(new HttpNoContentOutputFormatter()); options.OutputFormatters.Add(new TextPlainFormatter()); - options.OutputFormatters.Add(new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), - indent: false)); + options.OutputFormatters.Add(new JsonOutputFormatter()); options.OutputFormatters.Add( new XmlDataContractSerializerOutputFormatter(XmlOutputFormatter.GetDefaultXmlWriterSettings())); diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 2c531b79de..77573d17ea 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -85,8 +85,7 @@ namespace Microsoft.AspNet.Mvc yield return describe.Scoped(); yield return describe.Transient(); - yield return describe.Instance( - new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), indent: false)); + yield return describe.Instance(new JsonOutputFormatter()); yield return describe.Transient(); yield return describe.Scoped(); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectResultTests.cs index dd5c1863e7..917b57ec8b 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectResultTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectResultTests.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults result.Formatters = new List { new CannotWriteFormatter(), - new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), false), + new JsonOutputFormatter(), }; // Act @@ -170,7 +170,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults result.Formatters = new List { new CannotWriteFormatter(), - new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), false), + new JsonOutputFormatter(), }; // Act await result.ExecuteResultAsync(actionContext); @@ -202,7 +202,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults result.Formatters = new List { mockFormatter.Object, - new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), false), + new JsonOutputFormatter(), new CannotWriteFormatter() }; // Act @@ -232,7 +232,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults result.Formatters = new List { new CannotWriteFormatter(), - new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), false), + new JsonOutputFormatter(), }; // Act @@ -263,7 +263,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults result.Formatters = new List { new CannotWriteFormatter(), - new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), false), + new JsonOutputFormatter(), }; // Act await result.ExecuteResultAsync(actionContext); @@ -364,7 +364,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults result.Formatters = new List { new CannotWriteFormatter(), - new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), false), + new JsonOutputFormatter(), }; // Act await result.ExecuteResultAsync(actionContext); @@ -455,8 +455,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults Object = nonStringValue, DeclaredType = nonStringValue.GetType() }; - var formatter = new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), false); - formatter.WriteResponseContentHeaders(formatterContext); + var formatter = new JsonOutputFormatter(); + formatter.WriteResponseHeaders(formatterContext); await formatter.WriteAsync(formatterContext); // Act @@ -559,7 +559,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults return new List() { new TextPlainFormatter(), - new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), indent: false) + new JsonOutputFormatter() }; } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs index 2e015521b6..50bc3cecb3 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs @@ -1305,9 +1305,7 @@ namespace Microsoft.AspNet.Mvc .Returns( new List() { - new JsonOutputFormatter( - JsonOutputFormatter.CreateDefaultSettings(), - indent: false) + new JsonOutputFormatter() }); httpContext.SetupGet(o => o.Request.Accept) .Returns(""); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs index f1c5c2933a..f8a32b9dd3 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs @@ -3,6 +3,7 @@ #if ASPNET50 using System; +using System.Linq; using System.Collections.Generic; using System.IO; using System.Text; @@ -11,12 +12,13 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; using Moq; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; using Xunit; namespace Microsoft.AspNet.Mvc { public class JsonInputFormatterTest - { + { [Theory] [InlineData("application/json", true)] @@ -64,7 +66,7 @@ namespace Microsoft.AspNet.Mvc { yield return new object[] { "100", typeof(int), 100 }; yield return new object[] { "'abcd'", typeof(string), "abcd" }; - yield return new object[] { "'2012-02-01 12:45 AM'", typeof(DateTime), + yield return new object[] { "'2012-02-01 12:45 AM'", typeof(DateTime), new DateTime(2012, 02, 01, 00, 45, 00) }; } } @@ -168,6 +170,72 @@ namespace Microsoft.AspNet.Mvc Assert.IsType(error.Exception); } + [Fact] + public void Creates_SerializerSettings_ByDefault() + { + // Arrange + // Act + var jsonFormatter = new JsonInputFormatter(); + + // Assert + Assert.NotNull(jsonFormatter.SerializerSettings); + } + + [Fact] + public async Task ChangesTo_DefaultSerializerSettings_TakesEffect() + { + // Arrange + // missing password property here + var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}"); + + var jsonFormatter = new JsonInputFormatter() { CaptureDeserilizationErrors = true }; + // by default we ignore missing members, so here explicitly changing it + jsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error; + + var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8"); + var metadata = new EmptyModelMetadataProvider().GetMetadataForType(modelAccessor: null, + modelType: typeof(UserLogin)); + var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType); + + // Act + var obj = await jsonFormatter.ReadAsync(inputFormatterContext); + + // Assert + Assert.False(actionContext.ModelState.IsValid); + + var modelErrorMessage = actionContext.ModelState.Values.First().Errors[0].Exception.Message; + Assert.Contains("Required property 'Password' not found in JSON", modelErrorMessage); + } + + [Fact] + public async Task CustomSerializerSettingsObject_TakesEffect() + { + // Arrange + // missing password property here + var contentBytes = Encoding.UTF8.GetBytes("{ \"UserName\" : \"John\"}"); + + var jsonFormatter = new JsonInputFormatter() { CaptureDeserilizationErrors = true }; + // by default we ignore missing members, so here explicitly changing it + jsonFormatter.SerializerSettings = new JsonSerializerSettings() + { + MissingMemberHandling = MissingMemberHandling.Error + }; + + var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8"); + var metadata = new EmptyModelMetadataProvider().GetMetadataForType(modelAccessor: null, + modelType: typeof(UserLogin)); + var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType); + + // Act + var obj = await jsonFormatter.ReadAsync(inputFormatterContext); + + // Assert + Assert.False(actionContext.ModelState.IsValid); + + var modelErrorMessage = actionContext.ModelState.Values.First().Errors[0].Exception.Message; + Assert.Contains("Required property 'Password' not found in JSON", modelErrorMessage); + } + private static ActionContext GetActionContext(byte[] contentBytes, string contentType = "application/xml") { @@ -176,7 +244,7 @@ namespace Microsoft.AspNet.Mvc new ActionDescriptor()); } - private static HttpContext GetHttpContext(byte[] contentBytes, + private static HttpContext GetHttpContext(byte[] contentBytes, string contentType = "application/json") { var request = new Mock(); @@ -197,6 +265,15 @@ namespace Microsoft.AspNet.Mvc public decimal Age { get; set; } } + + private sealed class UserLogin + { + [JsonProperty(Required = Required.Always)] + public string UserName { get; set; } + + [JsonProperty(Required = Required.Always)] + public string Password { get; set; } + } } } #endif diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonOutputFormatterTests.cs new file mode 100644 index 0000000000..03bee39f53 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonOutputFormatterTests.cs @@ -0,0 +1,128 @@ +// 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 Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Core.Test.Formatters +{ + public class JsonOutputFormatterTests + { + [Fact] + public void Creates_SerializerSettings_ByDefault() + { + // Arrange + // Act + var jsonFormatter = new JsonOutputFormatter(); + + // Assert + Assert.NotNull(jsonFormatter.SerializerSettings); + } + + + [Fact] + public async Task ChangesTo_DefaultSerializerSettings_TakesEffect() + { + // Arrange + var person = new User() { Name = "John", Age = 35 }; + var expectedOutput = JsonConvert.SerializeObject(person, new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Formatting = Formatting.Indented + }); + + var jsonFormatter = new JsonOutputFormatter(); + jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + jsonFormatter.SerializerSettings.Formatting = Formatting.Indented; + var outputFormatterContext = GetOutputFormatterContext(person, typeof(User)); + + // Act + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext); + + // Assert + Assert.NotNull(outputFormatterContext.ActionContext.HttpContext.Response.Body); + outputFormatterContext.ActionContext.HttpContext.Response.Body.Position = 0; + Assert.Equal(expectedOutput, + new StreamReader(outputFormatterContext.ActionContext.HttpContext.Response.Body, Encoding.UTF8) + .ReadToEnd()); + } + + [Fact] + public async Task CustomSerializerSettingsObject_TakesEffect() + { + // Arrange + var person = new User() { Name = "John", Age = 35 }; + var expectedOutput = JsonConvert.SerializeObject(person, new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Formatting = Formatting.Indented + }); + + var jsonFormatter = new JsonOutputFormatter(); + jsonFormatter.SerializerSettings = new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Formatting = Formatting.Indented + }; + + var outputFormatterContext = GetOutputFormatterContext(person, typeof(User)); + + // Act + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext); + + // Assert + Assert.NotNull(outputFormatterContext.ActionContext.HttpContext.Response.Body); + outputFormatterContext.ActionContext.HttpContext.Response.Body.Position = 0; + + var streamReader = new StreamReader(outputFormatterContext.ActionContext.HttpContext.Response.Body, Encoding.UTF8); + Assert.Equal(expectedOutput, streamReader.ReadToEnd()); + } + + private OutputFormatterContext GetOutputFormatterContext(object outputValue, Type outputType, + string contentType = "application/xml; charset=utf-8") + { + var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType); + + return new OutputFormatterContext + { + Object = outputValue, + DeclaredType = outputType, + ActionContext = GetActionContext(mediaTypeHeaderValue), + SelectedEncoding = Encoding.GetEncoding(mediaTypeHeaderValue.Charset) + }; + } + + private static ActionContext GetActionContext(MediaTypeHeaderValue contentType) + { + var request = new Mock(); + var headers = new Mock(); + request.Setup(r => r.ContentType).Returns(contentType.RawValue); + request.SetupGet(r => r.Headers).Returns(headers.Object); + request.SetupGet(f => f.AcceptCharset).Returns(contentType.Charset); + var response = new Mock(); + response.SetupGet(f => f.Body).Returns(new MemoryStream()); + var httpContext = new Mock(); + httpContext.SetupGet(c => c.Request).Returns(request.Object); + httpContext.SetupGet(c => c.Response).Returns(response.Object); + return new ActionContext(httpContext.Object, routeData: null, actionDescriptor: null); + } + + private sealed class User + { + public string Name { get; set; } + + public int Age { get; set; } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs index 5fe71f035f..1310b5c4d1 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs @@ -89,7 +89,7 @@ namespace Microsoft.AspNet.Mvc.Test // Act & Assert var ex = Assert.Throws( - () => testFormatter.WriteResponseContentHeaders(formatterContext)); + () => testFormatter.WriteResponseHeaders(formatterContext)); Assert.Equal("No encoding found for output formatter " + "'Microsoft.AspNet.Mvc.Test.OutputFormatterTests+TestOutputFormatter'." + " There must be at least one supported encoding registered in order for the" + @@ -111,7 +111,7 @@ namespace Microsoft.AspNet.Mvc.Test formatterContext.ActionContext = actionContext; // Act - testFormatter.WriteResponseContentHeaders(formatterContext); + testFormatter.WriteResponseHeaders(formatterContext); // Assert Assert.Equal(Encodings.UTF16EncodingLittleEndian.WebName, formatterContext.SelectedEncoding.WebName); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/JsonResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/JsonResultTest.cs index 57666f66dc..d09c1c12e5 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/JsonResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/JsonResultTest.cs @@ -20,9 +20,7 @@ namespace Microsoft.AspNet.Mvc { private static readonly byte[] _abcdUTF8Bytes = new byte[] { 123, 34, 102, 111, 111, 34, 58, 34, 97, 98, 99, 100, 34, 125 }; - private JsonOutputFormatter _jsonformatter = new JsonOutputFormatter( - JsonOutputFormatter.CreateDefaultSettings(), - indent: false); + [Fact] public async Task ExecuteResult_GeneratesResultsWithoutBOMByDefault() { @@ -95,7 +93,7 @@ namespace Microsoft.AspNet.Mvc private HttpContext GetHttpContext(HttpResponse response, bool registerDefaultFormatter = true) { - var defaultFormatters = registerDefaultFormatter ? new List() { _jsonformatter } : + var defaultFormatters = registerDefaultFormatter ? new List() { new JsonOutputFormatter() } : new List(); var httpContext = new Mock(); var mockFormattersProvider = new Mock(); @@ -129,8 +127,7 @@ namespace Microsoft.AspNet.Mvc { // Override using the selected encoding. context.SelectedEncoding = Encoding; - var jsonFormatter = new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), - indent: false); + var jsonFormatter = new JsonOutputFormatter(); await jsonFormatter.WriteResponseBodyAsync(context); } } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs index 655fae1346..8b29e54eef 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/BasicTests.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using BasicWebSite; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; +using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNet.Mvc.FunctionalTests @@ -186,6 +187,29 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.OK, response.StatusCode); } + [Fact] + public async Task JsonViewComponent_RendersJson() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = new HttpClient(server.CreateHandler(), false); + var expectedBody = JsonConvert.SerializeObject(new BasicWebSite.Models.Person() + { + Id = 10, + Name = "John" + }); + + // Act + var response = await client.GetAsync("https://localhost/Home/JsonTextInView"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("text/html", response.Content.Headers.ContentType.MediaType); + + var actualBody = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedBody, actualBody); + } + public static IEnumerable HtmlHelperLinkGenerationData { get diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs index 1c58934b83..b955261d07 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using ConnegWebsite; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; +using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNet.Mvc.FunctionalTests diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonOutputFormatterTests.cs new file mode 100644 index 0000000000..b9802e7e53 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonOutputFormatterTests.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class JsonOutputFormatterTests + { + private readonly IServiceProvider _provider = TestHelper.CreateServices("FormatterWebSite"); + private readonly Action _app = new FormatterWebSite.Startup().Configure; + + [Fact] + public async Task JsonOutputFormatter_ReturnsIndentedJson() + { + // Arrange + var user = new FormatterWebSite.User() + { + Id = 1, + Alias = "john", + description = "Administrator", + Designation = "Administrator", + Name = "John Williams" + }; + + var serializerSettings = new JsonSerializerSettings(); + serializerSettings.Formatting = Formatting.Indented; + var expectedBody = JsonConvert.SerializeObject(user, serializerSettings); + + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/JsonFormatter/ReturnsIndentedJson"); + + // Assert + var actualBody = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedBody, actualBody); + } + } +} \ No newline at end of file diff --git a/test/WebSites/ApiExplorerWebSite/Startup.cs b/test/WebSites/ApiExplorerWebSite/Startup.cs index 8a3bf87ef0..33db37168a 100644 --- a/test/WebSites/ApiExplorerWebSite/Startup.cs +++ b/test/WebSites/ApiExplorerWebSite/Startup.cs @@ -28,9 +28,7 @@ namespace ApiExplorer typeof(ApiExplorerVisbilityDisabledByConventionController))); options.OutputFormatters.Clear(); - options.OutputFormatters.Add(new JsonOutputFormatter( - JsonOutputFormatter.CreateDefaultSettings(), - indent: false)); + options.OutputFormatters.Add(new JsonOutputFormatter()); options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); }); }); diff --git a/test/WebSites/BasicWebSite/Components/JsonTextInView.cs b/test/WebSites/BasicWebSite/Components/JsonTextInView.cs new file mode 100644 index 0000000000..56e150def3 --- /dev/null +++ b/test/WebSites/BasicWebSite/Components/JsonTextInView.cs @@ -0,0 +1,18 @@ +using BasicWebSite.Models; +using Microsoft.AspNet.Mvc; + +namespace BasicWebSite.Components +{ + [ViewComponent(Name = "JsonTextInView")] + public class JsonTextInView : ViewComponent + { + public IViewComponentResult Invoke() + { + return Json(new Person() + { + Id = 10, + Name = "John" + }); + } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Controllers/HomeController.cs b/test/WebSites/BasicWebSite/Controllers/HomeController.cs index 589bf7ba75..ef7c9599e4 100644 --- a/test/WebSites/BasicWebSite/Controllers/HomeController.cs +++ b/test/WebSites/BasicWebSite/Controllers/HomeController.cs @@ -55,5 +55,10 @@ namespace BasicWebSite.Controllers Context.Response.StatusCode = 204; await Context.Response.WriteAsync("Hello world"); } + + public IActionResult JsonTextInView() + { + return View(); + } } } \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Models/Person.cs b/test/WebSites/BasicWebSite/Models/Person.cs new file mode 100644 index 0000000000..b86f0bd513 --- /dev/null +++ b/test/WebSites/BasicWebSite/Models/Person.cs @@ -0,0 +1,11 @@ +using System; + +namespace BasicWebSite.Models +{ + public class Person + { + public int Id { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Views/Home/JsonTextInView.cshtml b/test/WebSites/BasicWebSite/Views/Home/JsonTextInView.cshtml new file mode 100644 index 0000000000..cc60ee4e84 --- /dev/null +++ b/test/WebSites/BasicWebSite/Views/Home/JsonTextInView.cshtml @@ -0,0 +1 @@ +@Component.Invoke("JsonTextInView") \ No newline at end of file diff --git a/test/WebSites/ConnegWebSite/Controllers/FallbackOnTypeBasedMatchController.cs b/test/WebSites/ConnegWebSite/Controllers/FallbackOnTypeBasedMatchController.cs index 773f01edcc..625fd0498f 100644 --- a/test/WebSites/ConnegWebSite/Controllers/FallbackOnTypeBasedMatchController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/FallbackOnTypeBasedMatchController.cs @@ -28,7 +28,7 @@ namespace ConnegWebsite // JsonOutputFormatter cannot write in the first attempt because it does not support the // request content type. objectResult.Formatters.Add(new PlainTextFormatter()); - objectResult.Formatters.Add(new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), false)); + objectResult.Formatters.Add(new JsonOutputFormatter()); return objectResult; } @@ -49,7 +49,7 @@ namespace ConnegWebsite var objectResult = new ObjectResult(input); objectResult.Formatters.Add(new HttpNotAcceptableOutputFormatter()); objectResult.Formatters.Add(new PlainTextFormatter()); - objectResult.Formatters.Add(new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), false)); + objectResult.Formatters.Add(new JsonOutputFormatter()); return objectResult; } diff --git a/test/WebSites/ConnegWebSite/Controllers/NormalController.cs b/test/WebSites/ConnegWebSite/Controllers/NormalController.cs index f8c689281d..69931916cd 100644 --- a/test/WebSites/ConnegWebSite/Controllers/NormalController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/NormalController.cs @@ -3,6 +3,8 @@ using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; namespace ConnegWebsite { @@ -15,8 +17,10 @@ namespace ConnegWebsite { result.Formatters.Add(new PlainTextFormatter()); result.Formatters.Add(new CustomFormatter("application/custom")); - result.Formatters.Add(new JsonOutputFormatter(JsonOutputFormatter.CreateDefaultSettings(), - indent: true)); + + var jsonFormatter = new JsonOutputFormatter(); + jsonFormatter.SerializerSettings.Formatting = Formatting.Indented; + result.Formatters.Add(jsonFormatter); } base.OnActionExecuted(context); diff --git a/test/WebSites/FormatterWebSite/Controllers/JsonFormattterController.cs b/test/WebSites/FormatterWebSite/Controllers/JsonFormattterController.cs new file mode 100644 index 0000000000..fedc406c6c --- /dev/null +++ b/test/WebSites/FormatterWebSite/Controllers/JsonFormattterController.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.AspNet.Mvc; +using Newtonsoft.Json; + +namespace FormatterWebSite.Controllers +{ + public class JsonFormatterController : Controller + { + public IActionResult ReturnsIndentedJson() + { + var user = new User() + { + Id = 1, + Alias = "john", + description = "Administrator", + Designation = "Administrator", + Name = "John Williams" + }; + + var jsonFormatter = new JsonOutputFormatter(); + jsonFormatter.SerializerSettings.Formatting = Formatting.Indented; + + var objectResult = new ObjectResult(user); + objectResult.Formatters.Add(jsonFormatter); + + return objectResult; + } + } +} \ No newline at end of file