Reputation: 3547
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.
Upvotes: 7
Views: 3639
Reputation: 1234
There are multiple things to this:
HttpClient
The answer by @Peter Csala addresses the first part - basically using
http.AddStandardResilienceHandler(options => {
// set options here
})
The question mentions ConfigureHttpClientDefaults
which means that the http.AddStandardResilienceHandler();
applies to all HttpClient
s.
If you want to replace it for a specific HttpClient
, then you could use the workaround by @NatMarchand until something more permanent is done about it in https://github.com/dotnet/extensions/issues/5695.
Bonus: If you want to override settings for a specific endpoint or in general do it based on request message properties, you can do something like the code below.
⚠️Warning: only apply this code on a specific HttpClient because otherwise all outgoing requests are checked, which might cause a performance issue. You can do this e.g. using the workaround by @NatMarchand for this.
builder.AddStandardResilienceHandler(options =>
{
// This code changes the default AttemptTimeout and TotalRequestTimeout for an endpoint that has a long-running import operation.
options.AttemptTimeout.TimeoutGenerator = timeoutGenerator(
options.AttemptTimeout.Timeout,
TimeSpan.FromSeconds(30));
options.TotalRequestTimeout.TimeoutGenerator = timeoutGenerator(
options.TotalRequestTimeout.Timeout,
options.Retry.MaxRetryAttempts * TimeSpan.FromSeconds(30));
Func<TimeoutGeneratorArguments, ValueTask<TimeSpan>> timeoutGenerator(TimeSpan defaultTimeout, TimeSpan importTimeout)
{
return arguments =>
{
// add the using Polly; namespace for GetRequestMessage()
var tryRequestMessage = arguments.Context.GetRequestMessage();
var timeout = tryRequestMessage switch
{
HttpRequestMessage request when request.RequestUri.AbsolutePath.EndsWith("/import")
=> ValueTask.FromResult(importTimeout),
_ => ValueTask.FromResult(defaultTimeout),
};
return timeout;
};
}
});
Upvotes: 1
Reputation: 22829
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();
test
nameAttemptTimeout
from 10 seconds (the default) to 11IOptionsMonitor
from the DIHttpStandardResilienceOptions
which is named as test-standard
$"{httpClientName}-{pipelineIdentifier}"
pipelineIdentifier
is 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
Upvotes: 5
Reputation: 1
For those who haven't found an answer. Call Configure() :
.AddStandardResilienceHandler().
Configure(options => ...);
Upvotes: -1