This is about policies and contexts using the Polly library (v7.2.3) and Microsoft.Extensions.Http.Polly (v7.0.5). I'm confused about the SetPolicyExecutionContext
method of the HttpRequestMessage
class: I know it works when policies are added to the HttpClient via an HttpClientFactory, but I'm using it in a case where the HttpClient does not have any policies attached and it's not working as I was expecting. The context is to be used later in the policy OnRetry delegate, like so:
OnRetry = (_, _, _, context) => {
var value = context.ContainsKey("X") ? (string)context["X"] : "NOT FOUND";
Debug.WriteLine(value);
},
The HttpRequest is sent using the ExecuteAsync method of the policy (after setting the context):
//Case 1 (Context in Request)
var context = new Context();
context.Add("X", "ABC");
request.SetPolicyExecutionContext(context);
policy.ExecuteAsync(() => _httpClient.SendAsync(request));
When this code executes and the web request fails, the OnRetry handler always writes "NOT FOUND" to the output.
But if the context is passed directly to the policy, like this:
//Case 2 (Context in Policy)
var context = new Context();
context.Add("myKey", "ABC");
policy.ExecuteAsync((token) => _httpClient.SendAsync(request), context);
Then it works as expected: on failed requests the output gets "ABC".
Why is that? I thought that in Case 1 the ExecuteAsync method would get the context from the request and pass it to the executing policy so it would be equivalent to Case 2, but that's not happening, so it looks like I'm missing something.
The SetPolicyExecutionContext
simply does the following:
request.Properties["PolicyExecutionContext"] = pollyContext;
which means after this call you can access the context
via the HttpRequestMessage
's Properties
collection or simply by calling the request.GetPolicyExecutionContext()
.
So, it attaches a context to the request object NOT to the execution.
If you want to access the context inside the OnRetry{Async}
delegate then you have to explicitly pass the context
to the Execute{Async}
call.
UPDATE #1: A bit more details
Whenever you use the AddHttpClient
with the AddPolicyHandler
then the context propagation is done on your behalf via the PolicyHttpMessageHandler
. This class is a DelegatingHandler
and its SendAsync
method does the magic for you:
var cleanUpContext = false;
var context = request.GetPolicyExecutionContext();
if (context == null)
{
context = new Context();
request.SetPolicyExecutionContext(context);
cleanUpContext = true;
}
HttpResponseMessage response;
try
{
var policy = _policy ?? SelectPolicy(request);
response = await policy.ExecuteAsync((c, ct) => SendCoreAsync(request, c, ct), context, cancellationToken).ConfigureAwait(false);
}
finally
{
if (cleanUpContext)
{
request.SetPolicyExecutionContext(null);
}
}
return response;
ExecuteAsync
SendAsync
call then it will delete the temporarily attached contextBased on your question I assume that you are not using the PolicyHttpMessageHandler
so, no one will automatically pass the context to the ExecuteAsync
.