diff --git a/src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 380dafdc6809..e34f74c2216e 100644 --- a/src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -258,6 +258,7 @@ internal static void AddMvcCoreServices(IServiceCollection services) services.TryAddSingleton, RedirectToRouteResultExecutor>(); services.TryAddSingleton, RedirectToPageResultExecutor>(); services.TryAddSingleton, ContentResultExecutor>(); + services.TryAddSingleton, SystemTextJsonResultExecutor>(); services.TryAddSingleton(); // 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 43d442b94573..3f58fab658d4 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> + public class JsonResultWithNewtonsoftJsonTest : IClassFixture> { - public JsonResultTest(MvcTestFixture fixture) + private IServiceCollection _serviceCollection; + + public JsonResultWithNewtonsoftJsonTest(MvcTestFixture fixture) { - Client = fixture.CreateDefaultClient(); + var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(b => b.UseStartup()); + factory = factory.WithWebHostBuilder(b => b.ConfigureTestServices(serviceCollection => _serviceCollection = serviceCollection)); + + Client = factory.CreateDefaultClient(); } public HttpClient Client { get; } @@ -21,7 +30,7 @@ public JsonResultTest(MvcTestFixture 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 @@ public async Task JsonResult_UsesDefaultContentType() 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 @@ public async Task JsonResult_Conneg_Fails(string mediaType) 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 @@ public async Task JsonResult_Null() 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 @@ public async Task JsonResult_String() 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 @@ public async Task JsonResult_Uses_CustomSerializerSettings() 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 @@ public async Task JsonResult_CustomContentType() 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 000000000000..fa76456e0270 --- /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> + { + private IServiceCollection _serviceCollection; + + public JsonResultWithSystemTextJsonTest(MvcTestFixture fixture) + { + var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(b => b.UseStartup()); + 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 0a077336298c..47f2616b1cfd 100644 --- a/src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs +++ b/src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultWithNewtonsoftJsonController.cs @@ -8,11 +8,11 @@ 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 @@ public JsonResult String() 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 000000000000..28362c511d9d --- /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 000000000000..b4c63a52a718 --- /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 000000000000..5d5f9d8e91ea --- /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()); + } + } +}