Skip to content

Keys and Context Data

martincostello edited this page Sep 28, 2023 · 11 revisions

What is Polly.Context?

ℹ️ This documentation describes the previous Polly v7 API. If you are using the new v8 API, please refer to pollydocs.org.

An execution-scoped instance of the Polly.Context class travels with every execution through a Polly policy.

The role of this class is to provide execution context and to allow the exchange of information between the pre-execution, mid-execution, and post-execution phases.

What information can Context carry?

Context carries two kinds of information:

  • Values for pre-defined keys - these can uniquely identify the policy in use, policywrap in use, execution call site, and a unique correlation-id per execution.
  • Custom data - you can add any custom data to Context, using Dictionary<string, object> semantics.

Where is Context available?

The context instance travelling with the execution is available:

  • before execution (if you define it yourself and pass it in)
  • to the main delegate .Execute/Async(...)-d through the policy (when you use an overload taking Context)
  • to every delegate hook (onRetry, onBreak etc)
  • after execution.

It can therefore be used to pass information into executions, get information out of executions, and exchange information among any part of a policy execution.

Pre-defined keys on Context

The following four pre-defined keys are available as properties directly on Context:

Key Scope (ie unique to) How to set Default
(if not set)
Purpose
PolicyWrapKey PolicyWrap instance

(in nested wrap, takes outermost specified value)
policyWrap .WithPolicyKey("someValue") PolicyWrap-partguid;

ornull if no PolicyWrap in use
Indicate the PolicyWrap in use
PolicyKey Policy instance policy .WithPolicyKey("someValue") PolicyType-partguid Indicate the Policy in use
OperationKey call site
(if code specifies it)
pass new Context("someOperationKey") into the Execute call null Indicate where the policy in use

Used by CachePolicy as the cache key for the execution
CorrelationId an individual execution not user-settable a unique guid Uniquely correlate logs/ metrics for a single execution

Changes between Polly <v6 and >=v6

OperationKey was named ExecutionKey prior to v6.

CorrelationId was named ExecutionGuid prior to v6.

Example: Using pre-defined keys

// Identify policies with a PolicyKey, using the WithPolicyKey() extension method
// (for example, for correlation in logs or metrics)

var policy = Policy
    .Handle<DataAccessException>()
    .Retry(3, onRetry: (exception, retryCount, context) =>
       {
           logger.Error($"Retry {retryCount} of {context.PolicyKey} at {context.OperationKey}, due to: {exception}.");
       })
    .WithPolicyKey("MyDataAccessPolicy");

// Identify call sites with an OperationKey, by passing in a Context
var customerDetails = policy.Execute(myDelegate, new Context("GetCustomerDetails"));

// "MyDataAccessPolicy" -> context.PolicyKey
// "GetCustomerDetails" -> context.OperationKey

Using Context for custom data

Context has full Dictionary<string, object> semantics. You can set data on an instance of Context

var context = new Polly.Context();
context["MyCustomData"] = foo;

and get it back later anywhere context is available - within an onRetry hook, within the delegate executed through the policy, after policy execution:

var myFooFromElsewhere = context["MyCustomData"];

Example: Pass information into an execution to enrich logging

Given a policy (abbreviated):

// Policy which logs information available from both pre-defined and custom user context data
var policy = Policy
    .Handle<FooException>()
    .RetryAsync(3, onRetry: (exception, retryCount, context) =>
       {
           logger.Error($"Retry {retryCount} of {context.PolicyKey} at {context.OperationKey}, getting {context["Type"]} of id {context["Id"]}, due to: {exception}.  CorrelationId: {context.CorrelationId}");
       })
    .WithPolicyKey("GetEntitiesPolicy");

An example call site could pass in extra information (abbreviated example code):

public class EntityDataAccess<T>
{
    private IAsyncPolicy Policy { get; }

    public EntityDataAccess(IAsyncPolicy policy) { Policy = policy; }

    public async T GetAsync(int id)
    {
        var context = new Context($"GetAsync-{typeof(T).Name}-{Id}", new Dictionary<string, object>() {
            { "Type" , typeof(T).Name },
            { "Id" , id }
            });

        return await Policy.ExecuteAsync(ctx => database.GetAsync(id), context);
    }
}

Example: Varying the ILogger used within an onRetry delegate

A common use of Context is to bridge the fact that policies are often defined (including policy callback delegates) on startup; but some information you want to use within the delegate hooks (such as an ILogger) might only be available via dependency injection at the call site.

The policy can be pre-defined in StartUp to retrieve the information from context:

var policy = Policy.Handle<FooException>()
   .WaitAndRetryAsync(3,
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
        (exception, timeSpan, retryCount, context) =>
        {
            var msg = $"Retrying request for {retryCount} " +
            $"at {context.OperationKey}, " +
            $"due to: {exception}.";

          var logger = context.GetLogger();
          logger?.LogWarning(msg);
    });

And the call site can configure the ILogger onto Context prior to execution:

// Meanwhile at the call site, given some ILogger logger available by dependency injection

Context context = new Context().WithLogger(logger);
return await policy.ExecuteAsync(ctx => FooAsync(), context);

In the above code, we used some helper methods to make the storage and retrieval of the ILogger more concise and robust:

public static class ContextExtensions
{
    private static readonly string LoggerKey = "LoggerKey";

    public static Context WithLogger(this Context context, ILogger logger)
    {
        context[LoggerKey] = logger;
        return context;
    }

    public static ILogger GetLogger(this Context context)
    {
        if (context.TryGetValue(LoggerKey, out object logger))
        {
            return logger as ILogger;
        }
        return null;
    }
}

Using Context with policies configured via HttpClientFactory

See the Polly with HttpClientFactory page.

Clone this wiki locally