'Unable to deserialize content' when updating Microsoft calendar events in batch on retry

Viewed 107

I'm working on an application based on .NET Framework 4.8. I'm using Microsoft Batching API. The below are code snippets

 public async Task<List<BatchResponse>> UpdateEventsInBatchAsync(string accessToken, Dictionary<int, Tuple<string, OfficeEvent>> absEvents)
    {
        var httpMethod = new HttpMethod("PATCH");
        var batches = GetUpdateRequestBatches(absEvents, httpMethod);

        var graphClient = GetGraphClient(accessToken);

        var batchResponses = new List<BatchResponse>();
        foreach (var batch in batches)
        {
            try
            {
                var batchResponseList = await ExecuteBatchRequestAsync(graphClient, batch).ConfigureAwait(false);
                batchResponses.AddRange(batchResponseList);
            }
            catch (ClientException exc)
            {
                _logService.LogException("Error while processing update batch", exc);
                batchResponses.Add(new BatchResponse
                { StatusCode = HttpStatusCode.InternalServerError, ReasonPhrase = exc.Message });
            }
            catch (Exception exc)
            {
                _logService.LogException("Error while processing update batch", exc);
                batchResponses.Add(new BatchResponse { StatusCode = HttpStatusCode.InternalServerError, ReasonPhrase = exc.Message });
            }
        }

        return batchResponses;
    }

The respective methods used in the above code are mentioned below in respective order-

GetUpdateRequestBatches

 private IEnumerable<BatchRequestContent> GetUpdateRequestBatches(Dictionary<int, Tuple<string, OfficeEvent>> absEvents, HttpMethod httpMethod)
    {
        var batches = new List<BatchRequestContent>();
        var batchRequestContent = new BatchRequestContent();

        const int maxNoBatchItems = 20;
        var batchItemsCount = 0;

        foreach (var kvp in absEvents)
        {
            System.Diagnostics.Debug.Write($"{kvp.Key} --- ");
            System.Diagnostics.Debug.WriteLine(_serializer.SerializeObject(kvp.Value.Item2));

            var requestUri = $"{_msOfficeBaseApiUrl}/me/events/{kvp.Value.Item1}";
            var httpRequestMessage = new HttpRequestMessage(httpMethod, requestUri)
            {
                Content = _serializer.SerializeAsJsonContent(kvp.Value.Item2)
            };
             
            var requestStep = new BatchRequestStep(kvp.Key.ToString(), httpRequestMessage);
            batchRequestContent.AddBatchRequestStep(requestStep);
            batchItemsCount++;

            // Max number of 20 request per batch. So we need to send out multiple batches.
            if (batchItemsCount > 0 && batchItemsCount % maxNoBatchItems == 0)
            {
                batches.Add(batchRequestContent);
                batchRequestContent = new BatchRequestContent();
                batchItemsCount = 0;
            }
        }

        if (batchRequestContent.BatchRequestSteps.Count < maxNoBatchItems)
        {
            batches.Add(batchRequestContent);
        }

        if (batches.Count == 0)
        {
            batches.Add(batchRequestContent);
        }

        return batches;
    }

GetGraphClient

  private static GraphServiceClient GetGraphClient(string accessToken)
    {
        var graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(requestMessage =>
         {
             requestMessage
                 .Headers
                 .Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

             return Task.FromResult(0);
         }));

        return graphClient;
    }

ExecuteBatchRequestAsync

private async Task<List<BatchResponse>> ExecuteBatchRequestAsync(IBaseClient graphClient, BatchRequestContent batch)
    {
        BatchResponseContent response = await graphClient.Batch.Request().PostAsync(batch);

        Dictionary<string, HttpResponseMessage> responses = await response.GetResponsesAsync();

        var batchResponses = new List<BatchResponse>();
        var failedReqKeys = new Dictionary<string, TimeSpan>();

        foreach (var key in responses.Keys)
        {
            using (HttpResponseMessage httpResponseMsg = await response.GetResponseByIdAsync(key))
            {
                var responseContent = await httpResponseMsg.Content.ReadAsStringAsync();

                string eventId = null;
                var reasonPhrase = httpResponseMsg.ReasonPhrase;

                if (!string.IsNullOrWhiteSpace(responseContent))
                {
                    var eventResponse = JObject.Parse(responseContent);
                    eventId = (string)eventResponse["id"];

                    // If still null, then might error have occurred
                    if (eventId == null)
                    {
                        var errorResponse = _serializer.DeserializeObject<ErrorResponse>(responseContent);
                        var error = errorResponse?.Error;
                        if (error != null)
                        {
                            if (httpResponseMsg.StatusCode == (HttpStatusCode)429)
                            {
                                System.Diagnostics.Debug.WriteLine($"{httpResponseMsg.StatusCode} {httpResponseMsg.Content}");
                                var executionDelay = httpResponseMsg.Headers.RetryAfter.Delta ?? TimeSpan.FromSeconds(5);
                                failedReqKeys.Add(key, executionDelay);

                                continue;
                            }

                            reasonPhrase = $"{error.Code} - {error.Message}";
                        }
                    }
                }

                var batchResponse = new BatchResponse
                {
                    Key = key,
                    EventId = eventId,
                    StatusCode = httpResponseMsg.StatusCode,
                    ReasonPhrase = reasonPhrase
                };
                batchResponses.Add(batchResponse);
            }
        }

        if (failedReqKeys.Count == 0) return batchResponses;

        return await HandleFailedRequestsAsync(graphClient, failedReqKeys, batch, batchResponses).ConfigureAwait(false);
    }

HandleFailedRequestsAsync

private async Task<List<BatchResponse>> HandleFailedRequestsAsync(IBaseClient graphClient, Dictionary<string, TimeSpan> failedReqKeys, BatchRequestContent batch, List<BatchResponse> batchResponses)
    {
        // Sleep for the duration as suggested in RetryAfter
        var sleepDuration = failedReqKeys.Values.Max();
        Thread.Sleep(sleepDuration);

        var failedBatchRequests = batch.BatchRequestSteps.Where(b => failedReqKeys.Keys.Contains(b.Key)).ToList();

        var failedBatch = new BatchRequestContent();
        foreach (var kvp in failedBatchRequests)
        {
            failedBatch.AddBatchRequestStep(kvp.Value);
        }

        var failedBatchResponses = await ExecuteBatchRequestAsync(graphClient, failedBatch);
        batchResponses.AddRange(failedBatchResponses);

        return batchResponses;
    }

I'm getting an error as Unable to deserialize content on the first line in method ExecuteBatchRequestAsync as

Microsoft.Graph.ClientException: Code: invalidRequest Message: Unable to deserialize content. ---> System.ObjectDisposedException: Cannot access a closed Stream.

Can anyone nudge me where I'm doing wrong?

0 Answers
Related