From 2143f21b8c77ba5f4d2a223c222b566a3c183825 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 9 Jan 2023 11:29:49 -0800 Subject: [PATCH 01/24] Adding STJ Polymorphism to Result Types --- src/Http/Http.Results/src/Accepted.cs | 2 +- src/Http/Http.Results/src/AcceptedAtRoute.cs | 2 +- .../Http.Results/src/AcceptedAtRouteOfT.cs | 6 +-- src/Http/Http.Results/src/AcceptedOfT.cs | 8 ++-- src/Http/Http.Results/src/BadRequest.cs | 2 +- src/Http/Http.Results/src/BadRequestOfT.cs | 6 +-- src/Http/Http.Results/src/Conflict.cs | 2 +- src/Http/Http.Results/src/ConflictOfT.cs | 6 +-- .../Http.Results/src/ContentHttpResult.cs | 4 +- src/Http/Http.Results/src/Created.cs | 2 +- src/Http/Http.Results/src/CreatedAtRoute.cs | 2 +- .../Http.Results/src/CreatedAtRouteOfT.cs | 6 +-- src/Http/Http.Results/src/CreatedOfT.cs | 8 ++-- .../Http.Results/src/FileContentHttpResult.cs | 2 +- .../Http.Results/src/FileStreamHttpResult.cs | 2 +- ...pResultsHelper.cs => HttpResultsWriter.cs} | 47 +++++++++++++------ .../Http.Results/src/JsonHttpResultOfT.cs | 4 +- .../Microsoft.AspNetCore.Http.Results.csproj | 1 + src/Http/Http.Results/src/NoContent.cs | 2 +- src/Http/Http.Results/src/NotFound.cs | 2 +- src/Http/Http.Results/src/NotFoundOfT.cs | 6 +-- src/Http/Http.Results/src/Ok.cs | 2 +- src/Http/Http.Results/src/OkOfT.cs | 6 +-- .../src/PhysicalFileHttpResult.cs | 2 +- .../Http.Results/src/ProblemHttpResult.cs | 4 +- .../Http.Results/src/PushStreamHttpResult.cs | 2 +- .../Http.Results/src/StatusCodeHttpResult.cs | 2 +- .../src/UnauthorizedHttpResult.cs | 2 +- .../Http.Results/src/UnprocessableEntity.cs | 2 +- .../src/UnprocessableEntityOfT.cs | 6 +-- .../Http.Results/src/Utf8ContentHttpResult.cs | 4 +- .../Http.Results/src/ValidationProblem.cs | 4 +- .../Http.Results/src/VirtualFileHttpResult.cs | 2 +- .../test/HttpResultsHelperTests.cs | 43 +++++++++++------ src/Http/Http.Results/test/JsonResultTests.cs | 5 +- 35 files changed, 122 insertions(+), 86 deletions(-) rename src/Http/Http.Results/src/{HttpResultsHelper.cs => HttpResultsWriter.cs} (79%) diff --git a/src/Http/Http.Results/src/Accepted.cs b/src/Http/Http.Results/src/Accepted.cs index 0a23b209c2b6..50898a01a785 100644 --- a/src/Http/Http.Results/src/Accepted.cs +++ b/src/Http/Http.Results/src/Accepted.cs @@ -69,7 +69,7 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = Location; } - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/AcceptedAtRoute.cs b/src/Http/Http.Results/src/AcceptedAtRoute.cs index 55489db2d29c..05917ad4265a 100644 --- a/src/Http/Http.Results/src/AcceptedAtRoute.cs +++ b/src/Http/Http.Results/src/AcceptedAtRoute.cs @@ -81,7 +81,7 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = url; - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs index c270926e5223..52044bcde7ef 100644 --- a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs @@ -44,7 +44,7 @@ internal AcceptedAtRoute( Value = value; RouteName = routeName; RouteValues = new RouteValueDictionary(routeValues); - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -94,10 +94,10 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = url; - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsHelper.WriteResultAsJsonAsync(httpContext, logger, Value); + return HttpResultsWriter.WriteResultAsJsonAsync(httpContext, logger, Value); } /// diff --git a/src/Http/Http.Results/src/AcceptedOfT.cs b/src/Http/Http.Results/src/AcceptedOfT.cs index cc3051108d36..57dbc7df744a 100644 --- a/src/Http/Http.Results/src/AcceptedOfT.cs +++ b/src/Http/Http.Results/src/AcceptedOfT.cs @@ -26,7 +26,7 @@ internal Accepted(string? location, TValue? value) { Value = value; Location = location; - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -38,7 +38,7 @@ internal Accepted(string? location, TValue? value) internal Accepted(Uri locationUri, TValue? value) { Value = value; - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); if (locationUri == null) { @@ -88,10 +88,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.AcceptedResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger, Value); diff --git a/src/Http/Http.Results/src/BadRequest.cs b/src/Http/Http.Results/src/BadRequest.cs index e27c56889ae5..990db867f44a 100644 --- a/src/Http/Http.Results/src/BadRequest.cs +++ b/src/Http/Http.Results/src/BadRequest.cs @@ -39,7 +39,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.BadRequestObjectResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/BadRequestOfT.cs b/src/Http/Http.Results/src/BadRequestOfT.cs index c9d48ed8be4f..2bf6db3e1cd0 100644 --- a/src/Http/Http.Results/src/BadRequestOfT.cs +++ b/src/Http/Http.Results/src/BadRequestOfT.cs @@ -24,7 +24,7 @@ public sealed class BadRequest : IResult, IEndpointMetadataProvider, ISt internal BadRequest(TValue? error) { Value = error; - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -50,10 +50,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.BadRequestObjectResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger: logger, Value); diff --git a/src/Http/Http.Results/src/Conflict.cs b/src/Http/Http.Results/src/Conflict.cs index 5504c2cab069..409401cff64c 100644 --- a/src/Http/Http.Results/src/Conflict.cs +++ b/src/Http/Http.Results/src/Conflict.cs @@ -39,7 +39,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.ConflictObjectResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/ConflictOfT.cs b/src/Http/Http.Results/src/ConflictOfT.cs index f78bf8b8b76c..16d76134d379 100644 --- a/src/Http/Http.Results/src/ConflictOfT.cs +++ b/src/Http/Http.Results/src/ConflictOfT.cs @@ -24,7 +24,7 @@ public sealed class Conflict : IResult, IEndpointMetadataProvider, IStat internal Conflict(TValue? error) { Value = error; - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -50,10 +50,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.ConflictObjectResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger: logger, Value); diff --git a/src/Http/Http.Results/src/ContentHttpResult.cs b/src/Http/Http.Results/src/ContentHttpResult.cs index 7d154b0ae853..b2ea33212321 100644 --- a/src/Http/Http.Results/src/ContentHttpResult.cs +++ b/src/Http/Http.Results/src/ContentHttpResult.cs @@ -65,11 +65,11 @@ public Task ExecuteAsync(HttpContext httpContext) if (StatusCode is { } statusCode) { - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, statusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, statusCode); httpContext.Response.StatusCode = statusCode; } - return HttpResultsHelper.WriteResultAsContentAsync( + return HttpResultsWriter.WriteResultAsContentAsync( httpContext, logger, ResponseContent, diff --git a/src/Http/Http.Results/src/Created.cs b/src/Http/Http.Results/src/Created.cs index 2908e07471b5..9108c7a877ab 100644 --- a/src/Http/Http.Results/src/Created.cs +++ b/src/Http/Http.Results/src/Created.cs @@ -69,7 +69,7 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = Location; } - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/CreatedAtRoute.cs b/src/Http/Http.Results/src/CreatedAtRoute.cs index 213576e24e52..c8e41924a9f8 100644 --- a/src/Http/Http.Results/src/CreatedAtRoute.cs +++ b/src/Http/Http.Results/src/CreatedAtRoute.cs @@ -81,7 +81,7 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = url; - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs index 6d543d9b98f5..29cd2bf927f5 100644 --- a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs @@ -44,7 +44,7 @@ internal CreatedAtRoute( Value = value; RouteName = routeName; RouteValues = new RouteValueDictionary(routeValues); - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -94,10 +94,10 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = url; - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger, Value); diff --git a/src/Http/Http.Results/src/CreatedOfT.cs b/src/Http/Http.Results/src/CreatedOfT.cs index 89e02738458f..f3a56d8845a2 100644 --- a/src/Http/Http.Results/src/CreatedOfT.cs +++ b/src/Http/Http.Results/src/CreatedOfT.cs @@ -26,7 +26,7 @@ internal Created(string? location, TValue? value) { Value = value; Location = location; - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -38,7 +38,7 @@ internal Created(string? location, TValue? value) internal Created(Uri? locationUri, TValue? value) { Value = value; - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); if (locationUri != null) { @@ -84,10 +84,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.CreatedResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger, Value); diff --git a/src/Http/Http.Results/src/FileContentHttpResult.cs b/src/Http/Http.Results/src/FileContentHttpResult.cs index b8496be0752d..390a359bc007 100644 --- a/src/Http/Http.Results/src/FileContentHttpResult.cs +++ b/src/Http/Http.Results/src/FileContentHttpResult.cs @@ -112,7 +112,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.FileContentResult"); - var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore( + var (range, rangeLength, completed) = HttpResultsWriter.WriteResultAsFileCore( httpContext, logger, FileDownloadName, diff --git a/src/Http/Http.Results/src/FileStreamHttpResult.cs b/src/Http/Http.Results/src/FileStreamHttpResult.cs index 4876449991b4..41eff86b29ad 100644 --- a/src/Http/Http.Results/src/FileStreamHttpResult.cs +++ b/src/Http/Http.Results/src/FileStreamHttpResult.cs @@ -123,7 +123,7 @@ public async Task ExecuteAsync(HttpContext httpContext) await using (FileStream) { - var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore( + var (range, rangeLength, completed) = HttpResultsWriter.WriteResultAsFileCore( httpContext, logger, FileDownloadName, diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsWriter.cs similarity index 79% rename from src/Http/Http.Results/src/HttpResultsHelper.cs rename to src/Http/Http.Results/src/HttpResultsWriter.cs index cb7e1edf20a5..8619c38f597d 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsWriter.cs @@ -3,17 +3,23 @@ using System.Text; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using Microsoft.AspNetCore.Http.Json; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Http; -internal static partial class HttpResultsHelper +internal static partial class HttpResultsWriter { internal const string DefaultContentType = "text/plain; charset=utf-8"; private static readonly Encoding DefaultEncoding = Encoding.UTF8; + private static JsonSerializerOptions? _defaultSerializerOptions; + private readonly static object _syncObj = new(); public static Task WriteResultAsJsonAsync( HttpContext httpContext, @@ -27,32 +33,45 @@ public static Task WriteResultAsJsonAsync( return Task.CompletedTask; } - var declaredType = typeof(T); - if (declaredType.IsValueType) - { - Log.WritingResultAsJson(logger, declaredType.Name); + jsonSerializerOptions ??= GetDefaultOptions(httpContext); + var declaredTypeInfo = (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(T)); + if (value is null || declaredTypeInfo.IsPolymorphicSafe()) + { // In this case the polymorphism is not // relevant and we don't need to box. return httpContext.Response.WriteAsJsonAsync( - value, - options: jsonSerializerOptions, - contentType: contentType); + value, + declaredTypeInfo, + contentType: contentType); } - var runtimeType = value.GetType(); - - Log.WritingResultAsJson(logger, runtimeType.Name); - // Call WriteAsJsonAsync() with the runtime type to serialize the runtime type rather than the declared type // and avoid source generators issues. // https://github.com/dotnet/aspnetcore/issues/43894 // https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-polymorphism + + var jsonTypeInfo = jsonSerializerOptions.GetTypeInfo(value.GetType()); + Log.WritingResultAsJson(logger, jsonTypeInfo.Type.Name); + return httpContext.Response.WriteAsJsonAsync( value, - runtimeType, - options: jsonSerializerOptions, + jsonTypeInfo, contentType: contentType); + + static JsonSerializerOptions GetDefaultOptions(HttpContext httpContext) + { + if (_defaultSerializerOptions != null) + { + return _defaultSerializerOptions; + } + + // TODO: Should I lock it??? + lock (_syncObj) + { + return _defaultSerializerOptions = httpContext.RequestServices.GetService>()?.Value.SerializerOptions ?? new JsonOptions().SerializerOptions; + } + } } public static Task WriteResultAsContentAsync( diff --git a/src/Http/Http.Results/src/JsonHttpResultOfT.cs b/src/Http/Http.Results/src/JsonHttpResultOfT.cs index d743591d547f..dce5a1950571 100644 --- a/src/Http/Http.Results/src/JsonHttpResultOfT.cs +++ b/src/Http/Http.Results/src/JsonHttpResultOfT.cs @@ -89,11 +89,11 @@ public Task ExecuteAsync(HttpContext httpContext) if (StatusCode is { } statusCode) { - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, statusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, statusCode); httpContext.Response.StatusCode = statusCode; } - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger, Value, diff --git a/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj index 7808c52b5ec9..3bc7cec04167 100644 --- a/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj +++ b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Http/Http.Results/src/NoContent.cs b/src/Http/Http.Results/src/NoContent.cs index 75c8cfa2c1c7..0b9727579bb0 100644 --- a/src/Http/Http.Results/src/NoContent.cs +++ b/src/Http/Http.Results/src/NoContent.cs @@ -38,7 +38,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.NoContentResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; diff --git a/src/Http/Http.Results/src/NotFound.cs b/src/Http/Http.Results/src/NotFound.cs index 67df2f05af2c..a840fba4b4a3 100644 --- a/src/Http/Http.Results/src/NotFound.cs +++ b/src/Http/Http.Results/src/NotFound.cs @@ -38,7 +38,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.NotFoundObjectResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/NotFoundOfT.cs b/src/Http/Http.Results/src/NotFoundOfT.cs index f358691e1ecd..3d05b5c49217 100644 --- a/src/Http/Http.Results/src/NotFoundOfT.cs +++ b/src/Http/Http.Results/src/NotFoundOfT.cs @@ -23,7 +23,7 @@ public sealed class NotFound : IResult, IEndpointMetadataProvider, IStat internal NotFound(TValue? value) { Value = value; - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -49,10 +49,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.NotFoundObjectResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger: logger, Value); diff --git a/src/Http/Http.Results/src/Ok.cs b/src/Http/Http.Results/src/Ok.cs index 110058a2591c..52c8e125d1bf 100644 --- a/src/Http/Http.Results/src/Ok.cs +++ b/src/Http/Http.Results/src/Ok.cs @@ -38,7 +38,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.OkObjectResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/OkOfT.cs b/src/Http/Http.Results/src/OkOfT.cs index d5a9646f70f4..fecc748f028d 100644 --- a/src/Http/Http.Results/src/OkOfT.cs +++ b/src/Http/Http.Results/src/OkOfT.cs @@ -23,7 +23,7 @@ public sealed class Ok : IResult, IEndpointMetadataProvider, IStatusCode internal Ok(TValue? value) { Value = value; - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -49,10 +49,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.OkObjectResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger: logger, Value); diff --git a/src/Http/Http.Results/src/PhysicalFileHttpResult.cs b/src/Http/Http.Results/src/PhysicalFileHttpResult.cs index 7b44bd6c1bc3..b4cfb1a0d66a 100644 --- a/src/Http/Http.Results/src/PhysicalFileHttpResult.cs +++ b/src/Http/Http.Results/src/PhysicalFileHttpResult.cs @@ -122,7 +122,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.PhysicalFileResult"); - var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore( + var (range, rangeLength, completed) = HttpResultsWriter.WriteResultAsFileCore( httpContext, logger, FileDownloadName, diff --git a/src/Http/Http.Results/src/ProblemHttpResult.cs b/src/Http/Http.Results/src/ProblemHttpResult.cs index b0a42aaa050e..6c6ce23edab4 100644 --- a/src/Http/Http.Results/src/ProblemHttpResult.cs +++ b/src/Http/Http.Results/src/ProblemHttpResult.cs @@ -55,11 +55,11 @@ public Task ExecuteAsync(HttpContext httpContext) if (StatusCode is { } code) { - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, code); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, code); httpContext.Response.StatusCode = code; } - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger, value: ProblemDetails, diff --git a/src/Http/Http.Results/src/PushStreamHttpResult.cs b/src/Http/Http.Results/src/PushStreamHttpResult.cs index e9c61785d37c..2ea30e3d719f 100644 --- a/src/Http/Http.Results/src/PushStreamHttpResult.cs +++ b/src/Http/Http.Results/src/PushStreamHttpResult.cs @@ -106,7 +106,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.PushStreamResult"); - var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore( + var (range, rangeLength, completed) = HttpResultsWriter.WriteResultAsFileCore( httpContext, logger, FileDownloadName, diff --git a/src/Http/Http.Results/src/StatusCodeHttpResult.cs b/src/Http/Http.Results/src/StatusCodeHttpResult.cs index 67c1f039cfff..5b2fc99f0bdd 100644 --- a/src/Http/Http.Results/src/StatusCodeHttpResult.cs +++ b/src/Http/Http.Results/src/StatusCodeHttpResult.cs @@ -41,7 +41,7 @@ public Task ExecuteAsync(HttpContext httpContext) // Creating the logger with a string to preserve the category after the refactoring. var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.StatusCodeResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; diff --git a/src/Http/Http.Results/src/UnauthorizedHttpResult.cs b/src/Http/Http.Results/src/UnauthorizedHttpResult.cs index a1a1320514e9..fecb51f1124a 100644 --- a/src/Http/Http.Results/src/UnauthorizedHttpResult.cs +++ b/src/Http/Http.Results/src/UnauthorizedHttpResult.cs @@ -34,7 +34,7 @@ public Task ExecuteAsync(HttpContext httpContext) // Creating the logger with a string to preserve the category after the refactoring. var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.UnauthorizedResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; diff --git a/src/Http/Http.Results/src/UnprocessableEntity.cs b/src/Http/Http.Results/src/UnprocessableEntity.cs index cc38731f1165..192a489f5363 100644 --- a/src/Http/Http.Results/src/UnprocessableEntity.cs +++ b/src/Http/Http.Results/src/UnprocessableEntity.cs @@ -39,7 +39,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.UnprocessableEntityObjectResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/UnprocessableEntityOfT.cs b/src/Http/Http.Results/src/UnprocessableEntityOfT.cs index d4eca8216826..e94c5d286183 100644 --- a/src/Http/Http.Results/src/UnprocessableEntityOfT.cs +++ b/src/Http/Http.Results/src/UnprocessableEntityOfT.cs @@ -24,7 +24,7 @@ public sealed class UnprocessableEntity : IResult, IEndpointMetadataProv internal UnprocessableEntity(TValue? value) { Value = value; - HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -50,10 +50,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.UnprocessableEntityObjectResult"); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger: logger, Value); diff --git a/src/Http/Http.Results/src/Utf8ContentHttpResult.cs b/src/Http/Http.Results/src/Utf8ContentHttpResult.cs index 725c2135e29a..4338975265f6 100644 --- a/src/Http/Http.Results/src/Utf8ContentHttpResult.cs +++ b/src/Http/Http.Results/src/Utf8ContentHttpResult.cs @@ -56,11 +56,11 @@ public Task ExecuteAsync(HttpContext httpContext) if (StatusCode is { } statusCode) { - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, statusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, statusCode); httpContext.Response.StatusCode = statusCode; } - httpContext.Response.ContentType = ContentType ?? HttpResultsHelper.DefaultContentType; + httpContext.Response.ContentType = ContentType ?? HttpResultsWriter.DefaultContentType; httpContext.Response.ContentLength = ResponseContent.Length; return httpContext.Response.Body.WriteAsync(ResponseContent).AsTask(); diff --git a/src/Http/Http.Results/src/ValidationProblem.cs b/src/Http/Http.Results/src/ValidationProblem.cs index 84ead95982d5..d00ff853526c 100644 --- a/src/Http/Http.Results/src/ValidationProblem.cs +++ b/src/Http/Http.Results/src/ValidationProblem.cs @@ -56,10 +56,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger(typeof(ValidationProblem)); - HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsHelper.WriteResultAsJsonAsync( + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger, value: ProblemDetails, diff --git a/src/Http/Http.Results/src/VirtualFileHttpResult.cs b/src/Http/Http.Results/src/VirtualFileHttpResult.cs index e3107f4b7929..458ddd4365de 100644 --- a/src/Http/Http.Results/src/VirtualFileHttpResult.cs +++ b/src/Http/Http.Results/src/VirtualFileHttpResult.cs @@ -117,7 +117,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.VirtualFileResult"); - var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore( + var (range, rangeLength, completed) = HttpResultsWriter.WriteResultAsFileCore( httpContext, logger, FileDownloadName, diff --git a/src/Http/Http.Results/test/HttpResultsHelperTests.cs b/src/Http/Http.Results/test/HttpResultsHelperTests.cs index b02612829fbb..fc97aefa8ef4 100644 --- a/src/Http/Http.Results/test/HttpResultsHelperTests.cs +++ b/src/Http/Http.Results/test/HttpResultsHelperTests.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Http.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -24,10 +25,16 @@ public async Task WriteResultAsJsonAsync_Works_ForValueTypes(bool useJsonContext Name = "Write even more tests!", }; var responseBodyStream = new MemoryStream(); - var httpContext = CreateHttpContext(responseBodyStream, useJsonContext); + var httpContext = CreateHttpContext(responseBodyStream); + var serializerOptions = new JsonOptions().SerializerOptions; + + if (useJsonContext) + { + serializerOptions.AddContext(); + } // Act - await HttpResultsHelper.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value); + await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), new JsonSerializerOptions @@ -52,10 +59,16 @@ public async Task WriteResultAsJsonAsync_Works_ForReferenceTypes(bool useJsonCon Name = "Write even more tests!", }; var responseBodyStream = new MemoryStream(); - var httpContext = CreateHttpContext(responseBodyStream, useJsonContext); + var httpContext = CreateHttpContext(responseBodyStream); + var serializerOptions = new JsonOptions().SerializerOptions; + + if (useJsonContext) + { + serializerOptions.AddContext(); + } // Act - await HttpResultsHelper.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value); + await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), new JsonSerializerOptions @@ -82,10 +95,16 @@ public async Task WriteResultAsJsonAsync_Works_ForChildTypes(bool useJsonContext Child = "With type hierarchies!" }; var responseBodyStream = new MemoryStream(); - var httpContext = CreateHttpContext(responseBodyStream, useJsonContext); + var httpContext = CreateHttpContext(responseBodyStream); + var serializerOptions = new JsonOptions().SerializerOptions; + + if (useJsonContext) + { + serializerOptions.AddContext(); + } // Act - await HttpResultsHelper.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value); + await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), new JsonSerializerOptions @@ -99,26 +118,20 @@ public async Task WriteResultAsJsonAsync_Works_ForChildTypes(bool useJsonContext Assert.Equal("With type hierarchies!", body!.Child); } - private static DefaultHttpContext CreateHttpContext(Stream stream, bool useJsonContext = false) + private static DefaultHttpContext CreateHttpContext(Stream stream) => new() { - RequestServices = CreateServices(useJsonContext), + RequestServices = CreateServices(), Response = { Body = stream, }, }; - private static IServiceProvider CreateServices(bool useJsonContext = false) + private static IServiceProvider CreateServices() { var services = new ServiceCollection(); services.AddSingleton(); - - if (useJsonContext) - { - services.ConfigureHttpJsonOptions(o => o.SerializerOptions.AddContext()); - } - return services.BuildServiceProvider(); } diff --git a/src/Http/Http.Results/test/JsonResultTests.cs b/src/Http/Http.Results/test/JsonResultTests.cs index 9ee546119010..26f40f6f826f 100644 --- a/src/Http/Http.Results/test/JsonResultTests.cs +++ b/src/Http/Http.Results/test/JsonResultTests.cs @@ -3,6 +3,7 @@ using System.Text; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -77,7 +78,9 @@ public async Task JsonResult_ExecuteAsync_JsonSerializesBody_WithOptions() var jsonOptions = new JsonSerializerOptions() { WriteIndented = true, - DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + //TODO: Is that true???? + TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; var value = new Todo(10, "MyName") { Description = null }; var result = new JsonHttpResult(value, jsonSerializerOptions: jsonOptions); From 1e048d3aa2522f831789f662176b0b43448d035b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 10 Jan 2023 10:56:50 -0800 Subject: [PATCH 02/24] Renaming unittest --- .../Http.Results/src/HttpResultsWriter.cs | 25 ++++++------------- ...lperTests.cs => HttpResultsWriterTests.cs} | 17 +++---------- 2 files changed, 12 insertions(+), 30 deletions(-) rename src/Http/Http.Results/test/{HttpResultsHelperTests.cs => HttpResultsWriterTests.cs} (92%) diff --git a/src/Http/Http.Results/src/HttpResultsWriter.cs b/src/Http/Http.Results/src/HttpResultsWriter.cs index 8619c38f597d..2e63e5bc534b 100644 --- a/src/Http/Http.Results/src/HttpResultsWriter.cs +++ b/src/Http/Http.Results/src/HttpResultsWriter.cs @@ -18,8 +18,7 @@ internal static partial class HttpResultsWriter { internal const string DefaultContentType = "text/plain; charset=utf-8"; private static readonly Encoding DefaultEncoding = Encoding.UTF8; - private static JsonSerializerOptions? _defaultSerializerOptions; - private readonly static object _syncObj = new(); + private static JsonOptions? _defaultOptions; public static Task WriteResultAsJsonAsync( HttpContext httpContext, @@ -33,7 +32,7 @@ public static Task WriteResultAsJsonAsync( return Task.CompletedTask; } - jsonSerializerOptions ??= GetDefaultOptions(httpContext); + jsonSerializerOptions ??= ResolveJsonOptions(httpContext).SerializerOptions; var declaredTypeInfo = (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(T)); if (value is null || declaredTypeInfo.IsPolymorphicSafe()) @@ -58,20 +57,6 @@ public static Task WriteResultAsJsonAsync( value, jsonTypeInfo, contentType: contentType); - - static JsonSerializerOptions GetDefaultOptions(HttpContext httpContext) - { - if (_defaultSerializerOptions != null) - { - return _defaultSerializerOptions; - } - - // TODO: Should I lock it??? - lock (_syncObj) - { - return _defaultSerializerOptions = httpContext.RequestServices.GetService>()?.Value.SerializerOptions ?? new JsonOptions().SerializerOptions; - } - } } public static Task WriteResultAsContentAsync( @@ -161,6 +146,12 @@ public static void ApplyProblemDetailsDefaultsIfNeeded(object? value, int? statu } } + private static JsonOptions ResolveJsonOptions(HttpContext httpContext) + { + // Attempt to resolve options from DI then fallback to default options + return _defaultOptions ??= httpContext.RequestServices?.GetService>()?.Value ?? new JsonOptions(); + } + internal static partial class Log { [LoggerMessage(1, LogLevel.Information, diff --git a/src/Http/Http.Results/test/HttpResultsHelperTests.cs b/src/Http/Http.Results/test/HttpResultsWriterTests.cs similarity index 92% rename from src/Http/Http.Results/test/HttpResultsHelperTests.cs rename to src/Http/Http.Results/test/HttpResultsWriterTests.cs index fc97aefa8ef4..fb25ddd51314 100644 --- a/src/Http/Http.Results/test/HttpResultsHelperTests.cs +++ b/src/Http/Http.Results/test/HttpResultsWriterTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Http.HttpResults; -public partial class HttpResultsHelperTests +public partial class HttpResultsWriterTests { [Theory] [InlineData(true)] @@ -37,10 +37,7 @@ public async Task WriteResultAsJsonAsync_Works_ForValueTypes(bool useJsonContext await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert - var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); + var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), serializerOptions); Assert.Equal("Write even more tests!", body!.Name); Assert.True(body!.IsComplete); @@ -71,10 +68,7 @@ public async Task WriteResultAsJsonAsync_Works_ForReferenceTypes(bool useJsonCon await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert - var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); + var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), serializerOptions); Assert.NotNull(body); Assert.Equal("Write even more tests!", body!.Name); @@ -107,10 +101,7 @@ public async Task WriteResultAsJsonAsync_Works_ForChildTypes(bool useJsonContext await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert - var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); + var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), serializerOptions); Assert.NotNull(body); Assert.Equal("Write even more tests!", body!.Name); From 7485673be05a1bbd6bf7c218a9952060381c2ca6 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 10 Jan 2023 11:03:39 -0800 Subject: [PATCH 03/24] Adding unit tests --- .../test/HttpResultsWriterTests.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/Http/Http.Results/test/HttpResultsWriterTests.cs b/src/Http/Http.Results/test/HttpResultsWriterTests.cs index fb25ddd51314..7f5ed44305eb 100644 --- a/src/Http/Http.Results/test/HttpResultsWriterTests.cs +++ b/src/Http/Http.Results/test/HttpResultsWriterTests.cs @@ -109,6 +109,40 @@ public async Task WriteResultAsJsonAsync_Works_ForChildTypes(bool useJsonContext Assert.Equal("With type hierarchies!", body!.Child); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task WriteResultAsJsonAsync_Works_ForChildTypes_WithJsonPolymorphism(bool useJsonContext) + { + // Arrange + var value = new TodoJsonChild() + { + Id = 1, + IsComplete = true, + Name = "Write even more tests!", + Child = "With type hierarchies!" + }; + var responseBodyStream = new MemoryStream(); + var httpContext = CreateHttpContext(responseBodyStream); + var serializerOptions = new JsonOptions().SerializerOptions; + + if (useJsonContext) + { + serializerOptions.AddContext(); + } + + // Act + await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); + + // Assert + var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), serializerOptions); + + Assert.NotNull(body); + Assert.Equal("Write even more tests!", body!.Name); + Assert.True(body!.IsComplete); + Assert.Equal("With type hierarchies!", body!.Child); + } + private static DefaultHttpContext CreateHttpContext(Stream stream) => new() { @@ -128,6 +162,7 @@ private static IServiceProvider CreateServices() [JsonSerializable(typeof(Todo))] [JsonSerializable(typeof(TodoChild))] + [JsonSerializable(typeof(JsonTodo))] [JsonSerializable(typeof(TodoStruct))] private partial class TestJsonContext : JsonSerializerContext { } @@ -154,4 +189,14 @@ private class TodoChild : Todo { public string Child { get; set; } } + + [JsonDerivedType(typeof(TodoJsonChild))] + private class JsonTodo : Todo + { + } + + private class TodoJsonChild : JsonTodo + { + public string Child { get; set; } + } } From af1eba887fb4c83fb931f3eb2b19e69647947911 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 10 Jan 2023 11:09:04 -0800 Subject: [PATCH 04/24] Adding more unit tests --- .../test/HttpResultsWriterTests.cs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Http/Http.Results/test/HttpResultsWriterTests.cs b/src/Http/Http.Results/test/HttpResultsWriterTests.cs index 7f5ed44305eb..a5be203fed78 100644 --- a/src/Http/Http.Results/test/HttpResultsWriterTests.cs +++ b/src/Http/Http.Results/test/HttpResultsWriterTests.cs @@ -112,7 +112,41 @@ public async Task WriteResultAsJsonAsync_Works_ForChildTypes(bool useJsonContext [Theory] [InlineData(true)] [InlineData(false)] - public async Task WriteResultAsJsonAsync_Works_ForChildTypes_WithJsonPolymorphism(bool useJsonContext) + public async Task WriteResultAsJsonAsync_Works_UsingBaseType_ForChildTypes(bool useJsonContext) + { + // Arrange + var value = new TodoChild() + { + Id = 1, + IsComplete = true, + Name = "Write even more tests!", + Child = "With type hierarchies!" + }; + var responseBodyStream = new MemoryStream(); + var httpContext = CreateHttpContext(responseBodyStream); + var serializerOptions = new JsonOptions().SerializerOptions; + + if (useJsonContext) + { + serializerOptions.AddContext(); + } + + // Act + await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); + + // Assert + var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), serializerOptions); + + Assert.NotNull(body); + Assert.Equal("Write even more tests!", body!.Name); + Assert.True(body!.IsComplete); + Assert.Equal("With type hierarchies!", body!.Child); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task WriteResultAsJsonAsync_Works_UsingBaseType_ForChildTypes_WithJsonPolymorphism(bool useJsonContext) { // Arrange var value = new TodoJsonChild() From 4a63010d93ce6ce3edd03c0573a1965da5adb795 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 12 Jan 2023 14:10:12 -0800 Subject: [PATCH 05/24] Setting DefaultTypeInfoResolver --- .../Http.Results/src/HttpResultsWriter.cs | 37 +++++++++++-------- .../Http.Results/src/JsonHttpResultOfT.cs | 7 ++++ .../Microsoft.AspNetCore.Http.Results.csproj | 1 + src/Http/Http.Results/test/JsonResultTests.cs | 2 - 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/Http/Http.Results/src/HttpResultsWriter.cs b/src/Http/Http.Results/src/HttpResultsWriter.cs index 2e63e5bc534b..6249502f44f2 100644 --- a/src/Http/Http.Results/src/HttpResultsWriter.cs +++ b/src/Http/Http.Results/src/HttpResultsWriter.cs @@ -20,10 +20,10 @@ internal static partial class HttpResultsWriter private static readonly Encoding DefaultEncoding = Encoding.UTF8; private static JsonOptions? _defaultOptions; - public static Task WriteResultAsJsonAsync( + public static Task WriteResultAsJsonAsync( HttpContext httpContext, ILogger logger, - T? value, + TValue? value, string? contentType = null, JsonSerializerOptions? jsonSerializerOptions = null) { @@ -33,32 +33,39 @@ public static Task WriteResultAsJsonAsync( } jsonSerializerOptions ??= ResolveJsonOptions(httpContext).SerializerOptions; + var jsonTypeInfo = GetTypeInfo(value, jsonSerializerOptions); - var declaredTypeInfo = (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(T)); - if (value is null || declaredTypeInfo.IsPolymorphicSafe()) + Log.WritingResultAsJson(logger, jsonTypeInfo.Type.Name); + + if (jsonTypeInfo is JsonTypeInfo genericTypeInfo) { - // In this case the polymorphism is not - // relevant and we don't need to box. + // In this case we don't need to box. return httpContext.Response.WriteAsJsonAsync( value, - declaredTypeInfo, + genericTypeInfo, contentType: contentType); } - // Call WriteAsJsonAsync() with the runtime type to serialize the runtime type rather than the declared type - // and avoid source generators issues. - // https://github.com/dotnet/aspnetcore/issues/43894 - // https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-polymorphism - - var jsonTypeInfo = jsonSerializerOptions.GetTypeInfo(value.GetType()); - Log.WritingResultAsJson(logger, jsonTypeInfo.Type.Name); - return httpContext.Response.WriteAsJsonAsync( value, jsonTypeInfo, contentType: contentType); } + private static JsonTypeInfo GetTypeInfo(TValue? value, JsonSerializerOptions jsonSerializerOptions) + { + var declaredTypeInfo = (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(TValue)); + if (value is null || declaredTypeInfo.IsPolymorphicSafe()) + { + return declaredTypeInfo; + } + + // Since we don't know the type's polymorphic characteristics + // our best option is use the runtime type + return jsonSerializerOptions.GetTypeInfo(value.GetType()); + + } + public static Task WriteResultAsContentAsync( HttpContext httpContext, ILogger logger, diff --git a/src/Http/Http.Results/src/JsonHttpResultOfT.cs b/src/Http/Http.Results/src/JsonHttpResultOfT.cs index dce5a1950571..70bc1a07ba33 100644 --- a/src/Http/Http.Results/src/JsonHttpResultOfT.cs +++ b/src/Http/Http.Results/src/JsonHttpResultOfT.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -44,6 +45,12 @@ internal JsonHttpResult(TValue? value, int? statusCode, JsonSerializerOptions? j internal JsonHttpResult(TValue? value, int? statusCode, string? contentType, JsonSerializerOptions? jsonSerializerOptions) { Value = value; + + if (jsonSerializerOptions is not null && !jsonSerializerOptions.IsReadOnly) //&& !TrimmingAppContextSwitches.EnsureJsonTrimmability + { + jsonSerializerOptions.TypeInfoResolver ??= new DefaultJsonTypeInfoResolver(); + } + JsonSerializerOptions = jsonSerializerOptions; ContentType = contentType; diff --git a/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj index 3bc7cec04167..aac2e0cbf2db 100644 --- a/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj +++ b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj @@ -9,6 +9,7 @@ false enable Microsoft.AspNetCore.Http.Result + true diff --git a/src/Http/Http.Results/test/JsonResultTests.cs b/src/Http/Http.Results/test/JsonResultTests.cs index 26f40f6f826f..dfc0f179072e 100644 --- a/src/Http/Http.Results/test/JsonResultTests.cs +++ b/src/Http/Http.Results/test/JsonResultTests.cs @@ -79,8 +79,6 @@ public async Task JsonResult_ExecuteAsync_JsonSerializesBody_WithOptions() { WriteIndented = true, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, - //TODO: Is that true???? - TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; var value = new Todo(10, "MyName") { Description = null }; var result = new JsonHttpResult(value, jsonSerializerOptions: jsonOptions); From cfbe2fd59e3a86253e3260e9475d9bfca0252eff Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 12 Jan 2023 14:21:41 -0800 Subject: [PATCH 06/24] Removing ISTrimmable --- .../Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj index aac2e0cbf2db..3bc7cec04167 100644 --- a/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj +++ b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj @@ -9,7 +9,6 @@ false enable Microsoft.AspNetCore.Http.Result - true From 8c10b8c815eb9065a24b486f72facaf0de155b34 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 12 Jan 2023 15:24:56 -0800 Subject: [PATCH 07/24] Removing cache --- src/Http/Http.Results/src/HttpResultsWriter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Http/Http.Results/src/HttpResultsWriter.cs b/src/Http/Http.Results/src/HttpResultsWriter.cs index 6249502f44f2..b0a7c5ba0800 100644 --- a/src/Http/Http.Results/src/HttpResultsWriter.cs +++ b/src/Http/Http.Results/src/HttpResultsWriter.cs @@ -18,7 +18,6 @@ internal static partial class HttpResultsWriter { internal const string DefaultContentType = "text/plain; charset=utf-8"; private static readonly Encoding DefaultEncoding = Encoding.UTF8; - private static JsonOptions? _defaultOptions; public static Task WriteResultAsJsonAsync( HttpContext httpContext, @@ -156,7 +155,7 @@ public static void ApplyProblemDetailsDefaultsIfNeeded(object? value, int? statu private static JsonOptions ResolveJsonOptions(HttpContext httpContext) { // Attempt to resolve options from DI then fallback to default options - return _defaultOptions ??= httpContext.RequestServices?.GetService>()?.Value ?? new JsonOptions(); + return httpContext.RequestServices?.GetService>()?.Value ?? new JsonOptions(); } internal static partial class Log From a4229b3a9eaba2b38fafecee3cc5975e46712667 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 13 Jan 2023 15:10:30 -0800 Subject: [PATCH 08/24] Clean up --- src/Http/Http.Results/src/HttpResultsWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Http.Results/src/HttpResultsWriter.cs b/src/Http/Http.Results/src/HttpResultsWriter.cs index b0a7c5ba0800..3fe7211fdf6d 100644 --- a/src/Http/Http.Results/src/HttpResultsWriter.cs +++ b/src/Http/Http.Results/src/HttpResultsWriter.cs @@ -53,7 +53,7 @@ public static Task WriteResultAsJsonAsync( private static JsonTypeInfo GetTypeInfo(TValue? value, JsonSerializerOptions jsonSerializerOptions) { - var declaredTypeInfo = (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(TValue)); + var declaredTypeInfo = jsonSerializerOptions.GetTypeInfo(typeof(TValue)); if (value is null || declaredTypeInfo.IsPolymorphicSafe()) { return declaredTypeInfo; From 2be89fc1b272b37291d3c34dba2f4db522034be6 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 17 Jan 2023 11:01:37 -0800 Subject: [PATCH 09/24] Avoiding multiple GetTypeInfo calls --- src/Http/Http.Results/src/HttpResultsWriter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Http/Http.Results/src/HttpResultsWriter.cs b/src/Http/Http.Results/src/HttpResultsWriter.cs index 3fe7211fdf6d..48dee81927c1 100644 --- a/src/Http/Http.Results/src/HttpResultsWriter.cs +++ b/src/Http/Http.Results/src/HttpResultsWriter.cs @@ -59,6 +59,12 @@ private static JsonTypeInfo GetTypeInfo(TValue? value, JsonSerializerOpt return declaredTypeInfo; } + var runtimeType = value.GetType(); + if (declaredTypeInfo.Type == runtimeType) + { + return declaredTypeInfo; + } + // Since we don't know the type's polymorphic characteristics // our best option is use the runtime type return jsonSerializerOptions.GetTypeInfo(value.GetType()); From 8c09b97e00d98046e704a8bb40e0de50099619ae Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 18 Jan 2023 09:55:07 -0800 Subject: [PATCH 10/24] Fixing JsonResult --- .../Http.Results/src/HttpResultsWriter.cs | 49 ++++++------ .../Http.Results/src/JsonHttpResultOfT.cs | 79 ++++++++++--------- src/Http/Http.Results/src/Results.cs | 37 ++++++++- src/Http/Http.Results/src/TypedResults.cs | 23 +++++- src/Http/Http.Results/test/JsonResultTests.cs | 2 +- 5 files changed, 124 insertions(+), 66 deletions(-) diff --git a/src/Http/Http.Results/src/HttpResultsWriter.cs b/src/Http/Http.Results/src/HttpResultsWriter.cs index 48dee81927c1..e5b65f8e04be 100644 --- a/src/Http/Http.Results/src/HttpResultsWriter.cs +++ b/src/Http/Http.Results/src/HttpResultsWriter.cs @@ -23,52 +23,53 @@ public static Task WriteResultAsJsonAsync( HttpContext httpContext, ILogger logger, TValue? value, - string? contentType = null, - JsonSerializerOptions? jsonSerializerOptions = null) + JsonTypeInfo jsonTypeInfo, + string? contentType = null) { if (value is null) { return Task.CompletedTask; } - jsonSerializerOptions ??= ResolveJsonOptions(httpContext).SerializerOptions; - var jsonTypeInfo = GetTypeInfo(value, jsonSerializerOptions); - Log.WritingResultAsJson(logger, jsonTypeInfo.Type.Name); - - if (jsonTypeInfo is JsonTypeInfo genericTypeInfo) - { - // In this case we don't need to box. - return httpContext.Response.WriteAsJsonAsync( - value, - genericTypeInfo, - contentType: contentType); - } - return httpContext.Response.WriteAsJsonAsync( value, jsonTypeInfo, contentType: contentType); } - private static JsonTypeInfo GetTypeInfo(TValue? value, JsonSerializerOptions jsonSerializerOptions) + public static Task WriteResultAsJsonAsync( + HttpContext httpContext, + ILogger logger, + TValue? value, + string? contentType = null, + JsonSerializerOptions? jsonSerializerOptions = null) { - var declaredTypeInfo = jsonSerializerOptions.GetTypeInfo(typeof(TValue)); - if (value is null || declaredTypeInfo.IsPolymorphicSafe()) + if (value is null) { - return declaredTypeInfo; + return Task.CompletedTask; } - var runtimeType = value.GetType(); - if (declaredTypeInfo.Type == runtimeType) + jsonSerializerOptions ??= ResolveJsonOptions(httpContext).SerializerOptions; + var jsonTypeInfo = (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(TValue)); + + Type? runtimeType; + if (jsonTypeInfo.IsPolymorphicSafe() || jsonTypeInfo.Type == (runtimeType = value.GetType())) { - return declaredTypeInfo; + Log.WritingResultAsJson(logger, jsonTypeInfo.Type.Name); + return httpContext.Response.WriteAsJsonAsync( + value, + jsonTypeInfo, + contentType: contentType); } + Log.WritingResultAsJson(logger, runtimeType.Name); // Since we don't know the type's polymorphic characteristics // our best option is use the runtime type - return jsonSerializerOptions.GetTypeInfo(value.GetType()); - + return httpContext.Response.WriteAsJsonAsync( + value, + jsonSerializerOptions.GetTypeInfo(runtimeType), + contentType: contentType); } public static Task WriteResultAsContentAsync( diff --git a/src/Http/Http.Results/src/JsonHttpResultOfT.cs b/src/Http/Http.Results/src/JsonHttpResultOfT.cs index 70bc1a07ba33..83748cdfe70a 100644 --- a/src/Http/Http.Results/src/JsonHttpResultOfT.cs +++ b/src/Http/Http.Results/src/JsonHttpResultOfT.cs @@ -14,59 +14,42 @@ namespace Microsoft.AspNetCore.Http.HttpResults; /// public sealed partial class JsonHttpResult : IResult, IStatusCodeHttpResult, IValueHttpResult, IValueHttpResult, IContentTypeHttpResult { + private readonly JsonTypeInfo? _jsonTypeInfo; + private int _statusCode; + /// /// Initializes a new instance of the class with the values. /// /// The value to format in the entity body. /// The serializer settings. internal JsonHttpResult(TValue? value, JsonSerializerOptions? jsonSerializerOptions) - : this(value, statusCode: null, contentType: null, jsonSerializerOptions: jsonSerializerOptions) { - } + Value = value; - /// - /// Initializes a new instance of the class with the values. - /// - /// The value to format in the entity body. - /// The HTTP status code of the response. - /// The serializer settings. - internal JsonHttpResult(TValue? value, int? statusCode, JsonSerializerOptions? jsonSerializerOptions) - : this(value, statusCode: statusCode, contentType: null, jsonSerializerOptions: jsonSerializerOptions) - { + if (jsonSerializerOptions is not null && !jsonSerializerOptions.IsReadOnly) // Switch + { + jsonSerializerOptions.TypeInfoResolver ??= new DefaultJsonTypeInfoResolver(); + } + + JsonSerializerOptions = jsonSerializerOptions; } /// /// Initializes a new instance of the class with the values. /// /// The value to format in the entity body. - /// The HTTP status code of the response. - /// The serializer settings. - /// The value for the Content-Type header - internal JsonHttpResult(TValue? value, int? statusCode, string? contentType, JsonSerializerOptions? jsonSerializerOptions) + /// The serializer type info. + internal JsonHttpResult(TValue? value, JsonTypeInfo jsonTypeInfo) { Value = value; - - if (jsonSerializerOptions is not null && !jsonSerializerOptions.IsReadOnly) //&& !TrimmingAppContextSwitches.EnsureJsonTrimmability - { - jsonSerializerOptions.TypeInfoResolver ??= new DefaultJsonTypeInfoResolver(); - } - - JsonSerializerOptions = jsonSerializerOptions; - ContentType = contentType; - - if (value is ProblemDetails problemDetails) - { - ProblemDetailsDefaults.Apply(problemDetails, statusCode); - statusCode ??= problemDetails.Status; - } - - StatusCode = statusCode; + _jsonTypeInfo = jsonTypeInfo; + JsonSerializerOptions = jsonTypeInfo.Options; } /// /// Gets or sets the serializer settings. /// - public JsonSerializerOptions? JsonSerializerOptions { get; internal init; } + public JsonSerializerOptions? JsonSerializerOptions { get; } /// /// Gets the object result. @@ -78,12 +61,26 @@ internal JsonHttpResult(TValue? value, int? statusCode, string? contentType, Jso /// /// Gets the value for the Content-Type header. /// - public string? ContentType { get; internal set; } + public string? ContentType { get; internal init; } /// /// Gets the HTTP status code. /// - public int? StatusCode { get; } + public int? StatusCode + { + get => _statusCode; + internal init + { + var statusCode = value; + if (Value is ProblemDetails problemDetails) + { + ProblemDetailsDefaults.Apply(problemDetails, statusCode); + statusCode ??= problemDetails.Status; + } + + _statusCode = statusCode!.Value; + } + } /// public Task ExecuteAsync(HttpContext httpContext) @@ -100,11 +97,21 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.StatusCode = statusCode; } + if (_jsonTypeInfo is null) + { + return HttpResultsWriter.WriteResultAsJsonAsync( + httpContext, + logger, + Value, + ContentType, + JsonSerializerOptions); + } + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger, Value, - ContentType, - JsonSerializerOptions); + _jsonTypeInfo, + ContentType); } } diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 40c283d7ec1b..3577cf341c3b 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -6,6 +6,7 @@ using System.Security.Claims; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.HttpResults; @@ -180,8 +181,24 @@ public static IResult Content(string? content, MediaTypeHeaderValue contentType) /// as JSON format for the response. /// Callers should cache an instance of serializer settings to avoid /// recreating cached data with each call. + [RequiresUnreferencedCode("Warning")] + [RequiresDynamicCode("Warning")] public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) - => Json(data, options, contentType, statusCode); + => Json(data, options, contentType, statusCode); + + /// + /// Creates a that serializes the specified object to JSON. + /// + /// The object to write as JSON. + /// TODO. + /// The content-type to set on the response. + /// The status code to set on the response. + /// The created that serializes the specified + /// as JSON format for the response. + /// Callers should cache an instance of serializer settings to avoid + /// recreating cached data with each call. + public static IResult Json(object? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) + => Json(data, jsonTypeInfo, contentType, statusCode); /// /// Creates a that serializes the specified object to JSON. @@ -194,11 +211,29 @@ public static IResult Json(object? data, JsonSerializerOptions? options = null, /// as JSON format for the response. /// Callers should cache an instance of serializer settings to avoid /// recreating cached data with each call. + [RequiresUnreferencedCode("Warning")] + [RequiresDynamicCode("Warning")] #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static IResult Json(TValue? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => TypedResults.Json(data, options, contentType, statusCode); + /// + /// Creates a that serializes the specified object to JSON. + /// + /// The object to write as JSON. + /// TODO. + /// The content-type to set on the response. + /// The status code to set on the response. + /// The created that serializes the specified + /// as JSON format for the response. + /// Callers should cache an instance of serializer settings to avoid + /// recreating cached data with each call. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => TypedResults.Json(data, jsonTypeInfo, contentType, statusCode); + /// /// Writes the byte-array content to the response. /// diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index c716a69a6d66..e415a162da77 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -6,6 +6,7 @@ using System.Security.Claims; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.HttpResults; @@ -194,11 +195,25 @@ public static ContentHttpResult Content(string? content, MediaTypeHeaderValue co /// The status code to set on the response. /// The created that serializes the specified /// as JSON format for the response. + [RequiresUnreferencedCode("Warning")] + [RequiresDynamicCode("Warning")] public static JsonHttpResult Json(TValue? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) - => new(data, statusCode, options) - { - ContentType = contentType, - }; + => new(data, options) { ContentType = contentType, StatusCode = statusCode }; + + /// + /// Creates a that serializes the specified object to JSON. + /// + /// Callers should cache an instance of serializer settings to avoid + /// recreating cached data with each call. + /// The type of object that will be JSON serialized to the response body. + /// The object to write as JSON. + /// TODO. + /// The content-type to set on the response. + /// The status code to set on the response. + /// The created that serializes the specified + /// as JSON format for the response. + public static JsonHttpResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) + => new(data, jsonTypeInfo) { ContentType = contentType, StatusCode = statusCode }; /// /// Writes the byte-array content to the response. diff --git a/src/Http/Http.Results/test/JsonResultTests.cs b/src/Http/Http.Results/test/JsonResultTests.cs index dfc0f179072e..30cf8093790f 100644 --- a/src/Http/Http.Results/test/JsonResultTests.cs +++ b/src/Http/Http.Results/test/JsonResultTests.cs @@ -243,7 +243,7 @@ public async Task ExecuteAsync_GetsStatusCodeFromProblemDetails() public void ExecuteAsync_ThrowsArgumentNullException_WhenHttpContextIsNull() { // Arrange - var result = new JsonHttpResult(null, null); + var result = new JsonHttpResult(null, null, null, null); HttpContext httpContext = null; // Act & Assert From 52c3b9db824022091382b947f09c887c04a4f045 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 19 Jan 2023 14:41:41 -0800 Subject: [PATCH 11/24] Clean up --- .../Http.Results/src/HttpResultsWriter.cs | 19 ------ .../Http.Results/src/JsonHttpResultOfT.cs | 65 ++++++------------- src/Http/Http.Results/src/Results.cs | 31 --------- src/Http/Http.Results/src/TypedResults.cs | 18 +---- src/Http/Http.Results/test/JsonResultTests.cs | 12 ++-- 5 files changed, 27 insertions(+), 118 deletions(-) diff --git a/src/Http/Http.Results/src/HttpResultsWriter.cs b/src/Http/Http.Results/src/HttpResultsWriter.cs index e5b65f8e04be..12dac32173ea 100644 --- a/src/Http/Http.Results/src/HttpResultsWriter.cs +++ b/src/Http/Http.Results/src/HttpResultsWriter.cs @@ -19,25 +19,6 @@ internal static partial class HttpResultsWriter internal const string DefaultContentType = "text/plain; charset=utf-8"; private static readonly Encoding DefaultEncoding = Encoding.UTF8; - public static Task WriteResultAsJsonAsync( - HttpContext httpContext, - ILogger logger, - TValue? value, - JsonTypeInfo jsonTypeInfo, - string? contentType = null) - { - if (value is null) - { - return Task.CompletedTask; - } - - Log.WritingResultAsJson(logger, jsonTypeInfo.Type.Name); - return httpContext.Response.WriteAsJsonAsync( - value, - jsonTypeInfo, - contentType: contentType); - } - public static Task WriteResultAsJsonAsync( HttpContext httpContext, ILogger logger, diff --git a/src/Http/Http.Results/src/JsonHttpResultOfT.cs b/src/Http/Http.Results/src/JsonHttpResultOfT.cs index 83748cdfe70a..a490e1582f3f 100644 --- a/src/Http/Http.Results/src/JsonHttpResultOfT.cs +++ b/src/Http/Http.Results/src/JsonHttpResultOfT.cs @@ -14,38 +14,37 @@ namespace Microsoft.AspNetCore.Http.HttpResults; /// public sealed partial class JsonHttpResult : IResult, IStatusCodeHttpResult, IValueHttpResult, IValueHttpResult, IContentTypeHttpResult { - private readonly JsonTypeInfo? _jsonTypeInfo; - private int _statusCode; - /// /// Initializes a new instance of the class with the values. /// /// The value to format in the entity body. /// The serializer settings. - internal JsonHttpResult(TValue? value, JsonSerializerOptions? jsonSerializerOptions) + /// The HTTP status code of the response. + /// The value for the Content-Type header + internal JsonHttpResult(TValue? value, JsonSerializerOptions? jsonSerializerOptions, int? statusCode = null, string? contentType = null) { Value = value; + ContentType = contentType; - if (jsonSerializerOptions is not null && !jsonSerializerOptions.IsReadOnly) // Switch + if (value is ProblemDetails problemDetails) + { + ProblemDetailsDefaults.Apply(problemDetails, statusCode); + statusCode ??= problemDetails.Status; + } + StatusCode = statusCode; + + // TODO: Waiting for https://github.com/dotnet/aspnetcore/pull/45886 + //if (!TrimmingAppContextSwitches.EnsureJsonTrimmability) + //{ + if (jsonSerializerOptions is not null && !jsonSerializerOptions.IsReadOnly) { jsonSerializerOptions.TypeInfoResolver ??= new DefaultJsonTypeInfoResolver(); } + //} JsonSerializerOptions = jsonSerializerOptions; } - /// - /// Initializes a new instance of the class with the values. - /// - /// The value to format in the entity body. - /// The serializer type info. - internal JsonHttpResult(TValue? value, JsonTypeInfo jsonTypeInfo) - { - Value = value; - _jsonTypeInfo = jsonTypeInfo; - JsonSerializerOptions = jsonTypeInfo.Options; - } - /// /// Gets or sets the serializer settings. /// @@ -61,26 +60,12 @@ internal JsonHttpResult(TValue? value, JsonTypeInfo jsonTypeInfo) /// /// Gets the value for the Content-Type header. /// - public string? ContentType { get; internal init; } + public string? ContentType { get; } /// /// Gets the HTTP status code. /// - public int? StatusCode - { - get => _statusCode; - internal init - { - var statusCode = value; - if (Value is ProblemDetails problemDetails) - { - ProblemDetailsDefaults.Apply(problemDetails, statusCode); - statusCode ??= problemDetails.Status; - } - - _statusCode = statusCode!.Value; - } - } + public int? StatusCode { get; } /// public Task ExecuteAsync(HttpContext httpContext) @@ -97,21 +82,11 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.StatusCode = statusCode; } - if (_jsonTypeInfo is null) - { - return HttpResultsWriter.WriteResultAsJsonAsync( - httpContext, - logger, - Value, - ContentType, - JsonSerializerOptions); - } - return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger, Value, - _jsonTypeInfo, - ContentType); + ContentType, + JsonSerializerOptions); } } diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 3577cf341c3b..e60de348f78b 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -6,7 +6,6 @@ using System.Security.Claims; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.HttpResults; @@ -186,20 +185,6 @@ public static IResult Content(string? content, MediaTypeHeaderValue contentType) public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) => Json(data, options, contentType, statusCode); - /// - /// Creates a that serializes the specified object to JSON. - /// - /// The object to write as JSON. - /// TODO. - /// The content-type to set on the response. - /// The status code to set on the response. - /// The created that serializes the specified - /// as JSON format for the response. - /// Callers should cache an instance of serializer settings to avoid - /// recreating cached data with each call. - public static IResult Json(object? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) - => Json(data, jsonTypeInfo, contentType, statusCode); - /// /// Creates a that serializes the specified object to JSON. /// @@ -218,22 +203,6 @@ public static IResult Json(TValue? data, JsonSerializerOptions? options #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => TypedResults.Json(data, options, contentType, statusCode); - /// - /// Creates a that serializes the specified object to JSON. - /// - /// The object to write as JSON. - /// TODO. - /// The content-type to set on the response. - /// The status code to set on the response. - /// The created that serializes the specified - /// as JSON format for the response. - /// Callers should cache an instance of serializer settings to avoid - /// recreating cached data with each call. -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public static IResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => TypedResults.Json(data, jsonTypeInfo, contentType, statusCode); - /// /// Writes the byte-array content to the response. /// diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index e415a162da77..a5b1874e5240 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -6,7 +6,6 @@ using System.Security.Claims; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.HttpResults; @@ -198,22 +197,7 @@ public static ContentHttpResult Content(string? content, MediaTypeHeaderValue co [RequiresUnreferencedCode("Warning")] [RequiresDynamicCode("Warning")] public static JsonHttpResult Json(TValue? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) - => new(data, options) { ContentType = contentType, StatusCode = statusCode }; - - /// - /// Creates a that serializes the specified object to JSON. - /// - /// Callers should cache an instance of serializer settings to avoid - /// recreating cached data with each call. - /// The type of object that will be JSON serialized to the response body. - /// The object to write as JSON. - /// TODO. - /// The content-type to set on the response. - /// The status code to set on the response. - /// The created that serializes the specified - /// as JSON format for the response. - public static JsonHttpResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) - => new(data, jsonTypeInfo) { ContentType = contentType, StatusCode = statusCode }; + => new(data, options, statusCode, contentType); /// /// Writes the byte-array content to the response. diff --git a/src/Http/Http.Results/test/JsonResultTests.cs b/src/Http/Http.Results/test/JsonResultTests.cs index 30cf8093790f..c83fb9fb4941 100644 --- a/src/Http/Http.Results/test/JsonResultTests.cs +++ b/src/Http/Http.Results/test/JsonResultTests.cs @@ -204,7 +204,7 @@ public async Task ExecuteAsync_SetsProblemDetailsStatus_ForValidationProblemDeta // Arrange var details = new HttpValidationProblemDetails(); - var result = new JsonHttpResult(details, StatusCodes.Status422UnprocessableEntity, jsonSerializerOptions: null); + var result = new JsonHttpResult(details, jsonSerializerOptions: null, StatusCodes.Status422UnprocessableEntity); var httpContext = new DefaultHttpContext() { RequestServices = CreateServices(), @@ -257,7 +257,7 @@ public void JsonResult_Implements_IContentTypeHttpResult_Correctly() var contentType = "application/json+custom"; // Act & Assert - var result = Assert.IsAssignableFrom(new JsonHttpResult(null, StatusCodes.Status200OK, contentType, null)); + var result = Assert.IsAssignableFrom(new JsonHttpResult(null, jsonSerializerOptions: null, StatusCodes.Status200OK, contentType)); Assert.Equal(contentType, result.ContentType); } @@ -268,7 +268,7 @@ public void JsonResult_Implements_IStatusCodeHttpResult_Correctly() var contentType = "application/json+custom"; // Act & Assert - var result = Assert.IsAssignableFrom(new JsonHttpResult(null, StatusCodes.Status202Accepted, contentType, null)); + var result = Assert.IsAssignableFrom(new JsonHttpResult(null, jsonSerializerOptions: null, StatusCodes.Status202Accepted, contentType)); Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); } @@ -279,7 +279,7 @@ public void JsonResult_Implements_IStatusCodeHttpResult_Correctly_WithNullStatus var contentType = "application/json+custom"; // Act & Assert - var result = Assert.IsAssignableFrom(new JsonHttpResult(null, statusCode: null, contentType, null)); + var result = Assert.IsAssignableFrom(new JsonHttpResult(null, jsonSerializerOptions: null, statusCode: null, contentType)); Assert.Null(result.StatusCode); } @@ -291,7 +291,7 @@ public void JsonResult_Implements_IValueHttpResult_Correctly() var contentType = "application/json+custom"; // Act & Assert - var result = Assert.IsAssignableFrom(new JsonHttpResult(value, statusCode: null, contentType, null)); + var result = Assert.IsAssignableFrom(new JsonHttpResult(value, jsonSerializerOptions: null, statusCode: null, contentType)); Assert.IsType(result.Value); Assert.Equal(value, result.Value); } @@ -304,7 +304,7 @@ public void JsonResult_Implements_IValueHttpResultOfT_Correctly() var contentType = "application/json+custom"; // Act & Assert - var result = Assert.IsAssignableFrom>(new JsonHttpResult(value, statusCode: null, contentType, null)); + var result = Assert.IsAssignableFrom>(new JsonHttpResult(value, jsonSerializerOptions: null, statusCode: null, contentType)); Assert.IsType(result.Value); Assert.Equal(value, result.Value); } From 3d7a9f9092ac626206e754d2c8f31212fc580c43 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 20 Jan 2023 10:40:07 -0800 Subject: [PATCH 12/24] clean up --- src/Http/Http.Results/test/JsonResultTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Http/Http.Results/test/JsonResultTests.cs b/src/Http/Http.Results/test/JsonResultTests.cs index c83fb9fb4941..e4d8f6f2b8e6 100644 --- a/src/Http/Http.Results/test/JsonResultTests.cs +++ b/src/Http/Http.Results/test/JsonResultTests.cs @@ -3,7 +3,6 @@ using System.Text; using System.Text.Json; -using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; From 891d9097155ebae4a68a6dc94cc82a6a016f0f5a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 20 Jan 2023 14:32:55 -0800 Subject: [PATCH 13/24] Adding Json apis proposal --- .../Http.Results/src/HttpResultsWriter.cs | 28 +++++++ .../Http.Results/src/JsonHttpResultOfT.cs | 50 ++++++++++++- .../src/JsonHttpResultTrimmerWarning.cs | 11 +++ .../Http.Results/src/PublicAPI.Unshipped.txt | 8 +- src/Http/Http.Results/src/Results.cs | 74 ++++++++++++++++++- src/Http/Http.Results/src/TypedResults.cs | 36 ++++++++- src/Http/Http.Results/test/JsonResultTests.cs | 2 +- src/Http/Http.Results/test/ResultsTests.cs | 2 +- 8 files changed, 198 insertions(+), 13 deletions(-) create mode 100644 src/Http/Http.Results/src/JsonHttpResultTrimmerWarning.cs diff --git a/src/Http/Http.Results/src/HttpResultsWriter.cs b/src/Http/Http.Results/src/HttpResultsWriter.cs index 12dac32173ea..16a584129d89 100644 --- a/src/Http/Http.Results/src/HttpResultsWriter.cs +++ b/src/Http/Http.Results/src/HttpResultsWriter.cs @@ -19,6 +19,34 @@ internal static partial class HttpResultsWriter internal const string DefaultContentType = "text/plain; charset=utf-8"; private static readonly Encoding DefaultEncoding = Encoding.UTF8; + public static Task WriteResultAsJsonAsync( + HttpContext httpContext, + ILogger logger, + TValue? value, + JsonTypeInfo jsonTypeInfo, + string? contentType = null) + { + if (value is null) + { + return Task.CompletedTask; + } + + Log.WritingResultAsJson(logger, jsonTypeInfo.Type.Name); + if (jsonTypeInfo is JsonTypeInfo genericTypeInfo) + { + // We don't need to box in this case + return httpContext.Response.WriteAsJsonAsync( + value, + genericTypeInfo, + contentType: contentType); + } + + return httpContext.Response.WriteAsJsonAsync( + value, + jsonTypeInfo, + contentType: contentType); + } + public static Task WriteResultAsJsonAsync( HttpContext httpContext, ILogger logger, diff --git a/src/Http/Http.Results/src/JsonHttpResultOfT.cs b/src/Http/Http.Results/src/JsonHttpResultOfT.cs index a490e1582f3f..829078ed0af8 100644 --- a/src/Http/Http.Results/src/JsonHttpResultOfT.cs +++ b/src/Http/Http.Results/src/JsonHttpResultOfT.cs @@ -1,7 +1,9 @@ // 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.CodeAnalysis; using System.Text.Json; +using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -14,6 +16,8 @@ namespace Microsoft.AspNetCore.Http.HttpResults; /// public sealed partial class JsonHttpResult : IResult, IStatusCodeHttpResult, IValueHttpResult, IValueHttpResult, IContentTypeHttpResult { + private readonly JsonTypeInfo? _jsonTypeInfo; + /// /// Initializes a new instance of the class with the values. /// @@ -21,6 +25,8 @@ public sealed partial class JsonHttpResult : IResult, IStatusCodeHttpRes /// The serializer settings. /// The HTTP status code of the response. /// The value for the Content-Type header + [RequiresDynamicCode(JsonHttpResultTrimmerWarning.SerializationRequiresDynamicCodeMessage)] + [RequiresUnreferencedCode(JsonHttpResultTrimmerWarning.SerializationUnreferencedCodeMessage)] internal JsonHttpResult(TValue? value, JsonSerializerOptions? jsonSerializerOptions, int? statusCode = null, string? contentType = null) { Value = value; @@ -33,18 +39,44 @@ internal JsonHttpResult(TValue? value, JsonSerializerOptions? jsonSerializerOpti } StatusCode = statusCode; - // TODO: Waiting for https://github.com/dotnet/aspnetcore/pull/45886 - //if (!TrimmingAppContextSwitches.EnsureJsonTrimmability) - //{ if (jsonSerializerOptions is not null && !jsonSerializerOptions.IsReadOnly) { jsonSerializerOptions.TypeInfoResolver ??= new DefaultJsonTypeInfoResolver(); } - //} JsonSerializerOptions = jsonSerializerOptions; } + internal JsonHttpResult(TValue? value, JsonSerializerContext jsonSerializerContext, int? statusCode = null, string? contentType = null) + : this(value, jsonSerializerContext.GetTypeInfo(typeof(TValue))!, statusCode, contentType) + { } + + /// + /// Initializes a new instance of the class with the values. + /// + /// The value to format in the entity body. + /// Metadata about the type to convert. + /// The HTTP status code of the response. + /// The value for the Content-Type header + internal JsonHttpResult(TValue? value, JsonTypeInfo jsonTypeInfo, int? statusCode = null, string? contentType = null) + { + ArgumentNullException.ThrowIfNull(jsonTypeInfo); + + Value = value; + ContentType = contentType; + + if (value is ProblemDetails problemDetails) + { + ProblemDetailsDefaults.Apply(problemDetails, statusCode); + statusCode ??= problemDetails.Status; + } + + StatusCode = statusCode; + + _jsonTypeInfo = jsonTypeInfo; + JsonSerializerOptions = jsonTypeInfo.Options; + } + /// /// Gets or sets the serializer settings. /// @@ -82,6 +114,16 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.StatusCode = statusCode; } + if (_jsonTypeInfo is not null) + { + return HttpResultsWriter.WriteResultAsJsonAsync( + httpContext, + logger, + Value, + _jsonTypeInfo, + ContentType); + } + return HttpResultsWriter.WriteResultAsJsonAsync( httpContext, logger, diff --git a/src/Http/Http.Results/src/JsonHttpResultTrimmerWarning.cs b/src/Http/Http.Results/src/JsonHttpResultTrimmerWarning.cs new file mode 100644 index 000000000000..e2120087ff24 --- /dev/null +++ b/src/Http/Http.Results/src/JsonHttpResultTrimmerWarning.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Http; + +internal class JsonHttpResultTrimmerWarning +{ + public const string SerializationUnreferencedCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext."; + public const string SerializationRequiresDynamicCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use the overload that takes a JsonTypeInfo or JsonSerializerContext."; + +} diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt index 157ef8226525..cfe30baced91 100644 --- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt @@ -12,6 +12,10 @@ static Microsoft.AspNetCore.Http.Results.Created(string? uri, object? value) -> static Microsoft.AspNetCore.Http.Results.Created(System.Uri? uri, object? value) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Created(string? uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Created(System.Uri? uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Json(object? data, System.Text.Json.Serialization.JsonSerializerContext! jsonSerializerContext, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Json(object? data, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Json(TValue? data, System.Text.Json.Serialization.JsonSerializerContext! jsonSerializerContext, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Json(TValue? data, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.TypedResults.Created() -> Microsoft.AspNetCore.Http.HttpResults.Created! static Microsoft.AspNetCore.Http.TypedResults.Created(string? uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri? uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! @@ -22,4 +26,6 @@ static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri? uri, T *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! -*REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! \ No newline at end of file +*REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! +static Microsoft.AspNetCore.Http.TypedResults.Json(TValue? data, System.Text.Json.Serialization.JsonSerializerContext! jsonContext, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.JsonHttpResult! +static Microsoft.AspNetCore.Http.TypedResults.Json(TValue? data, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.JsonHttpResult! \ No newline at end of file diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index e60de348f78b..9a38afbb9c92 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -6,6 +6,8 @@ using System.Security.Claims; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.HttpResults; @@ -180,11 +182,43 @@ public static IResult Content(string? content, MediaTypeHeaderValue contentType) /// as JSON format for the response. /// Callers should cache an instance of serializer settings to avoid /// recreating cached data with each call. - [RequiresUnreferencedCode("Warning")] - [RequiresDynamicCode("Warning")] + [RequiresUnreferencedCode(JsonHttpResultTrimmerWarning.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(JsonHttpResultTrimmerWarning.SerializationRequiresDynamicCodeMessage)] public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) => Json(data, options, contentType, statusCode); + /// + /// Creates a that serializes the specified object to JSON. + /// + /// The object to write as JSON. + /// Metadata about the type to convert. + /// The content-type to set on the response. + /// The status code to set on the response. + /// The created that serializes the specified + /// as JSON format for the response. + /// Callers should cache an instance of serializer settings to avoid + /// recreating cached data with each call. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult Json(object? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => Json(data, jsonTypeInfo, contentType, statusCode); + + /// + /// Creates a that serializes the specified object to JSON. + /// + /// The object to write as JSON. + /// Metadata about the type to convert. + /// The content-type to set on the response. + /// The status code to set on the response. + /// The created that serializes the specified + /// as JSON format for the response. + /// Callers should cache an instance of serializer settings to avoid + /// recreating cached data with each call. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult Json(object? data, JsonSerializerContext jsonSerializerContext, string? contentType = null, int? statusCode = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => Json(data, jsonSerializerContext, contentType, statusCode); + /// /// Creates a that serializes the specified object to JSON. /// @@ -196,13 +230,45 @@ public static IResult Json(object? data, JsonSerializerOptions? options = null, /// as JSON format for the response. /// Callers should cache an instance of serializer settings to avoid /// recreating cached data with each call. - [RequiresUnreferencedCode("Warning")] - [RequiresDynamicCode("Warning")] + [RequiresUnreferencedCode(JsonHttpResultTrimmerWarning.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(JsonHttpResultTrimmerWarning.SerializationRequiresDynamicCodeMessage)] #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static IResult Json(TValue? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => TypedResults.Json(data, options, contentType, statusCode); + /// + /// Creates a that serializes the specified object to JSON. + /// + /// The object to write as JSON. + /// Metadata about the type to convert. + /// The content-type to set on the response. + /// The status code to set on the response. + /// The created that serializes the specified + /// as JSON format for the response. + /// Callers should cache an instance of serializer settings to avoid + /// recreating cached data with each call. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => TypedResults.Json(data, jsonTypeInfo, contentType, statusCode); + + /// + /// Creates a that serializes the specified object to JSON. + /// + /// The object to write as JSON. + /// Metadata about the type to convert. + /// The content-type to set on the response. + /// The status code to set on the response. + /// The created that serializes the specified + /// as JSON format for the response. + /// Callers should cache an instance of serializer settings to avoid + /// recreating cached data with each call. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult Json(TValue? data, JsonSerializerContext jsonSerializerContext, string? contentType = null, int? statusCode = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => TypedResults.Json(data, jsonSerializerContext, contentType, statusCode); + /// /// Writes the byte-array content to the response. /// diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index a5b1874e5240..8e5647c8c670 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -6,6 +6,8 @@ using System.Security.Claims; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.HttpResults; @@ -194,11 +196,41 @@ public static ContentHttpResult Content(string? content, MediaTypeHeaderValue co /// The status code to set on the response. /// The created that serializes the specified /// as JSON format for the response. - [RequiresUnreferencedCode("Warning")] - [RequiresDynamicCode("Warning")] + [RequiresUnreferencedCode(JsonHttpResultTrimmerWarning.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(JsonHttpResultTrimmerWarning.SerializationRequiresDynamicCodeMessage)] public static JsonHttpResult Json(TValue? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) => new(data, options, statusCode, contentType); + /// + /// Creates a that serializes the specified object to JSON. + /// + /// The type of object that will be JSON serialized to the response body. + /// The object to write as JSON. + /// Metadata about the type to convert. + /// The content-type to set on the response. + /// The status code to set on the response. + /// The created that serializes the specified + /// as JSON format for the response. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static JsonHttpResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => new(data, jsonTypeInfo, statusCode, contentType); + + /// + /// Creates a that serializes the specified object to JSON. + /// + /// The type of object that will be JSON serialized to the response body. + /// The object to write as JSON. + /// Metadata about the type to convert. + /// The content-type to set on the response. + /// The status code to set on the response. + /// The created that serializes the specified + /// as JSON format for the response. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static JsonHttpResult Json(TValue? data, JsonSerializerContext jsonContext, string? contentType = null, int? statusCode = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + => new(data, jsonContext, statusCode, contentType); + /// /// Writes the byte-array content to the response. /// diff --git a/src/Http/Http.Results/test/JsonResultTests.cs b/src/Http/Http.Results/test/JsonResultTests.cs index e4d8f6f2b8e6..963e76c1ebd4 100644 --- a/src/Http/Http.Results/test/JsonResultTests.cs +++ b/src/Http/Http.Results/test/JsonResultTests.cs @@ -242,7 +242,7 @@ public async Task ExecuteAsync_GetsStatusCodeFromProblemDetails() public void ExecuteAsync_ThrowsArgumentNullException_WhenHttpContextIsNull() { // Arrange - var result = new JsonHttpResult(null, null, null, null); + var result = new JsonHttpResult(null, jsonSerializerOptions: null, null, null); HttpContext httpContext = null; // Act & Assert diff --git a/src/Http/Http.Results/test/ResultsTests.cs b/src/Http/Http.Results/test/ResultsTests.cs index 2220232e5739..61e01c493a7d 100644 --- a/src/Http/Http.Results/test/ResultsTests.cs +++ b/src/Http/Http.Results/test/ResultsTests.cs @@ -1351,7 +1351,7 @@ private static string GetMemberName(Expression expression) (() => Results.File(Path.Join(Path.DirectorySeparatorChar.ToString(), "rooted", "path"), null, null, null, null, false), typeof(PhysicalFileHttpResult)), (() => Results.File("path", null, null, null, null, false), typeof(VirtualFileHttpResult)), (() => Results.Forbid(null, null), typeof(ForbidHttpResult)), - (() => Results.Json(new(), null, null, null), typeof(JsonHttpResult)), + (() => Results.Json(new(), (JsonSerializerOptions)null, null, null), typeof(JsonHttpResult)), (() => Results.NoContent(), typeof(NoContent)), (() => Results.NotFound(null), typeof(NotFound)), (() => Results.NotFound(new()), typeof(NotFound)), From e8546678fdb912828148d117f6267475a639fc70 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 25 Jan 2023 10:51:30 -0800 Subject: [PATCH 14/24] Removing name change Removing the change from HttpResultsHelper to HttpResultsWriter to avoid polluting the git with not related changes and I will do it later. --- src/Http/Http.Results/src/Accepted.cs | 2 +- src/Http/Http.Results/src/AcceptedAtRoute.cs | 2 +- src/Http/Http.Results/src/AcceptedAtRouteOfT.cs | 6 +++--- src/Http/Http.Results/src/AcceptedOfT.cs | 8 ++++---- src/Http/Http.Results/src/BadRequest.cs | 2 +- src/Http/Http.Results/src/BadRequestOfT.cs | 6 +++--- src/Http/Http.Results/src/Conflict.cs | 2 +- src/Http/Http.Results/src/ConflictOfT.cs | 6 +++--- src/Http/Http.Results/src/ContentHttpResult.cs | 4 ++-- src/Http/Http.Results/src/Created.cs | 2 +- src/Http/Http.Results/src/CreatedAtRoute.cs | 2 +- src/Http/Http.Results/src/CreatedAtRouteOfT.cs | 6 +++--- src/Http/Http.Results/src/CreatedOfT.cs | 8 ++++---- src/Http/Http.Results/src/FileContentHttpResult.cs | 2 +- src/Http/Http.Results/src/FileStreamHttpResult.cs | 2 +- .../{HttpResultsWriter.cs => HttpResultsHelper.cs} | 2 +- src/Http/Http.Results/src/JsonHttpResultOfT.cs | 6 +++--- src/Http/Http.Results/src/NoContent.cs | 2 +- src/Http/Http.Results/src/NotFound.cs | 2 +- src/Http/Http.Results/src/NotFoundOfT.cs | 6 +++--- src/Http/Http.Results/src/Ok.cs | 2 +- src/Http/Http.Results/src/OkOfT.cs | 6 +++--- src/Http/Http.Results/src/PhysicalFileHttpResult.cs | 2 +- src/Http/Http.Results/src/ProblemHttpResult.cs | 4 ++-- src/Http/Http.Results/src/PushStreamHttpResult.cs | 2 +- src/Http/Http.Results/src/StatusCodeHttpResult.cs | 2 +- src/Http/Http.Results/src/UnauthorizedHttpResult.cs | 2 +- src/Http/Http.Results/src/UnprocessableEntity.cs | 2 +- src/Http/Http.Results/src/UnprocessableEntityOfT.cs | 6 +++--- src/Http/Http.Results/src/Utf8ContentHttpResult.cs | 4 ++-- src/Http/Http.Results/src/ValidationProblem.cs | 4 ++-- src/Http/Http.Results/src/VirtualFileHttpResult.cs | 2 +- ...sultsWriterTests.cs => HttpResultsHelperTests.cs} | 12 ++++++------ 33 files changed, 64 insertions(+), 64 deletions(-) rename src/Http/Http.Results/src/{HttpResultsWriter.cs => HttpResultsHelper.cs} (99%) rename src/Http/Http.Results/test/{HttpResultsWriterTests.cs => HttpResultsHelperTests.cs} (95%) diff --git a/src/Http/Http.Results/src/Accepted.cs b/src/Http/Http.Results/src/Accepted.cs index 50898a01a785..0a23b209c2b6 100644 --- a/src/Http/Http.Results/src/Accepted.cs +++ b/src/Http/Http.Results/src/Accepted.cs @@ -69,7 +69,7 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = Location; } - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/AcceptedAtRoute.cs b/src/Http/Http.Results/src/AcceptedAtRoute.cs index 05917ad4265a..55489db2d29c 100644 --- a/src/Http/Http.Results/src/AcceptedAtRoute.cs +++ b/src/Http/Http.Results/src/AcceptedAtRoute.cs @@ -81,7 +81,7 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = url; - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs index 52044bcde7ef..c270926e5223 100644 --- a/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/AcceptedAtRouteOfT.cs @@ -44,7 +44,7 @@ internal AcceptedAtRoute( Value = value; RouteName = routeName; RouteValues = new RouteValueDictionary(routeValues); - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -94,10 +94,10 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = url; - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsWriter.WriteResultAsJsonAsync(httpContext, logger, Value); + return HttpResultsHelper.WriteResultAsJsonAsync(httpContext, logger, Value); } /// diff --git a/src/Http/Http.Results/src/AcceptedOfT.cs b/src/Http/Http.Results/src/AcceptedOfT.cs index 0a31a421019c..3fabf1b9b34c 100644 --- a/src/Http/Http.Results/src/AcceptedOfT.cs +++ b/src/Http/Http.Results/src/AcceptedOfT.cs @@ -26,7 +26,7 @@ internal Accepted(string? location, TValue? value) { Value = value; Location = location; - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -38,7 +38,7 @@ internal Accepted(string? location, TValue? value) internal Accepted(Uri locationUri, TValue? value) { Value = value; - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); ArgumentNullException.ThrowIfNull(locationUri); @@ -85,10 +85,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.AcceptedResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger, Value); diff --git a/src/Http/Http.Results/src/BadRequest.cs b/src/Http/Http.Results/src/BadRequest.cs index 990db867f44a..e27c56889ae5 100644 --- a/src/Http/Http.Results/src/BadRequest.cs +++ b/src/Http/Http.Results/src/BadRequest.cs @@ -39,7 +39,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.BadRequestObjectResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/BadRequestOfT.cs b/src/Http/Http.Results/src/BadRequestOfT.cs index 2bf6db3e1cd0..c9d48ed8be4f 100644 --- a/src/Http/Http.Results/src/BadRequestOfT.cs +++ b/src/Http/Http.Results/src/BadRequestOfT.cs @@ -24,7 +24,7 @@ public sealed class BadRequest : IResult, IEndpointMetadataProvider, ISt internal BadRequest(TValue? error) { Value = error; - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -50,10 +50,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.BadRequestObjectResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger: logger, Value); diff --git a/src/Http/Http.Results/src/Conflict.cs b/src/Http/Http.Results/src/Conflict.cs index 409401cff64c..5504c2cab069 100644 --- a/src/Http/Http.Results/src/Conflict.cs +++ b/src/Http/Http.Results/src/Conflict.cs @@ -39,7 +39,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.ConflictObjectResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/ConflictOfT.cs b/src/Http/Http.Results/src/ConflictOfT.cs index 16d76134d379..f78bf8b8b76c 100644 --- a/src/Http/Http.Results/src/ConflictOfT.cs +++ b/src/Http/Http.Results/src/ConflictOfT.cs @@ -24,7 +24,7 @@ public sealed class Conflict : IResult, IEndpointMetadataProvider, IStat internal Conflict(TValue? error) { Value = error; - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -50,10 +50,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.ConflictObjectResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger: logger, Value); diff --git a/src/Http/Http.Results/src/ContentHttpResult.cs b/src/Http/Http.Results/src/ContentHttpResult.cs index b2ea33212321..7d154b0ae853 100644 --- a/src/Http/Http.Results/src/ContentHttpResult.cs +++ b/src/Http/Http.Results/src/ContentHttpResult.cs @@ -65,11 +65,11 @@ public Task ExecuteAsync(HttpContext httpContext) if (StatusCode is { } statusCode) { - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, statusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, statusCode); httpContext.Response.StatusCode = statusCode; } - return HttpResultsWriter.WriteResultAsContentAsync( + return HttpResultsHelper.WriteResultAsContentAsync( httpContext, logger, ResponseContent, diff --git a/src/Http/Http.Results/src/Created.cs b/src/Http/Http.Results/src/Created.cs index 9108c7a877ab..2908e07471b5 100644 --- a/src/Http/Http.Results/src/Created.cs +++ b/src/Http/Http.Results/src/Created.cs @@ -69,7 +69,7 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = Location; } - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/CreatedAtRoute.cs b/src/Http/Http.Results/src/CreatedAtRoute.cs index c8e41924a9f8..213576e24e52 100644 --- a/src/Http/Http.Results/src/CreatedAtRoute.cs +++ b/src/Http/Http.Results/src/CreatedAtRoute.cs @@ -81,7 +81,7 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = url; - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs index 29cd2bf927f5..6d543d9b98f5 100644 --- a/src/Http/Http.Results/src/CreatedAtRouteOfT.cs +++ b/src/Http/Http.Results/src/CreatedAtRouteOfT.cs @@ -44,7 +44,7 @@ internal CreatedAtRoute( Value = value; RouteName = routeName; RouteValues = new RouteValueDictionary(routeValues); - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -94,10 +94,10 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.Headers.Location = url; - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger, Value); diff --git a/src/Http/Http.Results/src/CreatedOfT.cs b/src/Http/Http.Results/src/CreatedOfT.cs index f3a56d8845a2..89e02738458f 100644 --- a/src/Http/Http.Results/src/CreatedOfT.cs +++ b/src/Http/Http.Results/src/CreatedOfT.cs @@ -26,7 +26,7 @@ internal Created(string? location, TValue? value) { Value = value; Location = location; - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -38,7 +38,7 @@ internal Created(string? location, TValue? value) internal Created(Uri? locationUri, TValue? value) { Value = value; - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); if (locationUri != null) { @@ -84,10 +84,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.CreatedResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger, Value); diff --git a/src/Http/Http.Results/src/FileContentHttpResult.cs b/src/Http/Http.Results/src/FileContentHttpResult.cs index 390a359bc007..b8496be0752d 100644 --- a/src/Http/Http.Results/src/FileContentHttpResult.cs +++ b/src/Http/Http.Results/src/FileContentHttpResult.cs @@ -112,7 +112,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.FileContentResult"); - var (range, rangeLength, completed) = HttpResultsWriter.WriteResultAsFileCore( + var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore( httpContext, logger, FileDownloadName, diff --git a/src/Http/Http.Results/src/FileStreamHttpResult.cs b/src/Http/Http.Results/src/FileStreamHttpResult.cs index 477fd1764c0c..12266995a453 100644 --- a/src/Http/Http.Results/src/FileStreamHttpResult.cs +++ b/src/Http/Http.Results/src/FileStreamHttpResult.cs @@ -120,7 +120,7 @@ public async Task ExecuteAsync(HttpContext httpContext) await using (FileStream) { - var (range, rangeLength, completed) = HttpResultsWriter.WriteResultAsFileCore( + var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore( httpContext, logger, FileDownloadName, diff --git a/src/Http/Http.Results/src/HttpResultsWriter.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs similarity index 99% rename from src/Http/Http.Results/src/HttpResultsWriter.cs rename to src/Http/Http.Results/src/HttpResultsHelper.cs index 16a584129d89..009f94afcd82 100644 --- a/src/Http/Http.Results/src/HttpResultsWriter.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Http; -internal static partial class HttpResultsWriter +internal static partial class HttpResultsHelper { internal const string DefaultContentType = "text/plain; charset=utf-8"; private static readonly Encoding DefaultEncoding = Encoding.UTF8; diff --git a/src/Http/Http.Results/src/JsonHttpResultOfT.cs b/src/Http/Http.Results/src/JsonHttpResultOfT.cs index 829078ed0af8..f6972f319e10 100644 --- a/src/Http/Http.Results/src/JsonHttpResultOfT.cs +++ b/src/Http/Http.Results/src/JsonHttpResultOfT.cs @@ -110,13 +110,13 @@ public Task ExecuteAsync(HttpContext httpContext) if (StatusCode is { } statusCode) { - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, statusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, statusCode); httpContext.Response.StatusCode = statusCode; } if (_jsonTypeInfo is not null) { - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger, Value, @@ -124,7 +124,7 @@ public Task ExecuteAsync(HttpContext httpContext) ContentType); } - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger, Value, diff --git a/src/Http/Http.Results/src/NoContent.cs b/src/Http/Http.Results/src/NoContent.cs index 0b9727579bb0..75c8cfa2c1c7 100644 --- a/src/Http/Http.Results/src/NoContent.cs +++ b/src/Http/Http.Results/src/NoContent.cs @@ -38,7 +38,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.NoContentResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; diff --git a/src/Http/Http.Results/src/NotFound.cs b/src/Http/Http.Results/src/NotFound.cs index a840fba4b4a3..67df2f05af2c 100644 --- a/src/Http/Http.Results/src/NotFound.cs +++ b/src/Http/Http.Results/src/NotFound.cs @@ -38,7 +38,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.NotFoundObjectResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/NotFoundOfT.cs b/src/Http/Http.Results/src/NotFoundOfT.cs index 3d05b5c49217..f358691e1ecd 100644 --- a/src/Http/Http.Results/src/NotFoundOfT.cs +++ b/src/Http/Http.Results/src/NotFoundOfT.cs @@ -23,7 +23,7 @@ public sealed class NotFound : IResult, IEndpointMetadataProvider, IStat internal NotFound(TValue? value) { Value = value; - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -49,10 +49,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.NotFoundObjectResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger: logger, Value); diff --git a/src/Http/Http.Results/src/Ok.cs b/src/Http/Http.Results/src/Ok.cs index 52c8e125d1bf..110058a2591c 100644 --- a/src/Http/Http.Results/src/Ok.cs +++ b/src/Http/Http.Results/src/Ok.cs @@ -38,7 +38,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.OkObjectResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/OkOfT.cs b/src/Http/Http.Results/src/OkOfT.cs index fecc748f028d..d5a9646f70f4 100644 --- a/src/Http/Http.Results/src/OkOfT.cs +++ b/src/Http/Http.Results/src/OkOfT.cs @@ -23,7 +23,7 @@ public sealed class Ok : IResult, IEndpointMetadataProvider, IStatusCode internal Ok(TValue? value) { Value = value; - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -49,10 +49,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.OkObjectResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger: logger, Value); diff --git a/src/Http/Http.Results/src/PhysicalFileHttpResult.cs b/src/Http/Http.Results/src/PhysicalFileHttpResult.cs index b4cfb1a0d66a..7b44bd6c1bc3 100644 --- a/src/Http/Http.Results/src/PhysicalFileHttpResult.cs +++ b/src/Http/Http.Results/src/PhysicalFileHttpResult.cs @@ -122,7 +122,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.PhysicalFileResult"); - var (range, rangeLength, completed) = HttpResultsWriter.WriteResultAsFileCore( + var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore( httpContext, logger, FileDownloadName, diff --git a/src/Http/Http.Results/src/ProblemHttpResult.cs b/src/Http/Http.Results/src/ProblemHttpResult.cs index 6c6ce23edab4..b0a42aaa050e 100644 --- a/src/Http/Http.Results/src/ProblemHttpResult.cs +++ b/src/Http/Http.Results/src/ProblemHttpResult.cs @@ -55,11 +55,11 @@ public Task ExecuteAsync(HttpContext httpContext) if (StatusCode is { } code) { - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, code); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, code); httpContext.Response.StatusCode = code; } - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger, value: ProblemDetails, diff --git a/src/Http/Http.Results/src/PushStreamHttpResult.cs b/src/Http/Http.Results/src/PushStreamHttpResult.cs index 2ea30e3d719f..e9c61785d37c 100644 --- a/src/Http/Http.Results/src/PushStreamHttpResult.cs +++ b/src/Http/Http.Results/src/PushStreamHttpResult.cs @@ -106,7 +106,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.PushStreamResult"); - var (range, rangeLength, completed) = HttpResultsWriter.WriteResultAsFileCore( + var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore( httpContext, logger, FileDownloadName, diff --git a/src/Http/Http.Results/src/StatusCodeHttpResult.cs b/src/Http/Http.Results/src/StatusCodeHttpResult.cs index 5b2fc99f0bdd..67c1f039cfff 100644 --- a/src/Http/Http.Results/src/StatusCodeHttpResult.cs +++ b/src/Http/Http.Results/src/StatusCodeHttpResult.cs @@ -41,7 +41,7 @@ public Task ExecuteAsync(HttpContext httpContext) // Creating the logger with a string to preserve the category after the refactoring. var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.StatusCodeResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; diff --git a/src/Http/Http.Results/src/UnauthorizedHttpResult.cs b/src/Http/Http.Results/src/UnauthorizedHttpResult.cs index fecb51f1124a..a1a1320514e9 100644 --- a/src/Http/Http.Results/src/UnauthorizedHttpResult.cs +++ b/src/Http/Http.Results/src/UnauthorizedHttpResult.cs @@ -34,7 +34,7 @@ public Task ExecuteAsync(HttpContext httpContext) // Creating the logger with a string to preserve the category after the refactoring. var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.UnauthorizedResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; diff --git a/src/Http/Http.Results/src/UnprocessableEntity.cs b/src/Http/Http.Results/src/UnprocessableEntity.cs index 192a489f5363..cc38731f1165 100644 --- a/src/Http/Http.Results/src/UnprocessableEntity.cs +++ b/src/Http/Http.Results/src/UnprocessableEntity.cs @@ -39,7 +39,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.UnprocessableEntityObjectResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; return Task.CompletedTask; diff --git a/src/Http/Http.Results/src/UnprocessableEntityOfT.cs b/src/Http/Http.Results/src/UnprocessableEntityOfT.cs index e94c5d286183..d4eca8216826 100644 --- a/src/Http/Http.Results/src/UnprocessableEntityOfT.cs +++ b/src/Http/Http.Results/src/UnprocessableEntityOfT.cs @@ -24,7 +24,7 @@ public sealed class UnprocessableEntity : IResult, IEndpointMetadataProv internal UnprocessableEntity(TValue? value) { Value = value; - HttpResultsWriter.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); + HttpResultsHelper.ApplyProblemDetailsDefaultsIfNeeded(Value, StatusCode); } /// @@ -50,10 +50,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.UnprocessableEntityObjectResult"); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger: logger, Value); diff --git a/src/Http/Http.Results/src/Utf8ContentHttpResult.cs b/src/Http/Http.Results/src/Utf8ContentHttpResult.cs index 4338975265f6..725c2135e29a 100644 --- a/src/Http/Http.Results/src/Utf8ContentHttpResult.cs +++ b/src/Http/Http.Results/src/Utf8ContentHttpResult.cs @@ -56,11 +56,11 @@ public Task ExecuteAsync(HttpContext httpContext) if (StatusCode is { } statusCode) { - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, statusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, statusCode); httpContext.Response.StatusCode = statusCode; } - httpContext.Response.ContentType = ContentType ?? HttpResultsWriter.DefaultContentType; + httpContext.Response.ContentType = ContentType ?? HttpResultsHelper.DefaultContentType; httpContext.Response.ContentLength = ResponseContent.Length; return httpContext.Response.Body.WriteAsync(ResponseContent).AsTask(); diff --git a/src/Http/Http.Results/src/ValidationProblem.cs b/src/Http/Http.Results/src/ValidationProblem.cs index d00ff853526c..84ead95982d5 100644 --- a/src/Http/Http.Results/src/ValidationProblem.cs +++ b/src/Http/Http.Results/src/ValidationProblem.cs @@ -56,10 +56,10 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger(typeof(ValidationProblem)); - HttpResultsWriter.Log.WritingResultAsStatusCode(logger, StatusCode); + HttpResultsHelper.Log.WritingResultAsStatusCode(logger, StatusCode); httpContext.Response.StatusCode = StatusCode; - return HttpResultsWriter.WriteResultAsJsonAsync( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger, value: ProblemDetails, diff --git a/src/Http/Http.Results/src/VirtualFileHttpResult.cs b/src/Http/Http.Results/src/VirtualFileHttpResult.cs index 458ddd4365de..e3107f4b7929 100644 --- a/src/Http/Http.Results/src/VirtualFileHttpResult.cs +++ b/src/Http/Http.Results/src/VirtualFileHttpResult.cs @@ -117,7 +117,7 @@ public Task ExecuteAsync(HttpContext httpContext) var loggerFactory = httpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Http.Result.VirtualFileResult"); - var (range, rangeLength, completed) = HttpResultsWriter.WriteResultAsFileCore( + var (range, rangeLength, completed) = HttpResultsHelper.WriteResultAsFileCore( httpContext, logger, FileDownloadName, diff --git a/src/Http/Http.Results/test/HttpResultsWriterTests.cs b/src/Http/Http.Results/test/HttpResultsHelperTests.cs similarity index 95% rename from src/Http/Http.Results/test/HttpResultsWriterTests.cs rename to src/Http/Http.Results/test/HttpResultsHelperTests.cs index a5be203fed78..0ebbdbe017a9 100644 --- a/src/Http/Http.Results/test/HttpResultsWriterTests.cs +++ b/src/Http/Http.Results/test/HttpResultsHelperTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Http.HttpResults; -public partial class HttpResultsWriterTests +public partial class HttpResultsHelperTests { [Theory] [InlineData(true)] @@ -34,7 +34,7 @@ public async Task WriteResultAsJsonAsync_Works_ForValueTypes(bool useJsonContext } // Act - await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); + await HttpResultsHelper.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), serializerOptions); @@ -65,7 +65,7 @@ public async Task WriteResultAsJsonAsync_Works_ForReferenceTypes(bool useJsonCon } // Act - await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); + await HttpResultsHelper.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), serializerOptions); @@ -98,7 +98,7 @@ public async Task WriteResultAsJsonAsync_Works_ForChildTypes(bool useJsonContext } // Act - await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); + await HttpResultsHelper.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), serializerOptions); @@ -132,7 +132,7 @@ public async Task WriteResultAsJsonAsync_Works_UsingBaseType_ForChildTypes(bool } // Act - await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); + await HttpResultsHelper.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), serializerOptions); @@ -166,7 +166,7 @@ public async Task WriteResultAsJsonAsync_Works_UsingBaseType_ForChildTypes_WithJ } // Act - await HttpResultsWriter.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); + await HttpResultsHelper.WriteResultAsJsonAsync(httpContext, NullLogger.Instance, value, jsonSerializerOptions: serializerOptions); // Assert var body = JsonSerializer.Deserialize(responseBodyStream.ToArray(), serializerOptions); From a2a972a8212fa8017d4bb76e7758c2c3a18641d5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 25 Jan 2023 11:28:27 -0800 Subject: [PATCH 15/24] Fixing bad merge --- src/Http/Http.Results/test/HttpResultsHelperTests.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Http/Http.Results/test/HttpResultsHelperTests.cs b/src/Http/Http.Results/test/HttpResultsHelperTests.cs index 51d0ad37bb51..0ebbdbe017a9 100644 --- a/src/Http/Http.Results/test/HttpResultsHelperTests.cs +++ b/src/Http/Http.Results/test/HttpResultsHelperTests.cs @@ -191,12 +191,6 @@ private static IServiceProvider CreateServices() { var services = new ServiceCollection(); services.AddSingleton(); - - if (useJsonContext) - { - services.ConfigureHttpJsonOptions(o => o.SerializerOptions.TypeInfoResolver = TestJsonContext.Default); - } - return services.BuildServiceProvider(); } From c2768f43d5a62eb711813830b0c6d31ec90e0925 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 26 Jan 2023 22:12:54 -0800 Subject: [PATCH 16/24] Fix build --- src/Http/Http.Results/src/HttpResultsHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index 6cb71091b8c7..40271f09c3f2 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -1,7 +1,6 @@ // 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.CodeAnalysis; using System.Text; using System.Text.Json; using System.Text.Json.Serialization.Metadata; From 610c7b1edf4b14c23a4c08900b4caf5c147aa070 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 26 Jan 2023 22:16:26 -0800 Subject: [PATCH 17/24] PR review --- src/Http/Http.Results/src/Results.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 3028cda44459..43bcdd7b45ec 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -186,7 +186,7 @@ public static IResult Content(string? content, MediaTypeHeaderValue contentType) [RequiresUnreferencedCode(JsonHttpResultTrimmerWarning.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(JsonHttpResultTrimmerWarning.SerializationRequiresDynamicCodeMessage)] public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) - => Json(data, options, contentType, statusCode); + => Json(data, options, contentType, statusCode); /// /// Creates a that serializes the specified object to JSON. @@ -202,7 +202,7 @@ public static IResult Json(object? data, JsonSerializerOptions? options = null, #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static IResult Json(object? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => Json(data, jsonTypeInfo, contentType, statusCode); + => Json(data, jsonTypeInfo, contentType, statusCode); /// /// Creates a that serializes the specified object to JSON. @@ -218,7 +218,7 @@ public static IResult Json(object? data, JsonTypeInfo jsonTypeInfo, string? cont #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static IResult Json(object? data, JsonSerializerContext jsonSerializerContext, string? contentType = null, int? statusCode = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => Json(data, jsonSerializerContext, contentType, statusCode); + => Json(data, jsonSerializerContext, contentType, statusCode); /// /// Creates a that serializes the specified object to JSON. From 1930364cbe768dae4b9c642a5b61a26238957846 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 27 Jan 2023 10:16:52 -0800 Subject: [PATCH 18/24] PR Feedback --- src/Http/Http.Results/src/HttpResultsHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index 40271f09c3f2..2e260b3d3efc 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -175,7 +175,7 @@ public static void ApplyProblemDetailsDefaultsIfNeeded(object? value, int? statu private static JsonOptions ResolveJsonOptions(HttpContext httpContext) { // Attempt to resolve options from DI then fallback to default options - return httpContext.RequestServices?.GetService>()?.Value ?? new JsonOptions(); + return httpContext.RequestServices.GetService>()?.Value ?? new JsonOptions(); } internal static partial class Log From 3b54e1a8e0953a874d5602e0af6b52012c56553a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 30 Jan 2023 15:52:27 -0800 Subject: [PATCH 19/24] Update for the approved API --- .../Http.Results/src/HttpResultsHelper.cs | 28 ----- .../Http.Results/src/JsonHttpResultOfT.cs | 52 ++++----- .../Http.Results/src/PublicAPI.Unshipped.txt | 10 +- src/Http/Http.Results/src/Results.cs | 28 +++-- src/Http/Http.Results/src/TypedResults.cs | 19 +++- src/Http/Http.Results/test/ResultsTests.cs | 102 +++++++++++++++++- .../Http.Results/test/TypedResultsTests.cs | 68 +++++++++++- 7 files changed, 231 insertions(+), 76 deletions(-) diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index 2e260b3d3efc..6f8df3b68f9b 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -19,34 +19,6 @@ internal static partial class HttpResultsHelper internal const string DefaultContentType = "text/plain; charset=utf-8"; private static readonly Encoding DefaultEncoding = Encoding.UTF8; - public static Task WriteResultAsJsonAsync( - HttpContext httpContext, - ILogger logger, - TValue? value, - JsonTypeInfo jsonTypeInfo, - string? contentType = null) - { - if (value is null) - { - return Task.CompletedTask; - } - - Log.WritingResultAsJson(logger, jsonTypeInfo.Type.Name); - if (jsonTypeInfo is JsonTypeInfo genericTypeInfo) - { - // We don't need to box in this case - return httpContext.Response.WriteAsJsonAsync( - value, - genericTypeInfo, - contentType: contentType); - } - - return httpContext.Response.WriteAsJsonAsync( - value, - jsonTypeInfo, - contentType: contentType); - } - public static Task WriteResultAsJsonAsync( HttpContext httpContext, ILogger logger, diff --git a/src/Http/Http.Results/src/JsonHttpResultOfT.cs b/src/Http/Http.Results/src/JsonHttpResultOfT.cs index f6972f319e10..d92ec573d5f7 100644 --- a/src/Http/Http.Results/src/JsonHttpResultOfT.cs +++ b/src/Http/Http.Results/src/JsonHttpResultOfT.cs @@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; -using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -16,8 +15,6 @@ namespace Microsoft.AspNetCore.Http.HttpResults; /// public sealed partial class JsonHttpResult : IResult, IStatusCodeHttpResult, IValueHttpResult, IValueHttpResult, IContentTypeHttpResult { - private readonly JsonTypeInfo? _jsonTypeInfo; - /// /// Initializes a new instance of the class with the values. /// @@ -47,21 +44,8 @@ internal JsonHttpResult(TValue? value, JsonSerializerOptions? jsonSerializerOpti JsonSerializerOptions = jsonSerializerOptions; } - internal JsonHttpResult(TValue? value, JsonSerializerContext jsonSerializerContext, int? statusCode = null, string? contentType = null) - : this(value, jsonSerializerContext.GetTypeInfo(typeof(TValue))!, statusCode, contentType) - { } - - /// - /// Initializes a new instance of the class with the values. - /// - /// The value to format in the entity body. - /// Metadata about the type to convert. - /// The HTTP status code of the response. - /// The value for the Content-Type header - internal JsonHttpResult(TValue? value, JsonTypeInfo jsonTypeInfo, int? statusCode = null, string? contentType = null) + internal JsonHttpResult(TValue? value, int? statusCode = null, string? contentType = null) { - ArgumentNullException.ThrowIfNull(jsonTypeInfo); - Value = value; ContentType = contentType; @@ -72,9 +56,6 @@ internal JsonHttpResult(TValue? value, JsonTypeInfo jsonTypeInfo, int? statusCod } StatusCode = statusCode; - - _jsonTypeInfo = jsonTypeInfo; - JsonSerializerOptions = jsonTypeInfo.Options; } /// @@ -82,6 +63,11 @@ internal JsonHttpResult(TValue? value, JsonTypeInfo jsonTypeInfo, int? statusCod /// public JsonSerializerOptions? JsonSerializerOptions { get; } + /// + /// Gets or sets the serializer settings. + /// + internal JsonTypeInfo? JsonTypeInfo { get; init; } + /// /// Gets the object result. /// @@ -114,14 +100,28 @@ public Task ExecuteAsync(HttpContext httpContext) httpContext.Response.StatusCode = statusCode; } - if (_jsonTypeInfo is not null) + if (Value is null) + { + return Task.CompletedTask; + } + + if (JsonTypeInfo != null) { - return HttpResultsHelper.WriteResultAsJsonAsync( - httpContext, - logger, + HttpResultsHelper.Log.WritingResultAsJson(logger, JsonTypeInfo.Type.Name); + + if (JsonTypeInfo is JsonTypeInfo typedJsonTypeInfo) + { + // We don't need to box here. + return httpContext.Response.WriteAsJsonAsync( + Value, + typedJsonTypeInfo, + contentType: ContentType); + + } + return httpContext.Response.WriteAsJsonAsync( Value, - _jsonTypeInfo, - ContentType); + JsonTypeInfo, + contentType: ContentType); } return HttpResultsHelper.WriteResultAsJsonAsync( diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt index cfe30baced91..393991164c2b 100644 --- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt @@ -12,10 +12,10 @@ static Microsoft.AspNetCore.Http.Results.Created(string? uri, object? value) -> static Microsoft.AspNetCore.Http.Results.Created(System.Uri? uri, object? value) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Created(string? uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Created(System.Uri? uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! -static Microsoft.AspNetCore.Http.Results.Json(object? data, System.Text.Json.Serialization.JsonSerializerContext! jsonSerializerContext, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Json(object? data, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! -static Microsoft.AspNetCore.Http.Results.Json(TValue? data, System.Text.Json.Serialization.JsonSerializerContext! jsonSerializerContext, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! -static Microsoft.AspNetCore.Http.Results.Json(TValue? data, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Json(object? data, System.Type! type, System.Text.Json.Serialization.JsonSerializerContext! context, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Json(TValue? data, System.Text.Json.Serialization.JsonSerializerContext! context, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Json(TValue? data, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.TypedResults.Created() -> Microsoft.AspNetCore.Http.HttpResults.Created! static Microsoft.AspNetCore.Http.TypedResults.Created(string? uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri? uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! @@ -27,5 +27,5 @@ static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri? uri, T *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(System.Uri! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! *REMOVED*static Microsoft.AspNetCore.Http.TypedResults.Created(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.HttpResults.Created! -static Microsoft.AspNetCore.Http.TypedResults.Json(TValue? data, System.Text.Json.Serialization.JsonSerializerContext! jsonContext, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.JsonHttpResult! -static Microsoft.AspNetCore.Http.TypedResults.Json(TValue? data, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.JsonHttpResult! \ No newline at end of file +static Microsoft.AspNetCore.Http.TypedResults.Json(TValue? data, System.Text.Json.Serialization.JsonSerializerContext! context, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.JsonHttpResult! +static Microsoft.AspNetCore.Http.TypedResults.Json(TValue? data, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.JsonHttpResult! diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 43bcdd7b45ec..39b1f8add1a7 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -201,14 +201,17 @@ public static IResult Json(object? data, JsonSerializerOptions? options = null, /// recreating cached data with each call. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static IResult Json(object? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => Json(data, jsonTypeInfo, contentType, statusCode); + { + ArgumentNullException.ThrowIfNull(jsonTypeInfo); + return new JsonHttpResult(data, statusCode, contentType) { JsonTypeInfo = jsonTypeInfo }; + } /// /// Creates a that serializes the specified object to JSON. /// /// The object to write as JSON. - /// Metadata about the type to convert. + /// The type of object to write. + /// A metadata provider for serializable types. /// The content-type to set on the response. /// The status code to set on the response. /// The created that serializes the specified @@ -216,9 +219,14 @@ public static IResult Json(object? data, JsonTypeInfo jsonTypeInfo, string? cont /// Callers should cache an instance of serializer settings to avoid /// recreating cached data with each call. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public static IResult Json(object? data, JsonSerializerContext jsonSerializerContext, string? contentType = null, int? statusCode = null) -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => Json(data, jsonSerializerContext, contentType, statusCode); + public static IResult Json(object? data, Type type, JsonSerializerContext context, string? contentType = null, int? statusCode = null) + { + ArgumentNullException.ThrowIfNull(context); + return new JsonHttpResult(data, statusCode, contentType) + { + JsonTypeInfo = context.GetTypeInfo(type) ?? throw new InvalidOperationException($"Unable to obtain the JsonTypeInfo for type '{type.FullName}' from the context '{context.GetType().FullName}'.") + }; + } /// /// Creates a that serializes the specified object to JSON. @@ -250,7 +258,7 @@ public static IResult Json(TValue? data, JsonSerializerOptions? options /// Callers should cache an instance of serializer settings to avoid /// recreating cached data with each call. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public static IResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) + public static IResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => TypedResults.Json(data, jsonTypeInfo, contentType, statusCode); @@ -258,7 +266,7 @@ public static IResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, stri /// Creates a that serializes the specified object to JSON. /// /// The object to write as JSON. - /// Metadata about the type to convert. + /// A metadata provider for serializable types. /// The content-type to set on the response. /// The status code to set on the response. /// The created that serializes the specified @@ -266,9 +274,9 @@ public static IResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, stri /// Callers should cache an instance of serializer settings to avoid /// recreating cached data with each call. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public static IResult Json(TValue? data, JsonSerializerContext jsonSerializerContext, string? contentType = null, int? statusCode = null) + public static IResult Json(TValue? data, JsonSerializerContext context, string? contentType = null, int? statusCode = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => TypedResults.Json(data, jsonSerializerContext, contentType, statusCode); + => TypedResults.Json(data, context, contentType, statusCode); /// /// Writes the byte-array content to the response. diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index 5c0631e7e287..faaee64e7e3b 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -209,24 +209,33 @@ public static JsonHttpResult Json(TValue? data, JsonSerializerOp /// The created that serializes the specified /// as JSON format for the response. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public static JsonHttpResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) + public static JsonHttpResult Json(TValue? data, JsonTypeInfo jsonTypeInfo, string? contentType = null, int? statusCode = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => new(data, jsonTypeInfo, statusCode, contentType); + { + ArgumentNullException.ThrowIfNull(jsonTypeInfo); + return new(data, statusCode, contentType) { JsonTypeInfo = jsonTypeInfo }; + } /// /// Creates a that serializes the specified object to JSON. /// /// The type of object that will be JSON serialized to the response body. /// The object to write as JSON. - /// Metadata about the type to convert. + /// A metadata provider for serializable types. /// The content-type to set on the response. /// The status code to set on the response. /// The created that serializes the specified /// as JSON format for the response. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public static JsonHttpResult Json(TValue? data, JsonSerializerContext jsonContext, string? contentType = null, int? statusCode = null) + public static JsonHttpResult Json(TValue? data, JsonSerializerContext context, string? contentType = null, int? statusCode = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - => new(data, jsonContext, statusCode, contentType); + { + ArgumentNullException.ThrowIfNull(context); + return new(data, statusCode, contentType) + { + JsonTypeInfo = context.GetTypeInfo(typeof(TValue)) ?? throw new InvalidOperationException($"Unable to obtain the JsonTypeInfo for type '{typeof(TValue).FullName}' from the context '{context.GetType().FullName}'.") + }; + } /// /// Writes the byte-array content to the response. diff --git a/src/Http/Http.Results/test/ResultsTests.cs b/src/Http/Http.Results/test/ResultsTests.cs index 61e01c493a7d..2a7d13267ccd 100644 --- a/src/Http/Http.Results/test/ResultsTests.cs +++ b/src/Http/Http.Results/test/ResultsTests.cs @@ -8,6 +8,8 @@ using System.Security.Claims; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; @@ -15,7 +17,7 @@ namespace Microsoft.AspNetCore.Http.HttpResults; -public class ResultsTests +public partial class ResultsTests { [Fact] public void Accepted_WithUrlAndValue_ResultHasCorrectValues() @@ -780,6 +782,100 @@ public void Json_WithNoArgs_ResultHasCorrectValues() Assert.Null(result.StatusCode); } + [Fact] + public void Json_WithTypeInfo_ResultHasCorrectValues() + { + // Act + var result = Results.Json(null, StringJsonContext.Default.String as JsonTypeInfo) as JsonHttpResult; + + // Assert + Assert.Null(result.Value); + Assert.Null(result.JsonSerializerOptions); + Assert.Null(result.ContentType); + Assert.Null(result.StatusCode); + Assert.Equal(StringJsonContext.Default.String, result.JsonTypeInfo); + } + + [Fact] + public void Json_WithJsonContext_ResultHasCorrectValues() + { + // Act + var result = Results.Json(null, typeof(string), StringJsonContext.Default) as JsonHttpResult; + + // Assert + Assert.Null(result.Value); + Assert.Null(result.JsonSerializerOptions); + Assert.Null(result.ContentType); + Assert.Null(result.StatusCode); + Assert.IsAssignableFrom>(result.JsonTypeInfo); + } + + [Fact] + public void JsonOfT_WithTypeInfo_ResultHasCorrectValues() + { + // Act + var result = Results.Json(null, StringJsonContext.Default.String) as JsonHttpResult; + + // Assert + Assert.Null(result.Value); + Assert.Null(result.JsonSerializerOptions); + Assert.Null(result.ContentType); + Assert.Null(result.StatusCode); + Assert.Equal(StringJsonContext.Default.String, result.JsonTypeInfo); + } + + [Fact] + public void JsonOfT_WithJsonContext_ResultHasCorrectValues() + { + // Act + var result = Results.Json(null, StringJsonContext.Default) as JsonHttpResult; + + // Assert + Assert.Null(result.Value); + Assert.Null(result.JsonSerializerOptions); + Assert.Null(result.ContentType); + Assert.Null(result.StatusCode); + Assert.IsAssignableFrom>(result.JsonTypeInfo); + } + + [Fact] + public void JsonOfT_WithNullSerializerContext_ThrowsArgException() + { + Assert.Throws("context", () => Results.Json(null, context: null)); + } + + [Fact] + public void Json_WithNullSerializerContext_ThrowsArgException() + { + Assert.Throws("context", () => Results.Json(null, type: typeof(object), context: null)); + } + + [Fact] + public void Json_WithInvalidSerializerContext_ThrowsInvalidOperationException() + { + var ex = Assert.Throws(() => Results.Json(null, type: typeof(object), context: StringJsonContext.Default)); + Assert.Equal(ex.Message, $"Unable to obtain the JsonTypeInfo for type 'System.Object' from the context '{typeof(StringJsonContext).FullName}'."); + } + + [Fact] + public void JsonOfT_WithInvalidSerializerContext_ThrowsInvalidOperationException() + { + var ex = Assert.Throws(() => Results.Json(null, context: StringJsonContext.Default)); + Assert.Equal(ex.Message, $"Unable to obtain the JsonTypeInfo for type 'System.Object' from the context '{typeof(StringJsonContext).FullName}'."); + } + + [Fact] + public void Json_WithNullTypeInfo_ThrowsArgException() + { + Assert.Throws("jsonTypeInfo", () => Results.Json(null, jsonTypeInfo: null)); + } + + [Fact] + public void JsonOfT_WithNullTypeInfo_ThrowsArgException() + { + Assert.Throws("jsonTypeInfo", () => Results.Json(null, jsonTypeInfo: null)); + } + [Fact] public void LocalRedirect_WithNullStringUrl_ThrowsArgException() { @@ -1377,4 +1473,8 @@ private static string GetMemberName(Expression expression) public static IEnumerable FactoryMethodsFromTuples() => FactoryMethodsTuples.Select(t => new object[] { t.Item1, t.Item2 }); private record Todo(int Id); + + [JsonSerializable(typeof(string))] + private partial class StringJsonContext : JsonSerializerContext + { } } diff --git a/src/Http/Http.Results/test/TypedResultsTests.cs b/src/Http/Http.Results/test/TypedResultsTests.cs index 067af3f2d560..f6b31f39c951 100644 --- a/src/Http/Http.Results/test/TypedResultsTests.cs +++ b/src/Http/Http.Results/test/TypedResultsTests.cs @@ -6,6 +6,8 @@ using System.Security.Claims; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; @@ -13,7 +15,7 @@ namespace Microsoft.AspNetCore.Http.HttpResults; -public class TypedResultsTests +public partial class TypedResultsTests { [Fact] public void Accepted_WithStringUrlAndValue_ResultHasCorrectValues() @@ -732,6 +734,65 @@ public void Json_WithNoArgs_ResultHasCorrectValues() Assert.Null(result.StatusCode); } + [Fact] + public void Json_WithTypeInfo_ResultHasCorrectValues() + { + // Arrange + var data = default(object); + + // Act + var result = TypedResults.Json(data, ObjectJsonContext.Default.Object); + + // Assert + Assert.Null(result.Value); + Assert.Null(result.JsonSerializerOptions); + Assert.Null(result.ContentType); + Assert.Null(result.StatusCode); + Assert.Equal(ObjectJsonContext.Default.Object, result.JsonTypeInfo); + } + + [Fact] + public void Json_WithJsonContext_ResultHasCorrectValues() + { + // Arrange + var data = default(object); + + // Act + var result = TypedResults.Json(data, ObjectJsonContext.Default); + + // Assert + Assert.Null(result.Value); + Assert.Null(result.JsonSerializerOptions); + Assert.Null(result.ContentType); + Assert.Null(result.StatusCode); + Assert.IsAssignableFrom>(result.JsonTypeInfo); + } + + [Fact] + public void Json_WithNullSerializerContext_ThrowsArgException() + { + // Arrange + var data = default(object); + + Assert.Throws("context", () => TypedResults.Json(data, context: null)); + } + + [Fact] + public void Json_WithInvalidSerializerContext_ThrowsInvalidOperationException() + { + var ex = Assert.Throws(() => TypedResults.Json(string.Empty, context: ObjectJsonContext.Default)); + Assert.Equal(ex.Message, $"Unable to obtain the JsonTypeInfo for type 'System.String' from the context '{typeof(ObjectJsonContext).FullName}'."); + } + + [Fact] + public void Json_WithNullTypeInfo_ThrowsArgException() + { + // Arrange + var data = default(object); + + Assert.Throws("jsonTypeInfo", () => TypedResults.Json(data, jsonTypeInfo: null)); + } + [Fact] public void LocalRedirect_WithNullStringUrl_ThrowsArgException() { @@ -1213,4 +1274,9 @@ public void UnprocessableEntity_ResultHasCorrectValues() // Assert Assert.Equal(StatusCodes.Status422UnprocessableEntity, result.StatusCode); } + + + [JsonSerializable(typeof(object))] + private partial class ObjectJsonContext : JsonSerializerContext + { } } From 205c6c6e6f4a7f9b96aef1f988e2388acba62ff0 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 30 Jan 2023 16:08:25 -0800 Subject: [PATCH 20/24] PR review --- src/Http/Http.Results/src/Results.cs | 2 +- src/Http/Http.Results/src/TypedResults.cs | 2 +- src/Shared/Json/JsonSerializerExtensions.cs | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 39b1f8add1a7..ab760c99f429 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -224,7 +224,7 @@ public static IResult Json(object? data, Type type, JsonSerializerContext contex ArgumentNullException.ThrowIfNull(context); return new JsonHttpResult(data, statusCode, contentType) { - JsonTypeInfo = context.GetTypeInfo(type) ?? throw new InvalidOperationException($"Unable to obtain the JsonTypeInfo for type '{type.FullName}' from the context '{context.GetType().FullName}'.") + JsonTypeInfo = context.GetRequiredTypeInfo(type) }; } diff --git a/src/Http/Http.Results/src/TypedResults.cs b/src/Http/Http.Results/src/TypedResults.cs index faaee64e7e3b..ceb2cea29bb2 100644 --- a/src/Http/Http.Results/src/TypedResults.cs +++ b/src/Http/Http.Results/src/TypedResults.cs @@ -233,7 +233,7 @@ public static JsonHttpResult Json(TValue? data, JsonSerializerCo ArgumentNullException.ThrowIfNull(context); return new(data, statusCode, contentType) { - JsonTypeInfo = context.GetTypeInfo(typeof(TValue)) ?? throw new InvalidOperationException($"Unable to obtain the JsonTypeInfo for type '{typeof(TValue).FullName}' from the context '{context.GetType().FullName}'.") + JsonTypeInfo = context.GetRequiredTypeInfo(typeof(TValue)) }; } diff --git a/src/Shared/Json/JsonSerializerExtensions.cs b/src/Shared/Json/JsonSerializerExtensions.cs index bf53e0f21993..b4c0b19f2076 100644 --- a/src/Shared/Json/JsonSerializerExtensions.cs +++ b/src/Shared/Json/JsonSerializerExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json; +using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; namespace Microsoft.AspNetCore.Http; @@ -16,4 +17,7 @@ public static JsonTypeInfo GetReadOnlyTypeInfo(this JsonSerializerOptions option options.MakeReadOnly(); return options.GetTypeInfo(type); } + + public static JsonTypeInfo GetRequiredTypeInfo(this JsonSerializerContext context, Type type) + => context.GetTypeInfo(type) ?? throw new InvalidOperationException($"Unable to obtain the JsonTypeInfo for type '{type.FullName}' from the context '{context.GetType().FullName}'."); } From fba0039c185f644118584dabb02524bca154eee4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 30 Jan 2023 16:39:11 -0800 Subject: [PATCH 21/24] Update TypedResultsTests.cs --- src/Http/Http.Results/test/TypedResultsTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Http/Http.Results/test/TypedResultsTests.cs b/src/Http/Http.Results/test/TypedResultsTests.cs index f6b31f39c951..93044558ef5b 100644 --- a/src/Http/Http.Results/test/TypedResultsTests.cs +++ b/src/Http/Http.Results/test/TypedResultsTests.cs @@ -1275,7 +1275,6 @@ public void UnprocessableEntity_ResultHasCorrectValues() Assert.Equal(StatusCodes.Status422UnprocessableEntity, result.StatusCode); } - [JsonSerializable(typeof(object))] private partial class ObjectJsonContext : JsonSerializerContext { } From e39d168da397cf87daaf68f4459fb3766160ecf8 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 31 Jan 2023 16:12:22 -0800 Subject: [PATCH 22/24] Changing IsPolymorphicSafe --- src/Http/Http.Extensions/src/RequestDelegateFactory.cs | 6 +++--- src/Http/Http.Results/src/HttpResultsHelper.cs | 2 +- .../src/Formatters/SystemTextJsonOutputFormatter.cs | 2 +- src/Shared/Json/JsonSerializerExtensions.cs | 5 ++++- src/Shared/RouteHandlers/ExecuteHandlerHelper.cs | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs index 409daf83f94a..08f8bacf5a82 100644 --- a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs +++ b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs @@ -1055,7 +1055,7 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall, { var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeArg); - if (jsonTypeInfo.IsPolymorphicSafe() == true) + if (jsonTypeInfo.HasKnownPolymorphism()) { return Expression.Call( ExecuteTaskOfTFastMethod.MakeGenericMethod(typeArg), @@ -1096,7 +1096,7 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall, { var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeArg); - if (jsonTypeInfo.IsPolymorphicSafe() == true) + if (jsonTypeInfo.HasKnownPolymorphism()) { return Expression.Call( ExecuteValueTaskOfTFastMethod.MakeGenericMethod(typeArg), @@ -1140,7 +1140,7 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall, { var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(returnType); - if (jsonTypeInfo.IsPolymorphicSafe() == true) + if (jsonTypeInfo.HasKnownPolymorphism()) { return Expression.Call( JsonResultWriteResponseOfTFastAsyncMethod.MakeGenericMethod(returnType), diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index 6f8df3b68f9b..d3a23dcfa68c 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -35,7 +35,7 @@ public static Task WriteResultAsJsonAsync( var jsonTypeInfo = (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(TValue)); Type? runtimeType; - if (jsonTypeInfo.IsPolymorphicSafe() || jsonTypeInfo.Type == (runtimeType = value.GetType())) + if (jsonTypeInfo.IsValid(runtimeType = value.GetType())) { Log.WritingResultAsJson(logger, jsonTypeInfo.Type.Name); return httpContext.Response.WriteAsJsonAsync( diff --git a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs index fb0833174549..b0f68a771377 100644 --- a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs +++ b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs @@ -72,7 +72,7 @@ public sealed override async Task WriteResponseBodyAsync(OutputFormatterWriteCon { var declaredTypeJsonInfo = SerializerOptions.GetTypeInfo(context.ObjectType); - if (declaredTypeJsonInfo.IsPolymorphicSafe() || context.Object is null || runtimeType == declaredTypeJsonInfo.Type) + if (declaredTypeJsonInfo.IsValid(runtimeType)) { jsonTypeInfo = declaredTypeJsonInfo; } diff --git a/src/Shared/Json/JsonSerializerExtensions.cs b/src/Shared/Json/JsonSerializerExtensions.cs index b4c0b19f2076..8f0de5bf0dab 100644 --- a/src/Shared/Json/JsonSerializerExtensions.cs +++ b/src/Shared/Json/JsonSerializerExtensions.cs @@ -9,9 +9,12 @@ namespace Microsoft.AspNetCore.Http; internal static class JsonSerializerExtensions { - public static bool IsPolymorphicSafe(this JsonTypeInfo jsonTypeInfo) + public static bool HasKnownPolymorphism(this JsonTypeInfo jsonTypeInfo) => jsonTypeInfo.Type.IsSealed || jsonTypeInfo.Type.IsValueType || jsonTypeInfo.PolymorphismOptions is not null; + public static bool IsValid(this JsonTypeInfo jsonTypeInfo, Type? runtimeType) + => runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.HasKnownPolymorphism(); + public static JsonTypeInfo GetReadOnlyTypeInfo(this JsonSerializerOptions options, Type type) { options.MakeReadOnly(); diff --git a/src/Shared/RouteHandlers/ExecuteHandlerHelper.cs b/src/Shared/RouteHandlers/ExecuteHandlerHelper.cs index e29b29dadee6..12217930d751 100644 --- a/src/Shared/RouteHandlers/ExecuteHandlerHelper.cs +++ b/src/Shared/RouteHandlers/ExecuteHandlerHelper.cs @@ -37,7 +37,7 @@ public static Task WriteJsonResponseAsync(HttpResponse response, T? value, Js { var runtimeType = value?.GetType(); - if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.IsPolymorphicSafe()) + if (jsonTypeInfo.IsValid(runtimeType)) { // In this case the polymorphism is not // relevant for us and will be handled by STJ, if needed. From 9df633ec160ba6d83d5e6b8f041cf6c448660398 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2023 09:56:01 -0800 Subject: [PATCH 23/24] Fixing notnull annotation --- src/Shared/Json/JsonSerializerExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Shared/Json/JsonSerializerExtensions.cs b/src/Shared/Json/JsonSerializerExtensions.cs index 8f0de5bf0dab..0c0638dc4675 100644 --- a/src/Shared/Json/JsonSerializerExtensions.cs +++ b/src/Shared/Json/JsonSerializerExtensions.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.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; @@ -12,7 +13,7 @@ internal static class JsonSerializerExtensions public static bool HasKnownPolymorphism(this JsonTypeInfo jsonTypeInfo) => jsonTypeInfo.Type.IsSealed || jsonTypeInfo.Type.IsValueType || jsonTypeInfo.PolymorphismOptions is not null; - public static bool IsValid(this JsonTypeInfo jsonTypeInfo, Type? runtimeType) + public static bool IsValid(this JsonTypeInfo jsonTypeInfo, [NotNullWhen(false)] Type? runtimeType) => runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.HasKnownPolymorphism(); public static JsonTypeInfo GetReadOnlyTypeInfo(this JsonSerializerOptions options, Type type) From a0705751a985424e611e3e72a856933970c400a0 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2023 10:18:57 -0800 Subject: [PATCH 24/24] Remove blank line --- src/Http/Http.Results/src/JsonHttpResultOfT.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Http.Results/src/JsonHttpResultOfT.cs b/src/Http/Http.Results/src/JsonHttpResultOfT.cs index d92ec573d5f7..4841e675ecfd 100644 --- a/src/Http/Http.Results/src/JsonHttpResultOfT.cs +++ b/src/Http/Http.Results/src/JsonHttpResultOfT.cs @@ -116,8 +116,8 @@ public Task ExecuteAsync(HttpContext httpContext) Value, typedJsonTypeInfo, contentType: ContentType); - } + return httpContext.Response.WriteAsJsonAsync( Value, JsonTypeInfo,