Search code examples
c#asp.net-coredotnet-httpclientpolly

How do you override AddStandardResilenceHandler() for a specific client?


Given the following is set as the default for all clients in the C# project:

builder.Services.ConfigureHttpClientDefaults(http => {
    // Turn on resilience by default
    http.AddStandardResilienceHandler();

});

How does one override the values of that StandardResilienceHandler for a specific Client?

I've tried the following variations:

    services.AddHttpClient<IMyClient, MyClient>()           
        .AddResilienceHandler("MyClient", (context, next) => {
            context.AddTimeout(TimeSpan.FromMinutes(10));
        });

ignored

.AddStandardResilienceHandler(options => {
                options.AttemptTimeout = new HttpTimeoutStrategyOptions {
                    Timeout = TimeSpan.FromMinutes(5)
                };
                options.TotalRequestTimeout = new HttpTimeoutStrategyOptions {
                    Timeout = TimeSpan.FromMinutes(15)
                };
                options.CircuitBreaker.SamplingDuration = TimeSpan.FromMinutes(10);
            });

Also ignored.

I also created a policy like this:

var retryPolicy = HttpPolicyExtensions

                    .HandleTransientHttpError()
                    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
                    .WrapAsync(Policy.TimeoutAsync<HttpResponseMessage>(
                        TimeSpan.FromMinutes(5),
                        TimeoutStrategy.Optimistic));

and then used this:

.AddPolicyHandler(retryPolicy)

It still uses the default no matter what.

You'd think this would be easy and documented but I can't find anything in search.


Solution

  • As far as I know you can't do what you want to achieve.

    Whenever you call AddStandardResilienceHandler or AddResilienceHandler it registers a brand new ResilienceHandler. That class is currently treated as internal. (So, calling it multiple times will register multiple ResilienceHandler instances.)

    You might have the temptation to retrieve the -standard pipeline itself (that's the name of the pipeline which is registered by the AddStandardResilienceHandler call) through the ResiliencePipelineProvider<string>. The problem with that the retrieved ResiliencePipeline it not adjustable directly. It only supports reload but that won't help you either...

    So all in all, currently this use case is not supported. I would recommend to file an issue on the https://github.com/dotnet/extensions/issues repository.


    UPDATE #1

    Here is an example how to set and retrieve overridden value.

    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Http.Resilience;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Options;
    
    var builder = Host.CreateApplicationBuilder(args);
    builder.Services
        .AddHttpClient("test")
        .AddStandardResilienceHandler(options =>
        {
            options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(11);
        });
    
    var serviceProvider = builder.Services.BuildServiceProvider();
    var monitor = serviceProvider.GetRequiredService<IOptionsMonitor<HttpStandardResilienceOptions>>();
    var options = monitor.Get("test-standard");
    Console.WriteLine(options.AttemptTimeout.Timeout);
    
    using var host = builder.Build();
    
    • It registers a named HttpClient with test name
    • It adds the standard resilience handler to it
    • It overrides the AttemptTimeout from 10 seconds (the default) to 11
    • It retrieves an IOptionsMonitor from the DI
    • It retrieves that HttpStandardResilienceOptions which is named as test-standard

    If you use a typed client (instead of a named client) then the options name will be -standard as mentioned in the original post.

    Please note that you can override the options more or less independently. If you want to override AttemptTimeout like in the above example then the rest of the options remain untouched.

    I said more or less independently because for example you can't set the AttemptTimeout to 20 seconds because it will throw an OptionsValidationException due to misalignment with Circuit Breaker options:

    Microsoft.Extensions.Options.OptionsValidationException: The sampling duration of circuit breaker strategy needs to be at least double of an attempt timeout strategy’s timeout interval, in order to be effective. Sampling Duration: 30s,Attempt Timeout: 20s