Learn how to use ResilienceContext to pass data and share state throughout pipeline execution
The ResilienceContext class provides an execution-scoped instance that accompanies each execution through a Polly resilience pipeline. It enables you to share context and facilitate information exchange between different stages of execution and across multiple strategies.
Think of ResilienceContext as a backpack that travels with your request through the entire pipeline. It carries important information that strategies and your code can access and modify along the way.
The resilience context is shared across all strategies in a pipeline and persists throughout the entire execution, including across retry attempts.
public static class MyResilienceKeys{ public static readonly ResiliencePropertyKey<string> Key1 = new("my-key-1"); public static readonly ResiliencePropertyKey<int> Key2 = new("my-key-2");}
Define keys in a static class for easy discovery and maintenance. The ResiliencePropertyKey<T> is a lightweight struct-based API.
ResiliencePipeline pipeline = new ResiliencePipelineBuilder() .AddRetry(new() { OnRetry = static args => { // Access custom data from the context if (args.Context.Properties.TryGetValue(MyResilienceKeys.Key1, out var data)) { Console.WriteLine("OnRetry, Custom Data: {0}", data); } return default; } }) .Build();await pipeline.ExecuteAsync( static async context => { // Your execution logic here // You can also access context.Properties here }, context);
Operation keys are reported in telemetry metrics. Avoid using unbounded values (like user IDs or GUIDs) as operation keys, as this can lead to metric cardinality explosion.
// ❌ Bad - unbounded values$"fetch-user-{userId}" // Don't include IDs$"order-{orderId}" // Don't include GUIDs $"request-{timestamp}" // Don't include timestamps
// ✅ Good - centralized and discoverablepublic static class ResilienceKeys{ public static readonly ResiliencePropertyKey<string> UserId = new("user-id"); public static readonly ResiliencePropertyKey<int> RetryCount = new("retry-count");}// ❌ Bad - scattered throughout codevar key = new ResiliencePropertyKey<string>("user-id"); // Repeated everywhere
3
Use typed keys
// ✅ Good - type-safepublic static readonly ResiliencePropertyKey<int> RetryCount = new("retry-count");context.Properties.Set(RetryCount, 42);// ❌ Bad - stringly-typedcontext.Properties.Set(new ResiliencePropertyKey<object>("retry-count"), "42");
You might have noticed that some Execute methods accept both a context and a state parameter:
await pipeline.ExecuteAsync( static async (context, state) => { // context: ResilienceContext // state: Your custom data }, context, state);
State Parameter
When to use: Pass parameters to your callback without closures (performance optimization).Scope: Only accessible inside your callback.Purpose: Avoid memory allocations from closures and enable static methods.
Context Parameter
When to use: Share information across strategies and retry attempts.Scope: Accessible throughout the entire pipeline execution.Purpose: Exchange data between strategy delegates and execution attempts.