'OutOfMemoryException while making a Http GET request

Its a straight forward scenario: make the request and convert the json result into equivalent POCO class. Initially I was converting the response to string and then deserializing it :

    private async Task<T> CallApiAsync<T>(string url)
        {
            var httpResponseMessage = await _httpClient.GetAsync(url);
            if (httpResponseMessage.IsSuccessStatusCode)
            {
                var responseBody = await httpResponseMessage.Content.ReadAsStringAsync(); // this one here
                return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(responseBody);
            }
            var errors = await httpResponseMessage.Content.ReadAsStringAsync();
            throw DomainError(httpResponseMessage.StatusCode, url, errors);
        }

For which I used to get this intermittent error:

"Message": "Exception of type 'System.OutOfMemoryException' was thrown.",
  "Source": "System.Private.CoreLib",
  "StackTraceString": "   at System.String.CreateStringFromEncoding(Byte* bytes, Int32 byteLength, Encoding encoding)
  in /_/src/System.Private.CoreLib/shared/System/String.cs:line 505\n 
  at System.Text.UTF8Encoding.GetString(Byte[] bytes, Int32 index, Int32 count)
  in /_/src/System.Private.CoreLib/shared/System/Text/UTF8Encoding.cs:line 663\n   
  at System.Net.Http.HttpContent.ReadBufferAsString(ArraySegment`1 buffer, HttpContentHeaders headers) 
  in /_/src/System.Net.Http/src/System/Net/Http/HttpContent.cs:line 223\n  
  at System.Net.Http.HttpContent.ReadBufferedContentAsString() in /_/src/System.Net.Http/src/System/Net/Http/HttpContent.cs:line 182\n 
  at System.Net.Http.HttpContent.WaitAndReturnAsync[TState,TResult](Task waitTask, TState state, Func`2 returnFunc)
  in /_/src/System.Net.Http/src/System/Net/Http/HttpContent.cs:line 717\n 
  at Infrastructure.Clients.SecurityMasterFileService.CallApiAsync[T](String url)
  in /app/Infrastructure/Clients/SecurityMasterFileService.cs:line 68\n  

So I switched to reading from stream since there was no point converting to string and hogging up the memory:

private async Task<T> CallApiAsync<T>(string url)
        {
            using var httpResponseMessage = await _httpClient.GetAsync(url); //added "using"
            if (httpResponseMessage.IsSuccessStatusCode)
            {
                using var responseBody = await httpResponseMessage.Content.ReadAsStreamAsync(); //changed this line 
                return await System.Text.Json.JsonSerializer.DeserializeAsync<T>(responseBody); //also changed from Newtonsoft to System.Text since the former did not have async overload
            }
            var errors = await httpResponseMessage.Content.ReadAsStringAsync();
            throw DomainError(httpResponseMessage.StatusCode, url, errors);
        }

Now I am getting this intermittent error:

"Message": "Exception of type 'System.OutOfMemoryException' was thrown.",
  "Source": "System.Private.CoreLib",
  "StackTraceString": "   at System.IO.MemoryStream..ctor(Int32 capacity)
  in /_/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs:line 47\n 
  at System.Net.Http.HttpContent.CreateMemoryStream(Int64 maxBufferSize, Exception& error)
  in /_/src/System.Net.Http/src/System/Net/Http/HttpContent.cs:line 522\n  
  at System.Net.Http.HttpContent.LoadIntoBufferAsync(Int64 maxBufferSize, CancellationToken cancellationToken)
  in /_/src/System.Net.Http/src/System/Net/Http/HttpContent.cs:line 430\n 
  at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) 
  in /_/src/System.Net.Http/src/System/Net/Http/HttpClient.cs:line 546\n 
  at Infrastructure.Clients.SecurityMasterFileService.CallApiAsync[T](String url) in /app/Infrastructure/Clients/SecurityMasterFileService.cs:line 68\n

So now I am wondering what further needs to be fixed in the new implementation. Can someone point me out in the correct direction. Thank you.

Edit: Adding the json payload : https://jsonblob.com/4d2eac0f-f42a-11eb-9b1d-736dff69cd5c

Note: From the json payload I require only the first 2 objects, so I have a POCO class for the first two:

    public class Root
    {
        public RootA RootA { get; set; }
        public RootB RootB { get; set; }
    }
    public class RootA
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public int Field3 { get; set; }
        public string Field4 { get; set; }
        public string Field5 { get; set; }
        public int Field6 { get; set; }
        public int Field7 { get; set; }
        public string Field8 { get; set; }
        public string Field9 { get; set; }
        public int Field10 { get; set; }
        public int Field11 { get; set; }
        public int Field12 { get; set; }
        public int Field13 { get; set; }
        public DateTime Field14 { get; set; }
        public string Field15 { get; set; }
        public DateTime Field16 { get; set; }
        public string Field17 { get; set; }
        public string Field18 { get; set; }
        public string Field19 { get; set; }
        public string Field20 { get; set; }
        public string Field21 { get; set; }
        public string Field22 { get; set; }
        public string Field23 { get; set; }
        public string Field24 { get; set; }
        public string Field25 { get; set; }
        public string Field26 { get; set; }
        public DateTime Field27 { get; set; }
    }

    public class RootB
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public DateTime Field3 { get; set; }
        public string Field4 { get; set; }
        public string Field5 { get; set; }
        public string Field6 { get; set; }
        public string Field7 { get; set; }
        public string Field8 { get; set; }
        public int Field9 { get; set; }
        public int Field10 { get; set; }
        public int Field11 { get; set; }
        public int Field12 { get; set; }
        public int Field13 { get; set; }
        public string Field14 { get; set; }
        public int Field15 { get; set; }
        public int Field16 { get; set; }
        public int Field17 { get; set; }
        public int Field18 { get; set; }
        public int Field19 { get; set; }
        public int Field20 { get; set; }
        public int Field21 { get; set; }
        public string Field22 { get; set; }
        public int Field23 { get; set; }
        public string Field24 { get; set; }
        public int Field25 { get; set; }
        public int Field26 { get; set; }
        public string Field27 { get; set; }
        public int Field28 { get; set; }
        public int Field29 { get; set; }
        public string Field30 { get; set; }
        public int Field31 { get; set; }
        public string Field32 { get; set; }
        public string Field33 { get; set; }
        public string Field34 { get; set; }
        public string Field35 { get; set; }
        public string Field36 { get; set; }
        public string Field37 { get; set; }
        public int Field38 { get; set; }
        public string Field39 { get; set; }
        public int Field40 { get; set; }
        public string Field41 { get; set; }
        public DateTime Field42 { get; set; }
        public DateTime Field43 { get; set; }
        public DateTime Field44 { get; set; }
        public string Field45 { get; set; }
        public DateTime Field46 { get; set; }
        public DateTime Field47 { get; set; }
        public string Field48 { get; set; }
        public int Field49 { get; set; }
        public string Field50 { get; set; }
        public string Field51 { get; set; }
        public DateTime Field52 { get; set; }
        public string Field53 { get; set; }
        public string Field54 { get; set; }
        public string Field55 { get; set; }
        public int Field56 { get; set; }
        public string Field57 { get; set; }
        public string Field58 { get; set; }
        public string Field59 { get; set; }
        public string Field60 { get; set; }
        public string Field61 { get; set; }
        public DateTime Field62 { get; set; }
        public string Field63 { get; set; }
        public int Field64 { get; set; }
        public string Field65 { get; set; }
        public string Field66 { get; set; }
    }    



Solution 1:[1]

I got a feedback from one of the senior developer. He asked me to switch from ReadAsStreamAsync to ReadAsByteArrayAsync. Now I don't get the error anymore and its stable in production also

private async Task<T> CallApiAsync<T>(string url)
        {
            using var httpResponseMessage = await _httpClient.GetAsync(url);
            if (httpResponseMessage.IsSuccessStatusCode)
            {
                var responseBody = await httpResponseMessage.Content.ReadAsByteArrayAsync(); //this fixed the issue
                return JsonSerializer.Deserialize<T>(responseBody);
            }
            var errors = await httpResponseMessage.Content.ReadAsStringAsync();
            throw GetDomainError(httpResponseMessage.StatusCode, url, errors);
        }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 afrose