Reactive strategies handle specific exceptions thrown or results returned by callbacks. This guide demonstrates how to create a Result Reporting Strategy that listens for specific results and reports them to other components.
Reactive strategies inherit from ResilienceStrategy<T> and use predicates to determine whether to handle an outcome.
Reactive strategies should be internal and not exposed in the public API. Configure them through extension methods and options.
// Strategies should be internal and not exposed in the library's public API.// Use extension methods and options to configure the strategy.internal sealed class ResultReportingResilienceStrategy<T> : ResilienceStrategy<T>{ private readonly Func<ResultReportingPredicateArguments<T>, ValueTask<bool>> _shouldHandle; private readonly Func<OnReportResultArguments<T>, ValueTask> _onReportResult; private readonly ResilienceStrategyTelemetry _telemetry; public ResultReportingResilienceStrategy( Func<ResultReportingPredicateArguments<T>, ValueTask<bool>> shouldHandle, Func<OnReportResultArguments<T>, ValueTask> onReportResult, ResilienceStrategyTelemetry telemetry) { _shouldHandle = shouldHandle; _onReportResult = onReportResult; _telemetry = telemetry; } protected override async ValueTask<Outcome<T>> ExecuteCore<TState>( Func<ResilienceContext, TState, ValueTask<Outcome<T>>> callback, ResilienceContext context, TState state) { // Execute the given callback and adhere to the ContinueOnCapturedContext property value. Outcome<T> outcome = await callback(context, state).ConfigureAwait(context.ContinueOnCapturedContext); // Check if the outcome should be reported using the "ShouldHandle" predicate. if (await _shouldHandle(new ResultReportingPredicateArguments<T>(context, outcome)).ConfigureAwait(context.ContinueOnCapturedContext)) { // Bundle information about the event into arguments. var args = new OnReportResultArguments<T>(context, outcome); // Report this as a resilience event with information severity level to the telemetry infrastructure. _telemetry.Report( new ResilienceEvent(ResilienceEventSeverity.Information, "ResultReported"), context, outcome, args); // Call the "OnReportResult" callback. await _onReportResult(args).ConfigureAwait(context.ContinueOnCapturedContext); } return outcome; }}
Reactive strategies use the ShouldHandle predicate to decide whether to handle the outcome of a user callback.
The predicate arguments follow the {StrategyName}PredicateArguments naming pattern:
public readonly struct ResultReportingPredicateArguments<TResult>{ public ResultReportingPredicateArguments(ResilienceContext context, Outcome<TResult> outcome) { Context = context; Outcome = outcome; } // Always include the "Context" property in the arguments. public ResilienceContext Context { get; } // Always have the "Outcome" property in reactive arguments. public Outcome<TResult> Outcome { get; }}
Reactive arguments must always contain both Context and Outcome properties.
For reporting outcomes, define event-specific arguments:
public readonly struct OnReportResultArguments<TResult>{ public OnReportResultArguments(ResilienceContext context, Outcome<TResult> outcome) { Context = context; Outcome = outcome; } // Always include the "Context" property in the arguments. public ResilienceContext Context { get; } // Always have the "Outcome" property in reactive arguments. public Outcome<TResult> Outcome { get; }}
Create a public options class that inherits from ResilienceStrategyOptions:
public class ResultReportingStrategyOptions<TResult> : ResilienceStrategyOptions{ public ResultReportingStrategyOptions() { // Assign a default name to the options for more detailed telemetry insights. Name = "ResultReporting"; } // Options for reactive strategies should always include a "ShouldHandle" delegate. // Set a sensible default when possible. Here, we handle all exceptions. public Func<ResultReportingPredicateArguments<TResult>, ValueTask<bool>> ShouldHandle { get; set; } = args => { return new ValueTask<bool>(args.Outcome.Exception is not null); }; // This illustrates an event delegate. Note that the arguments struct carries the same name as the delegate but with an "Arguments" suffix. // The event follows the async convention and must be set by the user. // // The [Required] attribute enforces the consumer to specify this property, used when some properties do not have sensible defaults and are required. [Required] public Func<OnReportResultArguments<TResult>, ValueTask>? OnReportResult { get; set; }}
2
Add Non-Generic Support (Optional)
For non-generic pipelines, derive from the generic options:
// Simply derive from the generic options, using 'object' as the result type.// This allows the strategy to manage all results.public class ResultReportingStrategyOptions : ResultReportingStrategyOptions<object>{}
Using options as a public contract ensures flexibility and allows you to add new members without breaking changes.
Create extension methods to register the strategy with the pipeline builder:
public static class ResultReportingResilienceStrategyBuilderExtensions{ // Add extensions for the generic builder. // Extensions should return the builder to support a fluent API. public static ResiliencePipelineBuilder<TResult> AddResultReporting<TResult>( this ResiliencePipelineBuilder<TResult> builder, ResultReportingStrategyOptions<TResult> options) { // Add the strategy through the AddStrategy method. This method accepts a factory delegate // and automatically validates the options. return builder.AddStrategy( context => { // The "context" provides various properties for the strategy's use. // In this case, we simply use the "Telemetry" property and pass it to the strategy. // The ShouldHandle and OnReportResult values are sourced from the options. var strategy = new ResultReportingResilienceStrategy<TResult>( options.ShouldHandle, options.OnReportResult!, context.Telemetry); return strategy; }, options); } // Optionally, if suitable for the strategy, add support for non-generic builders. // Observe the use of the non-generic ResultReportingStrategyOptions. public static ResiliencePipelineBuilder AddResultReporting( this ResiliencePipelineBuilder builder, ResultReportingStrategyOptions options) { return builder.AddStrategy( context => { var strategy = new ResultReportingResilienceStrategy<object>( options.ShouldHandle, options.OnReportResult!, context.Telemetry); return strategy; }, options); }}