diff --git a/src/Http/Http.Abstractions/src/ProblemDetails/HttpValidationProblemDetails.cs b/src/Http/Http.Abstractions/src/ProblemDetails/HttpValidationProblemDetails.cs index af225a4b13fd..99d07781b92a 100644 --- a/src/Http/Http.Abstractions/src/ProblemDetails/HttpValidationProblemDetails.cs +++ b/src/Http/Http.Abstractions/src/ProblemDetails/HttpValidationProblemDetails.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Mvc; namespace Microsoft.AspNetCore.Http; @@ -36,5 +37,6 @@ private HttpValidationProblemDetails(Dictionary errors) /// /// Gets the validation errors associated with this instance of . /// + [JsonPropertyName("errors")] public IDictionary Errors { get; set; } = new Dictionary(StringComparer.Ordinal); } diff --git a/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs b/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs index 96c7db573ba0..02a002451c4f 100644 --- a/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs +++ b/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs @@ -18,6 +18,7 @@ public class ProblemDetails /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyOrder(-5)] + [JsonPropertyName("type")] public string? Type { get; set; } /// @@ -27,6 +28,7 @@ public class ProblemDetails /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyOrder(-4)] + [JsonPropertyName("title")] public string? Title { get; set; } /// @@ -34,6 +36,7 @@ public class ProblemDetails /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyOrder(-3)] + [JsonPropertyName("status")] public int? Status { get; set; } /// @@ -41,6 +44,7 @@ public class ProblemDetails /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyOrder(-2)] + [JsonPropertyName("detail")] public string? Detail { get; set; } /// @@ -48,6 +52,7 @@ public class ProblemDetails /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyOrder(-1)] + [JsonPropertyName("instance")] public string? Instance { get; set; } /// diff --git a/src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs b/src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs index 5bd053f13f7d..8df91d099930 100644 --- a/src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs +++ b/src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs @@ -54,6 +54,68 @@ public async Task WriteAsync_Works() Assert.Equal(expectedProblem.Instance, problemDetails.Instance); } + [Fact] + public async Task WriteAsync_Works_ProperCasing() + { + // Arrange + var writer = GetWriter(); + var stream = new MemoryStream(); + var context = CreateContext(stream); + var expectedProblem = new ProblemDetails() + { + Detail = "Custom Bad Request", + Instance = "Custom Bad Request", + Status = StatusCodes.Status400BadRequest, + Type = "https://tools.ietf.org/html/rfc9110#section-15.5.1-custom", + Title = "Custom Bad Request", + Extensions = new Dictionary() { { "extensionKey", 1 } } + }; + var problemDetailsContext = new ProblemDetailsContext() + { + HttpContext = context, + ProblemDetails = expectedProblem + }; + + //Act + await writer.WriteAsync(problemDetailsContext); + + //Assert + stream.Position = 0; + var result = await JsonSerializer.DeserializeAsync>(stream, JsonSerializerOptions.Default); + Assert.Equal(result.Keys, new(new() { { "type", 0 }, { "title", 1 }, { "status", 2 }, { "detail", 3 }, { "instance", 4 }, { "extensionKey", 5 } })); + } + + [Fact] + public async Task WriteAsync_Works_ProperCasing_ValidationProblemDetails() + { + // Arrange + var writer = GetWriter(); + var stream = new MemoryStream(); + var context = CreateContext(stream); + var expectedProblem = new ValidationProblemDetails() + { + Detail = "Custom Bad Request", + Instance = "Custom Bad Request", + Status = StatusCodes.Status400BadRequest, + Type = "https://tools.ietf.org/html/rfc9110#section-15.5.1-custom", + Title = "Custom Bad Request", + Errors = new Dictionary() { { "name", ["Name is invalid."] } } + }; + var problemDetailsContext = new ProblemDetailsContext() + { + HttpContext = context, + ProblemDetails = expectedProblem + }; + + //Act + await writer.WriteAsync(problemDetailsContext); + + //Assert + stream.Position = 0; + var result = await JsonSerializer.DeserializeAsync>(stream, JsonSerializerOptions.Default); + Assert.Equal(result.Keys, new(new() { { "type", 0 }, { "title", 1 }, { "status", 2 }, { "detail", 3 }, { "instance", 4 }, { "errors", 5 } })); + } + [Fact] public async Task WriteAsync_Works_WhenReplacingProblemDetailsUsingSetter() { diff --git a/src/Mvc/Mvc.Core/src/ValidationProblemDetails.cs b/src/Mvc/Mvc.Core/src/ValidationProblemDetails.cs index 0701521ebb3e..d3ce4af1a8ce 100644 --- a/src/Mvc/Mvc.Core/src/ValidationProblemDetails.cs +++ b/src/Mvc/Mvc.Core/src/ValidationProblemDetails.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -81,5 +82,6 @@ public ValidationProblemDetails(IDictionary errors) /// /// Gets the validation errors associated with this instance of . /// + [JsonPropertyName("errors")] public new IDictionary Errors { get { return base.Errors; } set { base.Errors = value; } } }