diff --git a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/Controllers/ProductController.cs b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/Controllers/ProductController.cs
index 6ce51f034a..188b7693d2 100644
--- a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/Controllers/ProductController.cs
+++ b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/Controllers/ProductController.cs
@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Mvc;
-using Newtonsoft.Json;
namespace GlobalDefaultJsonSerializationoptions.Controllers;
@@ -16,6 +15,6 @@ public ActionResult CreateProduct(Product product)
[HttpPost("save")]
public ActionResult SaveProduct(Product product)
{
- return Ok(JsonConvert.SerializeObject(product));
+ return Ok(product);
}
}
diff --git a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions.csproj b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions.csproj
index c7cc13203b..703f2e6bb7 100644
--- a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions.csproj
+++ b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions.csproj
@@ -1,15 +1,15 @@
- net8.0
+ net10.0
enable
enable
true
-
-
+
+
diff --git a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/Program.cs b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/Program.cs
index 0397cc9e1d..adc6baf8a5 100644
--- a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/Program.cs
+++ b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptions/Program.cs
@@ -1,27 +1,22 @@
-using Microsoft.AspNetCore.Mvc;
+using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
+using GlobalDefaultJsonSerializationoptions;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
-using GlobalDefaultJsonSerializationoptions;
-using System.Text.Encodings.Web;
var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
-builder.Services.AddControllers();
-
-builder.Services.Configure(options =>
-{
- options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
- options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
- options.JsonSerializerOptions.WriteIndented = false;
- options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Default;
- options.JsonSerializerOptions.AllowTrailingCommas = true;
- options.JsonSerializerOptions.MaxDepth = 3;
- options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
-});
+var controllers = builder.Services.AddControllers()
+ .AddJsonOptions(options =>
+ {
+ options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+ options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
+ options.JsonSerializerOptions.WriteIndented = false;
+ options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Default;
+ options.JsonSerializerOptions.AllowTrailingCommas = true;
+ options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
+ });
builder.Services.ConfigureHttpJsonOptions(options =>
{
@@ -30,28 +25,28 @@
options.SerializerOptions.WriteIndented = false;
options.SerializerOptions.Encoder = JavaScriptEncoder.Default;
options.SerializerOptions.AllowTrailingCommas = true;
- options.SerializerOptions.MaxDepth = 3;
options.SerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
});
-JsonConvert.DefaultSettings = () => new JsonSerializerSettings
+// AddNewtonsoftJson() replaces both MVC formatters, so it would take the
+// System.Text.Json configuration above out of play for every controller action.
+// The sample demonstrates all three approaches, so this one is registered behind
+// a configuration switch: run with UseNewtonsoftJson=true to serialize controller
+// responses with Json.NET instead.
+if (builder.Configuration.GetValue("UseNewtonsoftJson"))
{
- ContractResolver = new CamelCasePropertyNamesContractResolver(),
- Formatting = Formatting.Indented,
- NullValueHandling = NullValueHandling.Ignore,
- DateFormatString = "dd-MM-yyyy",
- DefaultValueHandling = DefaultValueHandling.Ignore,
- MaxDepth = 3
-};
+ controllers.AddNewtonsoftJson(options =>
+ {
+ options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
+ options.SerializerSettings.Formatting = Formatting.Indented;
+ options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
+ options.SerializerSettings.DateFormatString = "dd-MM-yyyy";
+ options.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Ignore;
+ });
+}
var app = builder.Build();
-if (app.Environment.IsDevelopment())
-{
- app.UseSwagger();
- app.UseSwaggerUI();
-}
-
app.UseHttpsRedirection();
app.UseAuthorization();
@@ -64,3 +59,5 @@
});
app.Run();
+
+public partial class Program;
diff --git a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/GlobalDefaultJsonSerializationoptionsUnitTests.csproj b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/GlobalDefaultJsonSerializationoptionsUnitTests.csproj
index f0985e8813..4522c9d726 100644
--- a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/GlobalDefaultJsonSerializationoptionsUnitTests.csproj
+++ b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/GlobalDefaultJsonSerializationoptionsUnitTests.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
enable
@@ -10,11 +10,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/GlobalJsonOptionsIntegrationTests.cs b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/GlobalJsonOptionsIntegrationTests.cs
new file mode 100644
index 0000000000..3ded8c4eeb
--- /dev/null
+++ b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/GlobalJsonOptionsIntegrationTests.cs
@@ -0,0 +1,90 @@
+using System.Text;
+using System.Text.Json.Nodes;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace GlobalDefaultJsonSerializationoptionsUnitTests;
+
+[TestClass]
+public class GlobalJsonOptionsIntegrationTests
+{
+ private const string RequestBody = """
+ {
+ "Id": 1,
+ "Name": null,
+ "Price": 0,
+ "Quantity": "5",
+ "ReleaseDate": "2024-04-14T10:49:31.813Z",
+ "Manufacturer": { "Name": "Apple", "Location": "California" }
+ }
+ """;
+
+ private static HttpContent JsonContent() =>
+ new StringContent(RequestBody, Encoding.UTF8, "application/json");
+
+ private static WebApplicationFactory CreateFactory(bool useNewtonsoftJson) =>
+ new WebApplicationFactory()
+ .WithWebHostBuilder(builder =>
+ builder.UseSetting("UseNewtonsoftJson", useNewtonsoftJson.ToString()));
+
+ private static async Task PostAsync(HttpClient client, string route)
+ {
+ var response = await client.PostAsync(route, JsonContent());
+ var body = await response.Content.ReadAsStringAsync();
+
+ Assert.IsTrue(response.IsSuccessStatusCode, body);
+
+ return JsonNode.Parse(body)!.AsObject();
+ }
+
+ [TestMethod]
+ public async Task GivenSystemTextJsonOptions_WhenControllerActionResponds_ThenItAppliesTheConfiguredOptions()
+ {
+ using var factory = CreateFactory(useNewtonsoftJson: false);
+ using var client = factory.CreateClient();
+
+ var payload = await PostAsync(client, "/api/Product");
+
+ // Camel casing, nulls dropped, and a number read from a string.
+ CollectionAssert.AreEquivalent(
+ new[] { "id", "price", "quantity", "releaseDate", "manufacturer" },
+ payload.Select(property => property.Key).ToArray());
+ Assert.AreEqual(5, (int)payload["quantity"]!);
+ Assert.AreEqual("Apple", (string?)payload["manufacturer"]!["name"]);
+
+ // The Newtonsoft date format is not registered on this run.
+ Assert.AreNotEqual("14-04-2024", (string?)payload["releaseDate"]);
+ }
+
+ [TestMethod]
+ public async Task GivenNewtonsoftJsonIsRegistered_WhenControllerActionResponds_ThenJsonNetWritesTheResponse()
+ {
+ using var factory = CreateFactory(useNewtonsoftJson: true);
+ using var client = factory.CreateClient();
+
+ var payload = await PostAsync(client, "/api/Product/save");
+
+ // AddNewtonsoftJson() replaced the MVC formatters: Json.NET's date format
+ // and DefaultValueHandling.Ignore are both visible in the response.
+ CollectionAssert.AreEquivalent(
+ new[] { "id", "quantity", "releaseDate", "manufacturer" },
+ payload.Select(property => property.Key).ToArray());
+ Assert.AreEqual("14-04-2024", (string?)payload["releaseDate"]);
+ }
+
+ [TestMethod]
+ public async Task GivenNewtonsoftJsonIsRegistered_WhenMinimalApiEndpointResponds_ThenHttpJsonOptionsStillApply()
+ {
+ using var factory = CreateFactory(useNewtonsoftJson: true);
+ using var client = factory.CreateClient();
+
+ var payload = await PostAsync(client, "/api/Product/create");
+
+ // MVC options and Http JSON options do not cascade: the minimal API
+ // endpoint keeps serializing with System.Text.Json.
+ CollectionAssert.AreEquivalent(
+ new[] { "id", "price", "quantity", "releaseDate", "manufacturer" },
+ payload.Select(property => property.Key).ToArray());
+ Assert.AreNotEqual("14-04-2024", (string?)payload["releaseDate"]);
+ }
+}
diff --git a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/ProductControllerUnitTests.cs b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/ProductControllerUnitTests.cs
index d49a23d6e6..f803382284 100644
--- a/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/ProductControllerUnitTests.cs
+++ b/json-csharp/GlobalDefaultJsonSerializationoptions/GlobalDefaultJsonSerializationoptionsUnitTests/ProductControllerUnitTests.cs
@@ -2,7 +2,6 @@
using GlobalDefaultJsonSerializationoptions.Controllers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Newtonsoft.Json;
namespace GlobalDefaultJsonSerializationoptionsUnitTests;
@@ -42,8 +41,6 @@ public void GivenValidProduct_WhenSaveProductIsCalled_ThenReturnsProductWithSame
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(200, result.StatusCode);
-
- var expectedJson = JsonConvert.SerializeObject(product);
- Assert.AreEqual(expectedJson, result?.Value?.ToString());
+ Assert.AreEqual(product, result.Value);
}
}