After reading Peter Csala comment on my original post, I found out that Microsoft already include a RetryHandler
by default on the GraphServiceClient
.
However, I wanted to override the Retry mechanism with my own and I found this post showing a custom implementation that was a good starting point.
My final good looks like this:
private const string RetryAfterHeaderKey = "Retry-After";
private const int MaxRetry = 3;
[...]
context.Services.AddSingleton(_ =>
{
var graphApiOptions = new GraphApiOptions();
context.Services.GetConfiguration().Bind(GraphApiOptions.SectionName, graphApiOptions);
var clientSecretCredential = new ClientSecretCredential(graphApiOptions.TenantId, graphApiOptions.ClientId, graphApiOptions.ClientSecret);
var handlers = GraphClientFactory.CreateDefaultHandlers(new TokenCredentialAuthProvider(clientSecretCredential));
// Find the index of the existing RetryHandler, if any
var retryHandlerIndex = handlers.FindIndex(handler => handler is RetryHandler);
// Remove the existing RetryHandler, if found
if (retryHandlerIndex >= 0)
{
handlers.RemoveAt(retryHandlerIndex);
handlers.Insert(retryHandlerIndex, new RetryHandler(new RetryHandlerOption()
{
MaxRetry = MaxRetry,
ShouldRetry = (delay, attempt, httpResponse) =>
{
if (httpResponse.IsSuccessStatusCode)
{
return false;
}
Log.Error($"{httpResponse.RequestMessage?.RequestUri} request returned status code {httpResponse.StatusCode}");
// Add more status codes here or change your if statement...
if (httpResponse?.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden or HttpStatusCode.NotFound)
{
return false;
}
var delayInSeconds = CalculateDelay(httpResponse, attempt, delay);
switch (attempt)
{
case 0:
Log.Error($"Request failed, let's retry after a delay of {delayInSeconds} seconds");
break;
case MaxRetry:
Log.Error($"This was the last retry attempt {attempt}");
break;
default:
Log.Error($"This was retry attempt {attempt}, let's retry after a delay of {delayInSeconds} seconds");
break;
}
return true;
}
}));
}
// Now we have an extra step of creating a HTTPClient passing in the customized pipeline
var httpClient = GraphClientFactory.Create(handlers);
// Then we construct the Graph Service Client using the HTTPClient
return new GraphServiceClient(httpClient);
});
internal static double CalculateDelay(HttpResponseMessage response, int retryCount, int delay)
{
var headers = response.Headers;
double delayInSeconds = delay;
if (headers.TryGetValues(RetryAfterHeaderKey, out var values))
{
var retryAfter = values.First();
if (int.TryParse(retryAfter, out var delaySeconds))
{
delayInSeconds = delaySeconds;
}
}
else
{
delayInSeconds = retryCount * delay;
}
const int maxDelay = 9;
delayInSeconds = Math.Min(delayInSeconds, maxDelay);
return delayInSeconds;
}