From 87ea03daf12df8f6f5e606a001af607f12e2da09 Mon Sep 17 00:00:00 2001 From: Martin Costello <martin@martincostello.com> Date: Mon, 17 Jun 2019 17:33:45 +0100 Subject: [PATCH] Register SystemTextJsonResultExecutor (#11247) Register SystemTextJsonResultExecutor as part of MVC core services so that JsonResult works without Newtonsoft.Json. Addresses #11246. --- .../MvcCoreServiceCollectionExtensions.cs | 1 + ...cs => JsonResultWithNewtonsoftJsonTest.cs} | 29 ++-- .../JsonResultWithSystemTextJsonTest.cs | 137 ++++++++++++++++++ ...JsonResultWithNewtonsoftJsonController.cs} | 6 +- .../JsonResultWithSystemTextJsonController.cs | 45 ++++++ .../BasicWebSite/StartupWithNewtonsoftJson.cs | 29 ++++ .../BasicWebSite/StartupWithSystemTextJson.cs | 28 ++++ 7 files changed, 262 insertions(+), 13 deletions(-) rename src/Mvc/test/Mvc.FunctionalTests/{JsonResultTest.cs => JsonResultWithNewtonsoftJsonTest.cs} (77%) create mode 100644 src/Mvc/test/Mvc.FunctionalTests/JsonResultWithSystemTextJsonTest.cs rename src/Mvc/test/WebSites/BasicWebSite/Controllers/{JsonResultController.cs => JsonResultWithNewtonsoftJsonController.cs} (91%) create mode 100644 src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultWithSystemTextJsonController.cs create mode 100644 src/Mvc/test/WebSites/BasicWebSite/StartupWithNewtonsoftJson.cs create mode 100644 src/Mvc/test/WebSites/BasicWebSite/StartupWithSystemTextJson.cs diff --git a/src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 380dafdc680..e34f74c2216 100644 --- a/src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -258,6 +258,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton<IActionResultExecutor<RedirectToRouteResult>, RedirectToRouteResultExecutor>(); services.TryAddSingleton<IActionResultExecutor<RedirectToPageResult>, RedirectToPageResultExecutor>(); services.TryAddSingleton<IActionResultExecutor<ContentResult>, ContentResultExecutor>(); + services.TryAddSingleton<IActionResultExecutor<JsonResult>, SystemTextJsonResultExecutor>(); services.TryAddSingleton<IClientErrorFactory, ProblemDetailsClientErrorFactory>(); // diff --git a/src/Mvc/test/Mvc.FunctionalTests/JsonResultTest.cs b/src/Mvc/test/Mvc.FunctionalTests/JsonResultWithNewtonsoftJsonTest.cs similarity index 77% rename from src/Mvc/test/Mvc.FunctionalTests/JsonResultTest.cs rename to src/Mvc/test/Mvc.FunctionalTests/JsonResultWithNewtonsoftJsonTest.cs index 43d442b9457..3f58fab658d 100644 --- a/src/Mvc/test/Mvc.FunctionalTests/JsonResultTest.cs +++ b/src/Mvc/test/Mvc.FunctionalTests/JsonResultWithNewtonsoftJsonTest.cs @@ -1,18 +1,27 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class JsonResultTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting>> + public class JsonResultWithNewtonsoftJsonTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithNewtonsoftJson>> { - public JsonResultTest(MvcTestFixture<BasicWebSite.StartupWithoutEndpointRouting> fixture) + private IServiceCollection _serviceCollection; + + public JsonResultWithNewtonsoftJsonTest(MvcTestFixture<BasicWebSite.StartupWithNewtonsoftJson> fixture) { - Client = fixture.CreateDefaultClient(); + var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(b => b.UseStartup<BasicWebSite.StartupWithNewtonsoftJson>()); + factory = factory.WithWebHostBuilder(b => b.ConfigureTestServices(serviceCollection => _serviceCollection = serviceCollection)); + + Client = factory.CreateDefaultClient(); } public HttpClient Client { get; } @@ -21,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task JsonResult_UsesDefaultContentType() { // Arrange - var url = "http://localhost/JsonResult/Plain"; + var url = "http://localhost/JsonResultWithNewtonsoftJson/Plain"; var request = new HttpRequestMessage(HttpMethod.Get, url); // Act @@ -42,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task JsonResult_Conneg_Fails(string mediaType) { // Arrange - var url = "http://localhost/JsonResult/Plain"; + var url = "http://localhost/JsonResultWithNewtonsoftJson/Plain"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.TryAddWithoutValidation("Accept", mediaType); @@ -61,7 +70,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task JsonResult_Null() { // Arrange - var url = "http://localhost/JsonResult/Null"; + var url = "http://localhost/JsonResultWithNewtonsoftJson/Null"; var request = new HttpRequestMessage(HttpMethod.Get, url); // Act @@ -79,7 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task JsonResult_String() { // Arrange - var url = "http://localhost/JsonResult/String"; + var url = "http://localhost/JsonResultWithNewtonsoftJson/String"; var request = new HttpRequestMessage(HttpMethod.Get, url); // Act @@ -96,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task JsonResult_Uses_CustomSerializerSettings() { // Arrange - var url = "http://localhost/JsonResult/CustomSerializerSettings"; + var url = "http://localhost/JsonResultWithNewtonsoftJson/CustomSerializerSettings"; var request = new HttpRequestMessage(HttpMethod.Get, url); // Act @@ -112,7 +121,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task JsonResult_CustomContentType() { // Arrange - var url = "http://localhost/JsonResult/CustomContentType"; + var url = "http://localhost/JsonResultWithNewtonsoftJson/CustomContentType"; var request = new HttpRequestMessage(HttpMethod.Get, url); // Act @@ -125,4 +134,4 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("{\"message\":\"hello\"}", content); } } -} \ No newline at end of file +} diff --git a/src/Mvc/test/Mvc.FunctionalTests/JsonResultWithSystemTextJsonTest.cs b/src/Mvc/test/Mvc.FunctionalTests/JsonResultWithSystemTextJsonTest.cs new file mode 100644 index 00000000000..fa76456e027 --- /dev/null +++ b/src/Mvc/test/Mvc.FunctionalTests/JsonResultWithSystemTextJsonTest.cs @@ -0,0 +1,137 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class JsonResultWithSystemTextJsonTest : IClassFixture<MvcTestFixture<BasicWebSite.StartupWithSystemTextJson>> + { + private IServiceCollection _serviceCollection; + + public JsonResultWithSystemTextJsonTest(MvcTestFixture<BasicWebSite.StartupWithSystemTextJson> fixture) + { + var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(b => b.UseStartup<BasicWebSite.StartupWithSystemTextJson>()); + factory = factory.WithWebHostBuilder(b => b.ConfigureTestServices(serviceCollection => _serviceCollection = serviceCollection)); + + Client = factory.CreateDefaultClient(); + } + + public HttpClient Client { get; } + + [Fact] + public async Task JsonResult_UsesDefaultContentType() + { + // Arrange + var url = "http://localhost/JsonResultWithSystemTextJson/Plain"; + var request = new HttpRequestMessage(HttpMethod.Get, url); + + // Act + var response = await Client.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType); + Assert.Equal("{\"message\":\"hello\"}", content); + } + + // Using an Accept header can't force Json to not be Json. If your accept header doesn't jive with the + // formatters/content-type configured on the result it will be ignored. + [Theory] + [InlineData("application/xml")] + [InlineData("text/xml")] + public async Task JsonResult_Conneg_Fails(string mediaType) + { + // Arrange + var url = "http://localhost/JsonResultWithSystemTextJson/Plain"; + 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("application/json", response.Content.Headers.ContentType.MediaType); + Assert.Equal("{\"message\":\"hello\"}", content); + } + + // If the object is null, it will get formatted as JSON. NOT as a 204/NoContent + [Fact] + public async Task JsonResult_Null() + { + // Arrange + var url = "http://localhost/JsonResultWithSystemTextJson/Null"; + var request = new HttpRequestMessage(HttpMethod.Get, url); + + // Act + var response = await Client.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType); + Assert.Equal("null", content); + } + + // If the object is a string, it will get formatted as JSON. NOT as text/plain. + [Fact] + public async Task JsonResult_String() + { + // Arrange + var url = "http://localhost/JsonResultWithSystemTextJson/String"; + var request = new HttpRequestMessage(HttpMethod.Get, url); + + // Act + var response = await Client.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType); + Assert.Equal("\"hello\"", content); + } + + [Fact] + public async Task JsonResult_Uses_CustomSerializerSettings() + { + // Arrange + var url = "http://localhost/JsonResultWithSystemTextJson/CustomSerializerSettings"; + var request = new HttpRequestMessage(HttpMethod.Get, url); + + // Act + var response = await Client.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("{\"Message\":\"hello\"}", content); + } + + [Fact] + public async Task JsonResult_CustomContentType() + { + // Arrange + var url = "http://localhost/JsonResultWithSystemTextJson/CustomContentType"; + var request = new HttpRequestMessage(HttpMethod.Get, url); + + // Act + var response = await Client.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("application/message+json", response.Content.Headers.ContentType.MediaType); + Assert.Equal("{\"message\":\"hello\"}", content); + } + } +} diff --git a/src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultWithNewtonsoftJsonController.cs similarity index 91% rename from src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultWithNewtonsoftJsonController.cs index 0a077336298..47f2616b1cf 100644 --- a/src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs +++ b/src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultWithNewtonsoftJsonController.cs @@ -8,11 +8,11 @@ using Newtonsoft.Json.Serialization; namespace BasicWebSite.Controllers { - public class JsonResultController : Controller + public class JsonResultWithNewtonsoftJsonController : Controller { private static readonly JsonSerializerSettings _customSerializerSettings; - static JsonResultController() + static JsonResultWithNewtonsoftJsonController() { _customSerializerSettings = JsonSerializerSettingsProvider.CreateSerializerSettings(); _customSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); @@ -45,4 +45,4 @@ namespace BasicWebSite.Controllers return new JsonResult("hello"); } } -} \ No newline at end of file +} diff --git a/src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultWithSystemTextJsonController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultWithSystemTextJsonController.cs new file mode 100644 index 00000000000..28362c511d9 --- /dev/null +++ b/src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultWithSystemTextJsonController.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; + +namespace BasicWebSite.Controllers +{ + public class JsonResultWithSystemTextJsonController : Controller + { + private static readonly JsonSerializerOptions _customSerializerSettings; + + static JsonResultWithSystemTextJsonController() + { + _customSerializerSettings = new JsonSerializerOptions(); + } + + public JsonResult Plain() + { + return new JsonResult(new { Message = "hello" }); + } + + public JsonResult CustomContentType() + { + var result = new JsonResult(new { Message = "hello" }); + result.ContentType = "application/message+json"; + return result; + } + + public JsonResult CustomSerializerSettings() + { + return new JsonResult(new { Message = "hello" }, _customSerializerSettings); + } + + public JsonResult Null() + { + return new JsonResult(null); + } + + public JsonResult String() + { + return new JsonResult("hello"); + } + } +} diff --git a/src/Mvc/test/WebSites/BasicWebSite/StartupWithNewtonsoftJson.cs b/src/Mvc/test/WebSites/BasicWebSite/StartupWithNewtonsoftJson.cs new file mode 100644 index 00000000000..b4c63a52a71 --- /dev/null +++ b/src/Mvc/test/WebSites/BasicWebSite/StartupWithNewtonsoftJson.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; + +namespace BasicWebSite +{ + public class StartupWithNewtonsoftJson + { + public void ConfigureServices(IServiceCollection services) + { + services + .AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest) + .AddNewtonsoftJson(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); + + app.UseRouting(); + + app.UseEndpoints((endpoints) => endpoints.MapDefaultControllerRoute()); + } + } +} diff --git a/src/Mvc/test/WebSites/BasicWebSite/StartupWithSystemTextJson.cs b/src/Mvc/test/WebSites/BasicWebSite/StartupWithSystemTextJson.cs new file mode 100644 index 00000000000..5d5f9d8e91e --- /dev/null +++ b/src/Mvc/test/WebSites/BasicWebSite/StartupWithSystemTextJson.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; + +namespace BasicWebSite +{ + public class StartupWithSystemTextJson + { + public void ConfigureServices(IServiceCollection services) + { + services + .AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); + } + + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); + + app.UseRouting(); + + app.UseEndpoints((endpoints) => endpoints.MapDefaultControllerRoute()); + } + } +} -- GitLab