Просмотр исходного кода

Adding untyped JsonTypeInfo overloads (#45593)

* Adding untyped JsonTypeInfo overloads

* Adding to unshipped.txt
Bruno Oliveira 3 лет назад
Родитель
Сommit
b7ee5054eb

+ 41 - 0
src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs

@@ -126,6 +126,47 @@ public static class HttpRequestJsonExtensions
         }
     }
 
+    /// <summary>
+    /// Read JSON from the request and deserialize to object type.
+    /// If the request's content-type is not a known JSON type then an error will be thrown.
+    /// </summary>
+    /// <param name="request">The request to read from.</param>
+    /// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
+    /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+    /// <returns>The deserialized value.</returns>
+#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
+    public static async ValueTask<object?> ReadFromJsonAsync(
+#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
+        this HttpRequest request,
+        JsonTypeInfo jsonTypeInfo,
+        CancellationToken cancellationToken = default)
+    {
+        if (request == null)
+        {
+            throw new ArgumentNullException(nameof(request));
+        }
+
+        if (!request.HasJsonContentType(out var charset))
+        {
+            ThrowContentTypeError(request);
+        }
+
+        var encoding = GetEncodingFromCharset(charset);
+        var (inputStream, usesTranscodingStream) = GetInputStream(request.HttpContext, encoding);
+
+        try
+        {
+            return await JsonSerializer.DeserializeAsync(inputStream, jsonTypeInfo, cancellationToken);
+        }
+        finally
+        {
+            if (usesTranscodingStream)
+            {
+                await inputStream.DisposeAsync();
+            }
+        }
+    }
+
     /// <summary>
     /// Read JSON from the request and deserialize to the specified type.
     /// If the request's content-type is not a known JSON type then an error will be thrown.

+ 44 - 0
src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs

@@ -144,6 +144,50 @@ public static partial class HttpResponseJsonExtensions
         }
     }
 
+    /// <summary>
+    /// Write the specified value as JSON to the response body. The response content-type will be set to
+    /// the specified content-type.
+    /// </summary>
+    /// <param name="response">The response to write JSON to.</param>
+    /// <param name="value">The value to write as JSON.</param>
+    /// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
+    /// <param name="contentType">The content-type to set on the response.</param>
+    /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+    /// <returns>The task object representing the asynchronous operation.</returns>
+#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
+    public static Task WriteAsJsonAsync(
+#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
+        this HttpResponse response,
+        object? value,
+        JsonTypeInfo jsonTypeInfo,
+        string? contentType = default,
+        CancellationToken cancellationToken = default)
+    {
+        if (response == null)
+        {
+            throw new ArgumentNullException(nameof(response));
+        }
+
+        response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset;
+
+        // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
+        if (!cancellationToken.CanBeCanceled)
+        {
+            return WriteAsJsonAsyncSlow(response, value, jsonTypeInfo);
+        }
+
+        return JsonSerializer.SerializeAsync(response.Body, value, jsonTypeInfo, cancellationToken);
+
+        static async Task WriteAsJsonAsyncSlow(HttpResponse response, object? value, JsonTypeInfo jsonTypeInfo)
+        {
+            try
+            {
+                await JsonSerializer.SerializeAsync(response.Body, value, jsonTypeInfo, response.HttpContext.RequestAborted);
+            }
+            catch (OperationCanceledException) { }
+        }
+    }
+
     [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
     [RequiresDynamicCode(RequiresDynamicCodeMessage)]
     private static async Task WriteAsJsonAsyncSlow<TValue>(

+ 2 - 0
src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt

@@ -45,8 +45,10 @@ Microsoft.AspNetCore.Mvc.ProblemDetails.Type.get -> string? (forwarded, containe
 Microsoft.AspNetCore.Mvc.ProblemDetails.Type.set -> void (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions)
 Microsoft.Extensions.DependencyInjection.ProblemDetailsServiceCollectionExtensions
 Microsoft.Extensions.DependencyInjection.HttpJsonServiceExtensions
+static Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync(this Microsoft.AspNetCore.Http.HttpRequest! request, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<object?>
 static Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync(this Microsoft.AspNetCore.Http.HttpRequest! request, System.Type! type, System.Text.Json.Serialization.JsonSerializerContext! context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<object?>
 static Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync<TValue>(this Microsoft.AspNetCore.Http.HttpRequest! request, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue>! jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<TValue?>
+static Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsync(this Microsoft.AspNetCore.Http.HttpResponse! response, object? value, System.Text.Json.Serialization.Metadata.JsonTypeInfo! jsonTypeInfo, string? contentType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
 static Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsync(this Microsoft.AspNetCore.Http.HttpResponse! response, object? value, System.Type! type, System.Text.Json.Serialization.JsonSerializerContext! context, string? contentType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
 static Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsync<TValue>(this Microsoft.AspNetCore.Http.HttpResponse! response, TValue value, System.Text.Json.Serialization.Metadata.JsonTypeInfo<TValue>! jsonTypeInfo, string? contentType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
 *REMOVED*static Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(System.Delegate! handler, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions? options = null) -> Microsoft.AspNetCore.Http.RequestDelegateResult!

+ 0 - 29
src/Http/Http.Extensions/test/HttpRequestExtensionsTests.cs

@@ -1,29 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#nullable enable
-
-namespace Microsoft.AspNetCore.Http.Extensions.Tests;
-
-public class HttpRequestExtensionsTests
-{
-    [Theory]
-    [InlineData(null, false)]
-    [InlineData("", false)]
-    [InlineData("application/xml", false)]
-    [InlineData("text/json", false)]
-    [InlineData("text/json; charset=utf-8", false)]
-    [InlineData("application/json", true)]
-    [InlineData("application/json; charset=utf-8", true)]
-    [InlineData("application/ld+json", true)]
-    [InlineData("APPLICATION/JSON", true)]
-    [InlineData("APPLICATION/JSON; CHARSET=UTF-8", true)]
-    [InlineData("APPLICATION/LD+JSON", true)]
-    public void HasJsonContentType(string contentType, bool hasJsonContentType)
-    {
-        var request = new DefaultHttpContext().Request;
-        request.ContentType = contentType;
-
-        Assert.Equal(hasJsonContentType, request.HasJsonContentType());
-    }
-}

+ 66 - 0
src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs

@@ -3,6 +3,7 @@
 
 using System.Text;
 using System.Text.Json;
+using System.Text.Json.Serialization.Metadata;
 
 #nullable enable
 
@@ -10,6 +11,26 @@ namespace Microsoft.AspNetCore.Http.Extensions.Tests;
 
 public class HttpRequestJsonExtensionsTests
 {
+    [Theory]
+    [InlineData(null, false)]
+    [InlineData("", false)]
+    [InlineData("application/xml", false)]
+    [InlineData("text/json", false)]
+    [InlineData("text/json; charset=utf-8", false)]
+    [InlineData("application/json", true)]
+    [InlineData("application/json; charset=utf-8", true)]
+    [InlineData("application/ld+json", true)]
+    [InlineData("APPLICATION/JSON", true)]
+    [InlineData("APPLICATION/JSON; CHARSET=UTF-8", true)]
+    [InlineData("APPLICATION/LD+JSON", true)]
+    public void HasJsonContentType(string contentType, bool hasJsonContentType)
+    {
+        var request = new DefaultHttpContext().Request;
+        request.ContentType = contentType;
+
+        Assert.Equal(hasJsonContentType, request.HasJsonContentType());
+    }
+
     [Fact]
     public async Task ReadFromJsonAsyncGeneric_NonJsonContentType_ThrowError()
     {
@@ -207,4 +228,49 @@ public class HttpRequestJsonExtensionsTests
             i => Assert.Equal(1, i),
             i => Assert.Equal(2, i));
     }
+
+    [Fact]
+    public async Task ReadFromJsonAsync_WithTypeInfo_ReturnValue()
+    {
+        // Arrange
+        var context = new DefaultHttpContext();
+        context.Request.ContentType = "application/json";
+        context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2,]"));
+
+        var options = new JsonSerializerOptions();
+        options.AllowTrailingCommas = true;
+        options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+        // Act
+        var result = (List<int>?)await context.Request.ReadFromJsonAsync(options.GetTypeInfo(typeof(List<int>)));
+
+        // Assert
+        Assert.NotNull(result);
+        Assert.Collection(result,
+            i => Assert.Equal(1, i),
+            i => Assert.Equal(2, i));
+    }
+
+    [Fact]
+    public async Task ReadFromJsonAsync_WithGenericTypeInfo_ReturnValue()
+    {
+        // Arrange
+        var context = new DefaultHttpContext();
+        context.Request.ContentType = "application/json";
+        context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2,]"));
+
+        var options = new JsonSerializerOptions();
+        options.AllowTrailingCommas = true;
+        options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+        // Act
+        var typeInfo = (JsonTypeInfo<List<int>>)options.GetTypeInfo(typeof(List<int>));
+        var result = await context.Request.ReadFromJsonAsync(typeInfo);
+
+        // Assert
+        Assert.NotNull(result);
+        Assert.Collection(result,
+            i => Assert.Equal(1, i),
+            i => Assert.Equal(2, i));
+    }
 }

+ 43 - 0
src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs

@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
 using System.Text;
 using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
 using Microsoft.AspNetCore.Testing;
 
 #nullable enable
@@ -438,6 +439,48 @@ public class HttpResponseJsonExtensionsTests
         }
     }
 
+    [Fact]
+    public async Task WriteAsJsonAsyncGeneric_WithJsonTypeInfo_JsonResponse()
+    {
+        // Arrange
+        var body = new MemoryStream();
+        var context = new DefaultHttpContext();
+        context.Response.Body = body;
+
+        // Act
+        var options = new JsonSerializerOptions();
+        options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+        await context.Response.WriteAsJsonAsync(new int[] { 1, 2, 3 }, (JsonTypeInfo<int[]>)options.GetTypeInfo(typeof(int[])));
+
+        // Assert
+        Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+
+        var data = Encoding.UTF8.GetString(body.ToArray());
+        Assert.Equal("[1,2,3]", data);
+    }
+
+    [Fact]
+    public async Task WriteAsJsonAsync_NullValue_WithJsonTypeInfo_JsonResponse()
+    {
+        // Arrange
+        var body = new MemoryStream();
+        var context = new DefaultHttpContext();
+        context.Response.Body = body;
+
+        // Act
+        var options = new JsonSerializerOptions();
+        options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+        await context.Response.WriteAsJsonAsync(value : null, options.GetTypeInfo(typeof(Uri)));
+
+        // Assert
+        Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+
+        var data = Encoding.UTF8.GetString(body.ToArray());
+        Assert.Equal("null", data);
+    }
+
     public class TestObject
     {
         public string? StringProperty { get; set; }