简体   繁体   中英

Why GetFromJsonAsync can't parse the JSON but ReadFromJsonAsync can parse the same JSON response?

I'm using HttpClient to send requests to my API project, the problem is that the GetFromJsonAsync method fails to parse the response JSON, but if I first get the response using the GetAsync method, and then do a ReadFromJsonAsync on that, it'll parse the JSON successfully, why is that?

This code doesn't work:

public async Task<OperationResult<LoginNextStep>> SearchByEmailOrPhone(string emailOrPhone)
{
    // This fails to parse the response body
    var result = await _client.GetFromJsonAsync<ApiResult<OperationResult<LoginNextStep>>>
        ($"api/user/searchbyemailorphone/{emailOrPhone}", _jsonOptions);
    
    return result.Data;
}

This one does:

public async Task<OperationResult<LoginNextStep>> SearchByEmailOrPhone(string emailOrPhone)
{
    var result = await _client.GetAsync($"api/user/searchbyemailorphone/{emailOrPhone}");

    // But this one works, with the same JSON
    var jsonResult = await result.Content.ReadFromJsonAsync
        <ApiResult<OperationResult<LoginNextStep>>>(_jsonOptions);

    return jsonResult.Data;
}

This is the _jsonOptions passed into the methods (Injected through the constructor into the class):

services.AddSingleton(new JsonSerializerOptions
{
    Converters = { new JsonStringEnumConverter() },
    PropertyNameCaseInsensitive = true
});

EDIT:

I forgot to say this, this only happens with a specific JSON returned from the API, I mean it's the same type as ApiResutl but for some weird reasons it fails to parse it, and in my API project, I'm using the AspNetCoreRateLimit package to throttle the spam requests, and in that package, if it decides to drop a request, it would by default return a line of text, but I could overwrite that and I added my own return type when it drops a request, and it's an ApiResult which is the default return type of all of my API's endpoints, (so I wanted that package to return the same result as other endpoints with 429 status code to indicate TooManyRequests).

This is the overwritten return type when it drops a spam request:

public class CustomIpRateLimitMiddleware : IpRateLimitMiddleware
{
    public CustomIpRateLimitMiddleware(RequestDelegate next, IProcessingStrategy processingStrategy,
        IOptions<IpRateLimitOptions> options, IIpPolicyStore policyStore, IRateLimitConfiguration config,
        ILogger<IpRateLimitMiddleware> logger) : base(next, processingStrategy, options, policyStore, config, logger)
    {
    }

    public override async Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitRule rule,
        string retryAfter)
    {
        var result = new ApiResult
        {
            IsSuccessful = false,
            MetaData = new MetaData
            {
                ApiStatusCode = ApiStatusCode.TooManyRequests,
                Message = "You sent too many requests!!!"
            }
        };

        var jsonOptions = new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } };
        var jsonResult = JsonSerializer.Serialize(result, jsonOptions);
        httpContext.Response.Headers["Retry-After"] = retryAfter;
        httpContext.Response.StatusCode = (int)ApiStatusCode.TooManyRequests;
        httpContext.Response.ContentType = "application/json";

        await httpContext.Response.WriteAsync(jsonResult);
        //return base.ReturnQuotaExceededResponse(httpContext, rule, retryAfter);
    }
}

To produce the issue, first I manually try to spam and the API will throttle my requests:

在此处输入图像描述

And then this is the response body as string, which is the same as the picture above:

在此处输入图像描述

The ReadFromJsonAsync could parse the JSON:

在此处输入图像描述

But the same request with GetFromJsonAsync fails, and it throws an HttpRequestException exception, (I think I did the same thing yesterday and it would throw an exception like CouldNotParseJson to ApiResult type 😐) anyways:

在此处输入图像描述

Exception details:

System.Net.Http.HttpRequestException HResult=0x80131500
Message=Response status code does not indicate success: 429 (Too Many Requests). Source=System.Net.Http StackTrace: at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() at System.Net.Http.Json.HttpClientJsonExtensions.d__13'1.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter'1.GetResult() at Shop.UI.Services.Users.UserService.d__14.MoveNext() in D:\VS Projects\Didikala\src\Shop\Shop.Presentation\Shop.UI\Services\Users\UserService.cs:line 99

This exception was originally thrown at this call stack: System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() System.Net.Http.Json.HttpClientJsonExtensions.GetFromJsonAsyncCore(System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage>, System.Text.Json.JsonSerializerOptions, System.Threading.CancellationToken) System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task) System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task) System.Runtime.CompilerServices.TaskAwaiter.GetResult() Shop.UI.Services.Users.UserService.SearchByEmailOrPhone(string) in UserService.cs

It seems that GetFromJsonAsync tries to parse the JSON only when the response was successful, and it fails because of that, is there any way to make the GetFromJsonAsync parse the JSON response, whether it was successful or not? or I just have to do it in two steps, ie first GetAsync and then parse it with ReadFromJsonAsync ?

GetFromJsonAsync calls EnsureSuccessStatusCode , so it is expecting a status code between 200-299. On the other hand ReadFromJsonAsync doesn't.

See the relevant code here and here .

Your message has a status of 429 so it fails.

You could write your own extension if you want.

public async Task<T> GetFromJsonAsync(string requestUri, JsonSerializerOptions options, CancellationToken cancellationToken = default)
{
    using (var response = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
    {
        var jsonResult = await result.Content.ReadFromJsonAsync<T>(_jsonOptions, cancellationToken);

        return jsonResult;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM