The fallback reactive resilience strategy provides a substitute if the execution of the callback fails. Failure can be either an Exception or a result object indicating unsuccessful processing.
Typically, fallback is used as a last resort - if all other strategies failed to overcome the transient failure, you can still provide a fallback value to the caller.
var options = new FallbackStrategyOptions<UserAvatar>{ ShouldHandle = new PredicateBuilder<UserAvatar>() .Handle<HttpRequestException>() .HandleResult(r => r is null), FallbackAction = static args => { // Generate dynamic fallback based on context var avatar = UserAvatar.GenerateRandomAvatar(); return Outcome.FromResultAsValueTask(avatar); }};
var cache = new MemoryCache(new MemoryCacheOptions());var cacheKey = new ResiliencePropertyKey<string>("CacheKey");var options = new FallbackStrategyOptions<WeatherData>{ ShouldHandle = new PredicateBuilder<WeatherData>() .Handle<HttpRequestException>() .Handle<TimeoutRejectedException>(), FallbackAction = args => { // Try to get from cache var key = args.Context.Properties.GetValue(cacheKey, "default"); if (cache.TryGetValue(key, out WeatherData cachedData)) { Console.WriteLine("Using cached data"); return Outcome.FromResultAsValueTask(cachedData); } // Return default if cache miss return Outcome.FromResultAsValueTask(WeatherData.GetDefault()); }};
public class DataResult<T>{ public T Value { get; set; } public bool IsFallback { get; set; } public DateTime? CacheTime { get; set; }}var pipeline = new ResiliencePipelineBuilder<DataResult<WeatherData>>() .AddFallback(new FallbackStrategyOptions<DataResult<WeatherData>> { ShouldHandle = new PredicateBuilder<DataResult<WeatherData>>() .Handle<Exception>(), FallbackAction = args => { var cached = cache.Get<WeatherData>("weather"); var result = new DataResult<WeatherData> { Value = cached ?? WeatherData.GetDefault(), IsFallback = true, CacheTime = cache.GetCacheTime("weather") }; return Outcome.FromResultAsValueTask(result); } }) .Build();var result = await pipeline.ExecuteAsync(async ct =>{ var data = await weatherService.GetCurrentWeatherAsync(ct); return new DataResult<WeatherData> { Value = data, IsFallback = false };}, cancellationToken);if (result.IsFallback){ Console.WriteLine($"Using fallback data from {result.CacheTime}");}
public class MultiRegionService{ private readonly HttpClient _httpClient; private readonly string[] _regionEndpoints = { "https://us-east.api.example.com", "https://us-west.api.example.com", "https://eu-west.api.example.com" }; private int _currentRegionIndex = 0; public async Task<HttpResponseMessage> GetDataAsync(CancellationToken ct) { var pipeline = new ResiliencePipelineBuilder<HttpResponseMessage>() .AddFallback(new FallbackStrategyOptions<HttpResponseMessage> { ShouldHandle = new PredicateBuilder<HttpResponseMessage>() .Handle<HttpRequestException>() .HandleResult(r => !r.IsSuccessStatusCode), FallbackAction = async args => { // Try next region _currentRegionIndex = (_currentRegionIndex + 1) % _regionEndpoints.Length; var fallbackUrl = _regionEndpoints[_currentRegionIndex]; Console.WriteLine($"Failing over to {fallbackUrl}"); try { var response = await _httpClient.GetAsync( fallbackUrl, args.Context.CancellationToken); return Outcome.FromResult(response); } catch (Exception ex) { return Outcome.FromException<HttpResponseMessage>(ex); } } }) .Build(); return await pipeline.ExecuteAsync(async token => { var primaryUrl = _regionEndpoints[_currentRegionIndex]; return await _httpClient.GetAsync(primaryUrl, token); }, ct); }}
When should I use Fallback vs Retry?
Retry: When you want to attempt the same operation again, hoping it will succeed
Fallback: When you want to provide an alternative value or call a different operation
Often used together: Retry first, then fallback if all retries fail.
Can fallback return a different type?
No, the fallback must return the same type as the original operation. However, you can use wrapper types that indicate whether the result is primary or fallback data.
Should I always provide a fallback?
No. Only use fallback when:
You have a sensible default or cached value
Degraded functionality is acceptable
You can call an alternative service
Some failures should propagate to the caller.
How do I indicate that fallback data was used?
Options:
Return a wrapper type with a flag (e.g., DataResult<T> with IsFallback property)