Reputation: 458
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 99This 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, i.e. first GetAsync
and then parse it with ReadFromJsonAsync
?
Upvotes: 3
Views: 8140
Reputation: 72040
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;
}
}
Upvotes: 2