diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs index ccf9cba086..4592967e02 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs @@ -9,6 +9,7 @@ using Microsoft.Framework.Internal; using Microsoft.Framework.OptionsModel; using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; namespace Microsoft.AspNet.Mvc { @@ -35,6 +36,16 @@ public JsonResult(object value) { } + /// + /// Creates a new with the given . + /// + /// The value to format as JSON. + /// The to be used by the formatter. + public JsonResult(object value, JsonSerializerSettings serializerSettings) + : this(value, formatter: new JsonOutputFormatter { SerializerSettings = serializerSettings }) + { + } + /// /// Creates a new with the given . /// @@ -48,6 +59,7 @@ public JsonResult(object value, IOutputFormatter formatter) ContentTypes = new List(); } + /// /// Gets or sets the list of supported Content-Types. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index 2cf4f110da..9fe99c5ae9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -15,6 +15,7 @@ using Microsoft.AspNet.Routing; using Microsoft.Framework.Internal; using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; namespace Microsoft.AspNet.Mvc { @@ -399,6 +400,28 @@ public virtual JsonResult Json(object data) return new JsonResult(data); } + /// + /// Creates a object that serializes the specified object + /// to JSON. + /// + /// The object to serialize. + /// The to be used by the formatter. + /// The created that serializes the specified + /// to JSON format for the response. + /// It is recommended to cache the instance for the action + /// since it creates a new contract for every instance. + [NonAction] + public virtual JsonResult Json(object data, [NotNull] JsonSerializerSettings serializerSettings) + { + var disposableValue = data as IDisposable; + if (disposableValue != null) + { + Response.OnResponseCompleted(_ => disposableValue.Dispose(), state: null); + } + + return new JsonResult(data, serializerSettings); + } + /// /// Creates a object that redirects to the specified . /// diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs index 72625e3eb7..9c5b9dff96 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs @@ -18,6 +18,7 @@ using Microsoft.AspNet.WebUtilities; #if DNX451 using Moq; +using Newtonsoft.Json; #endif using Xunit; @@ -1031,6 +1032,24 @@ public void Controller_Json_WithParameterValue_SetsResultData() Assert.Same(data, actualJsonResult.Value); } + [Fact] + public void Controller_Json_WithParameterValueAndSerializerSettings_SetsRespectiveValues() + { + // Arrange + var controller = new TestableController(); + var data = new object(); + var serializerSettings = new JsonSerializerSettings(); + + // Act + var actualJsonResult = controller.Json(data, serializerSettings); + + // Assert + Assert.IsType(actualJsonResult); + Assert.Same(data, actualJsonResult.Value); + var jsonFormatter = actualJsonResult.Formatter as JsonOutputFormatter; + Assert.Same(serializerSettings, jsonFormatter.SerializerSettings); + } + [Fact] public void Controller_Json_IDisposableObject_RegistersForDispose() { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs index 28f5a2450c..4b47f515ed 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs @@ -10,6 +10,7 @@ using Microsoft.AspNet.Routing; using Microsoft.AspNet.WebUtilities; using Moq; +using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNet.Mvc @@ -209,6 +210,28 @@ public void ControllerJson_InvokedInUnitTests() Assert.Null(jsonResult.Value); } + [Fact] + public void ControllerJsonWithSerializerSettings_InvokedInUnitTests() + { + // Arrange + var controller = new TestabilityController(); + var model = new MyModel() { Property1 = "Property_1" }; + var serializerSettings = new JsonSerializerSettings(); + + // Act + var result = controller.JsonWithSerializerSettings_Action(model, serializerSettings); + + // Assert + Assert.NotNull(result); + + var jsonResult = Assert.IsType(result); + Assert.NotNull(jsonResult.Value); + Assert.Same(model, jsonResult.Value); + Assert.IsType(model.GetType(), jsonResult.Value); + var jsonFormatter = jsonResult.Formatter as JsonOutputFormatter; + Assert.Same(serializerSettings, jsonFormatter.SerializerSettings); + } + [Fact] public void ControllerHttpNotFound_InvokedInUnitTests() { @@ -590,6 +613,11 @@ public IActionResult Json_Action(object data) return Json(data); } + public IActionResult JsonWithSerializerSettings_Action(object data, JsonSerializerSettings serializerSettings) + { + return Json(data, serializerSettings); + } + public IActionResult Redirect_Action(string url) { return Redirect(url); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/JsonResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/JsonResultTest.cs index 9fa37a02bb..d914d1d9a2 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/JsonResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/JsonResultTest.cs @@ -15,6 +15,7 @@ using Microsoft.Framework.OptionsModel; using Microsoft.Net.Http.Headers; using Moq; +using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNet.Mvc @@ -24,6 +25,9 @@ public class JsonResultTest private static readonly byte[] _abcdUTF8Bytes = new byte[] { 123, 34, 102, 111, 111, 34, 58, 34, 97, 98, 99, 100, 34, 125 }; + private static readonly byte[] _abcdIndentedUTF8Bytes + = new byte[] { 123, 13, 10, 32, 32, 34, 102, 111, 111, 34, 58, 32, 34, 97, 98, 99, 100, 34, 13, 10, 125 }; + [Fact] public async Task ExecuteResultAsync_OptionsFormatter_WithoutBOM() { @@ -154,6 +158,29 @@ public async Task ExecuteResultAsync_UsesPassedInFormatter_ContentTypeSpecified( Assert.Equal("application/hal+json; charset=utf-8", context.Response.ContentType); } + [Fact] + public async Task ExecuteResultAsync_UsesPassedInSerializerSettings() + { + // Arrange + var expected = _abcdIndentedUTF8Bytes; + + var context = GetHttpContext(); + var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor()); + + var serializerSettings = new JsonSerializerSettings(); + serializerSettings.Formatting = Formatting.Indented; + + var result = new JsonResult(new { foo = "abcd" }, serializerSettings); + + // Act + await result.ExecuteResultAsync(actionContext); + var written = GetWrittenBytes(context); + + // Assert + Assert.Equal(expected, written); + Assert.Equal("application/json; charset=utf-8", context.Response.ContentType); + } + // If no formatter in options can match the given content-types, then use the one registered // in services [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonResultTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonResultTest.cs index 6b0577d5f9..483eb1a146 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonResultTest.cs @@ -161,6 +161,30 @@ public async Task JsonResult_CustomFormatter_Conneg_Fails(string mediaType) Assert.Equal("{\"message\":\"hello\"}", content); } + [Theory] + [InlineData("application/json")] + [InlineData("text/json")] + public async Task JsonResult_CustomSerializerSettings_Conneg(string mediaType) + { + // Arrange + var server = TestHelper.CreateServer(_app, SiteName, _configureServices); + var client = server.CreateClient(); + + var url = "http://localhost/JsonResult/CustomSerializerSettings"; + + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.TryAddWithoutValidation("Accept", mediaType); + + // Act + var response = await client.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(mediaType, response.Content.Headers.ContentType.MediaType); + Assert.Equal("{\"message\":\"hello\"}", content); + } + [Fact] public async Task JsonResult_CustomContentType() { diff --git a/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs b/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs index c31a0e653e..a324f311df 100644 --- a/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs +++ b/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNet.Mvc; using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace BasicWebSite.Controllers @@ -32,6 +33,14 @@ public JsonResult CustomContentType() return result; } + public JsonResult CustomSerializerSettings() + { + var serializerSettings = new JsonSerializerSettings(); + serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + + return new JsonResult(new { Message = "hello" }, serializerSettings); + } + public JsonResult Null() { return Json(null);