/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs
724 строки
28 KB
Jakub Florkowski
Fix ClientErrorMapping 500 title to match RFC 9110 (#65590)
02 июл 2026, 02:41
Не верифицирован
02 июл 2026, 02:41
813b5e7
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using JsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions; namespace Microsoft.AspNetCore.Http.Extensions.Tests; public partial class DefaultProblemDetailsWriterTest { private static readonly JsonSerializerOptions SerializerOptions = JsonOptions.DefaultSerializerOptions; [Fact] public async Task WriteAsync_Works() { // Arrange var writer = GetWriter(); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; 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", }; var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<ProblemDetails>(stream, SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(expectedProblem.Status, problemDetails.Status); Assert.Equal(expectedProblem.Type, problemDetails.Type); Assert.Equal(expectedProblem.Title, problemDetails.Title); Assert.Equal(expectedProblem.Detail, problemDetails.Detail); Assert.Equal(expectedProblem.Instance, problemDetails.Instance); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Fact] public async Task WriteAsync_Works_ProperCasing() { // Arrange var writer = GetWriter(); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; 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<string, object>() { { "extensionKey", 1 } } }; var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var result = await JsonSerializer.DeserializeAsync<Dictionary<string, object>>(stream, JsonSerializerOptions.Default); Assert.Equal(result.Keys, new(new() { { "type", 0 }, { "title", 1 }, { "status", 2 }, { "detail", 3 }, { "instance", 4 }, { "extensionKey", 5 }, {"traceId", expectedTraceId } })); } [Fact] public async Task WriteAsync_Works_ProperCasing_ValidationProblemDetails() { // Arrange var writer = GetWriter(); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; 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<string, string[]>() { { "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<Dictionary<string, object>>(stream, JsonSerializerOptions.Default); Assert.Equal(result.Keys, new(new() { { "type", 0 }, { "title", 1 }, { "status", 2 }, { "detail", 3 }, { "instance", 4 }, { "errors", 5 }, {"traceId", expectedTraceId } })); } [Fact] public async Task WriteAsync_Works_WhenReplacingProblemDetailsUsingSetter() { // Arrange var writer = GetWriter(); var stream = new MemoryStream(); var context = CreateContext(stream); var originalProblemDetails = new ProblemDetails(); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; 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", }; var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = originalProblemDetails }; problemDetailsContext.ProblemDetails = expectedProblem; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<ProblemDetails>(stream, SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(expectedProblem.Status, problemDetails.Status); Assert.Equal(expectedProblem.Type, problemDetails.Type); Assert.Equal(expectedProblem.Title, problemDetails.Title); Assert.Equal(expectedProblem.Detail, problemDetails.Detail); Assert.Equal(expectedProblem.Instance, problemDetails.Instance); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Fact] public async Task WriteAsync_Works_WithJsonContext() { // Arrange var options = new JsonOptions(); options.SerializerOptions.TypeInfoResolver = ProblemDetailsJsonContext.Default; var writer = GetWriter(jsonOptions: options); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; 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", }; var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<ProblemDetails>(stream, SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(expectedProblem.Status, problemDetails.Status); Assert.Equal(expectedProblem.Type, problemDetails.Type); Assert.Equal(expectedProblem.Title, problemDetails.Title); Assert.Equal(expectedProblem.Detail, problemDetails.Detail); Assert.Equal(expectedProblem.Instance, problemDetails.Instance); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Fact] public async Task WriteAsync_Works_WithMultipleJsonContext() { // Arrange var options = new JsonOptions(); options.SerializerOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine(CustomProblemDetailsContext.Default, CustomProblemDetailsContext2.Default, ProblemDetailsJsonContext.Default); var writer = GetWriter(jsonOptions: options); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; 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", }; var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<ProblemDetails>(stream, SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(expectedProblem.Status, problemDetails.Status); Assert.Equal(expectedProblem.Type, problemDetails.Type); Assert.Equal(expectedProblem.Title, problemDetails.Title); Assert.Equal(expectedProblem.Detail, problemDetails.Detail); Assert.Equal(expectedProblem.Instance, problemDetails.Instance); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Fact] public async Task WriteAsync_Works_WithHttpValidationProblemDetails() { // Arrange var writer = GetWriter(); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; var expectedProblem = new HttpValidationProblemDetails() { 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", }; expectedProblem.Errors.Add("sample", new string[] { "error-message" }); var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<HttpValidationProblemDetails>(stream, SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(expectedProblem.Status, problemDetails.Status); Assert.Equal(expectedProblem.Type, problemDetails.Type); Assert.Equal(expectedProblem.Title, problemDetails.Title); Assert.Equal(expectedProblem.Detail, problemDetails.Detail); Assert.Equal(expectedProblem.Instance, problemDetails.Instance); Assert.Equal(expectedProblem.Errors, problemDetails.Errors); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Fact] public async Task WriteAsync_Works_WithHttpValidationProblemDetails_AndJsonContext() { // Arrange var options = new JsonOptions(); options.SerializerOptions.TypeInfoResolver = ProblemDetailsJsonContext.Default; var writer = GetWriter(jsonOptions: options); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; var expectedProblem = new HttpValidationProblemDetails() { 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", }; expectedProblem.Errors.Add("sample", new string[] { "error-message" }); var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<HttpValidationProblemDetails>(stream, SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(expectedProblem.Status, problemDetails.Status); Assert.Equal(expectedProblem.Type, problemDetails.Type); Assert.Equal(expectedProblem.Title, problemDetails.Title); Assert.Equal(expectedProblem.Detail, problemDetails.Detail); Assert.Equal(expectedProblem.Instance, problemDetails.Instance); Assert.Equal(expectedProblem.Errors, problemDetails.Errors); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Fact] public async Task WriteAsync_Works_WithCustomDerivedProblemDetails() { // Arrange var options = new JsonOptions(); options.SerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); var writer = GetWriter(jsonOptions: options); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; var expectedProblem = new CustomProblemDetails() { 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", ExtraProperty = "My Extra property" }; var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<CustomProblemDetails>(stream, options.SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(expectedProblem.Status, problemDetails.Status); Assert.Equal(expectedProblem.Type, problemDetails.Type); Assert.Equal(expectedProblem.Title, problemDetails.Title); Assert.Equal(expectedProblem.Detail, problemDetails.Detail); Assert.Equal(expectedProblem.Instance, problemDetails.Instance); Assert.Equal(expectedProblem.ExtraProperty, problemDetails.ExtraProperty); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Fact] public async Task WriteAsync_Works_WithCustomDerivedProblemDetails_AndJsonContext() { // Arrange var options = new JsonOptions(); options.SerializerOptions.TypeInfoResolver = CustomProblemDetailsContext.Default; var writer = GetWriter(jsonOptions: options); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; var expectedProblem = new CustomProblemDetails() { 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", ExtraProperty = "My Extra property" }; var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<CustomProblemDetails>(stream, options.SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(expectedProblem.Status, problemDetails.Status); Assert.Equal(expectedProblem.Type, problemDetails.Type); Assert.Equal(expectedProblem.Title, problemDetails.Title); Assert.Equal(expectedProblem.Detail, problemDetails.Detail); Assert.Equal(expectedProblem.Instance, problemDetails.Instance); Assert.Equal(expectedProblem.ExtraProperty, problemDetails.ExtraProperty); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Fact] public async Task WriteAsync_Works_WithCustomDerivedProblemDetails_AndMultipleJsonContext() { // Arrange var options = new JsonOptions(); options.SerializerOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine(CustomProblemDetailsContext.Default, ProblemDetailsJsonContext.Default); var writer = GetWriter(jsonOptions: options); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; var expectedProblem = new CustomProblemDetails() { 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", ExtraProperty = "My Extra property" }; var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<CustomProblemDetails>(stream, options.SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(expectedProblem.Status, problemDetails.Status); Assert.Equal(expectedProblem.Type, problemDetails.Type); Assert.Equal(expectedProblem.Title, problemDetails.Title); Assert.Equal(expectedProblem.Detail, problemDetails.Detail); Assert.Equal(expectedProblem.Instance, problemDetails.Instance); Assert.Equal(expectedProblem.ExtraProperty, problemDetails.ExtraProperty); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Fact] public async Task WriteAsync_AddExtensions() { // Arrange var writer = GetWriter(); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedProblem = new ProblemDetails(); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; expectedProblem.Extensions["Extension1"] = "Extension1-Value"; expectedProblem.Extensions["Extension2"] = "Extension2-Value"; var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<ProblemDetails>(stream, SerializerOptions); Assert.NotNull(problemDetails); Assert.Collection(problemDetails.Extensions, (extension) => { Assert.Equal("Extension1", extension.Key); Assert.Equal("Extension1-Value", extension.Value.ToString()); }, (extension) => { Assert.Equal("Extension2", extension.Key); Assert.Equal("Extension2-Value", extension.Value.ToString()); }, (extension) => { Assert.Equal("traceId", extension.Key); Assert.Equal(expectedTraceId, extension.Value.ToString()); }); } [Fact] public async Task WriteAsync_AddExtensions_WithJsonContext() { // Arrange var options = new JsonOptions(); options.SerializerOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine(CustomProblemDetailsContext.Default, ProblemDetailsJsonContext.Default); var writer = GetWriter(jsonOptions: options); var stream = new MemoryStream(); var context = CreateContext(stream); var expectedProblem = new ProblemDetails(); var customExtensionData = new CustomExtensionData("test"); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; expectedProblem.Extensions["Extension"] = customExtensionData; var problemDetailsContext = new ProblemDetailsContext() { HttpContext = context, ProblemDetails = expectedProblem }; //Act await writer.WriteAsync(problemDetailsContext); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<ProblemDetails>(stream, options.SerializerOptions); Assert.NotNull(problemDetails); Assert.Collection(problemDetails.Extensions, (extension) => { Assert.Equal("Extension", extension.Key); var expectedExtension = JsonSerializer.SerializeToElement(customExtensionData, options.SerializerOptions); var value = Assert.IsType<JsonElement>(extension.Value); Assert.Equal(expectedExtension.GetProperty("data").GetString(), value.GetProperty("data").GetString()); }, (extension) => { Assert.Equal("traceId", extension.Key); Assert.Equal(expectedTraceId, extension.Value.ToString()); }); } [Fact] public async Task WriteAsync_Applies_Defaults() { // Arrange var writer = GetWriter(); var stream = new MemoryStream(); var context = CreateContext(stream, StatusCodes.Status500InternalServerError); var expectedTraceId = Activity.Current?.Id ?? context.TraceIdentifier; //Act await writer.WriteAsync(new ProblemDetailsContext() { HttpContext = context }); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<ProblemDetails>(stream, SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(StatusCodes.Status500InternalServerError, problemDetails.Status); Assert.Equal("https://tools.ietf.org/html/rfc9110#section-15.6.1", problemDetails.Type); Assert.Equal("Internal Server Error", problemDetails.Title); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Fact] public async Task WriteAsync_Applies_CustomConfiguration() { // Arrange const string expectedTraceId = "new-traceId-Value"; var options = new ProblemDetailsOptions() { CustomizeProblemDetails = (context) => { context.ProblemDetails.Status = StatusCodes.Status406NotAcceptable; context.ProblemDetails.Title = "Custom Title"; context.ProblemDetails.Extensions["new-extension"] = new { TraceId = Guid.NewGuid() }; context.ProblemDetails.Extensions["traceId"] = expectedTraceId; } }; var writer = GetWriter(options); var stream = new MemoryStream(); var context = CreateContext(stream, StatusCodes.Status500InternalServerError); //Act await writer.WriteAsync(new ProblemDetailsContext() { HttpContext = context, ProblemDetails = { Status = StatusCodes.Status400BadRequest } }); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<ProblemDetails>(stream, SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(StatusCodes.Status406NotAcceptable, problemDetails.Status); Assert.Equal("https://tools.ietf.org/html/rfc9110#section-15.5.1", problemDetails.Type); Assert.Equal("Custom Title", problemDetails.Title); Assert.Contains("new-extension", problemDetails.Extensions); Assert.Equal(expectedTraceId, problemDetails.Extensions["traceId"].ToString()); } [Theory] [InlineData(StatusCodes.Status400BadRequest, "Bad Request", "https://tools.ietf.org/html/rfc9110#section-15.5.1")] [InlineData(StatusCodes.Status418ImATeapot, "I'm a teapot", null)] [InlineData(498, null, null)] public async Task WriteAsync_UsesStatusCode_FromProblemDetails_WhenSpecified( int statusCode, string title, string type) { // Arrange var writer = GetWriter(); var stream = new MemoryStream(); var context = CreateContext(stream, StatusCodes.Status500InternalServerError); //Act await writer.WriteAsync(new ProblemDetailsContext() { HttpContext = context, ProblemDetails = { Status = statusCode } }); //Assert stream.Position = 0; var problemDetails = await JsonSerializer.DeserializeAsync<ProblemDetails>(stream, SerializerOptions); Assert.NotNull(problemDetails); Assert.Equal(statusCode, problemDetails.Status); Assert.Equal(type, problemDetails.Type); Assert.Equal(title, problemDetails.Title); } [Theory] [InlineData("")] [InlineData("*/*")] [InlineData("application/*")] [InlineData("application/json")] [InlineData("application/problem+json")] [InlineData("application/json; charset=utf-8")] [InlineData("application/json; v=1.0")] public void CanWrite_ReturnsTrue_WhenJsonAccepted(string contentType) { // Arrange var writer = GetWriter(); var stream = new MemoryStream(); var context = CreateContext(stream, contentType: contentType); //Act var result = writer.CanWrite(new() { HttpContext = context }); //Assert Assert.True(result); } [Theory] [InlineData("application/xml")] [InlineData("application/problem+xml")] public void CanWrite_ReturnsFalse_WhenJsonNotAccepted(string contentType) { // Arrange var writer = GetWriter(); var stream = new MemoryStream(); var context = CreateContext(stream, contentType: contentType); //Act var result = writer.CanWrite(new() { HttpContext = context }); //Assert Assert.False(result); } private static HttpContext CreateContext( Stream body, int statusCode = StatusCodes.Status400BadRequest, string contentType = "application/json") { var context = new DefaultHttpContext() { Response = { Body = body, StatusCode = statusCode }, RequestServices = CreateServices() }; if (!string.IsNullOrEmpty(contentType)) { context.Request.Headers.Accept = contentType; } return context; } private static IServiceProvider CreateServices() { var services = new ServiceCollection(); services.AddTransient(typeof(ILogger<>), typeof(NullLogger<>)); services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance); return services.BuildServiceProvider(); } private static DefaultProblemDetailsWriter GetWriter(ProblemDetailsOptions options = null, JsonOptions jsonOptions = null) { options ??= new ProblemDetailsOptions(); jsonOptions ??= new JsonOptions(); return new DefaultProblemDetailsWriter(Options.Create(options), Options.Create(jsonOptions)); } internal class CustomProblemDetails : ProblemDetails { public string ExtraProperty { get; set; } } [JsonSerializable(typeof(CustomProblemDetails))] [JsonSerializable(typeof(CustomExtensionData))] internal partial class CustomProblemDetailsContext : JsonSerializerContext { } [JsonSerializable(typeof(CustomProblemDetails))] internal partial class CustomProblemDetailsContext2 : JsonSerializerContext { } internal record CustomExtensionData(string Data); }