diff --git a/Directory.Build.targets b/Directory.Build.targets
index c95f8662..7923ee0a 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -116,6 +116,7 @@
+ $(DefineConstants);SUPPORTS_HTTP_CLIENT_RESILIENCE
$(DefineConstants);SUPPORTS_IMMUTABLE_COLLECTIONS_MARSHAL
$(DefineConstants);SUPPORTS_TIME_PROVIDER
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 240d50fa..a4131234 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -326,6 +326,7 @@
+
diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj b/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj
index 8bbc5c0d..16f9ae41 100644
--- a/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj
+++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddict.Client.SystemNetHttp.csproj
@@ -42,6 +42,11 @@
+
+
+
+
diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs
index 9d5278bc..f9ac960d 100644
--- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs
+++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs
@@ -241,6 +241,52 @@ public sealed class OpenIddictClientSystemNetHttpBuilder
return Configure(options => options.HttpErrorPolicy = policy);
}
+#if SUPPORTS_HTTP_CLIENT_RESILIENCE
+ ///
+ /// Replaces the default HTTP resilience pipeline used by the OpenIddict client services.
+ ///
+ ///
+ /// The delegate used to configure the .
+ ///
+ ///
+ /// Note: this option has no effect when an HTTP error policy was explicitly configured
+ /// using .
+ ///
+ /// The instance.
+ public OpenIddictClientSystemNetHttpBuilder SetHttpResiliencePipeline(
+ Action> configuration)
+ {
+ if (configuration is null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ var builder = new ResiliencePipelineBuilder();
+ configuration(builder);
+
+ return SetHttpResiliencePipeline(builder.Build());
+ }
+
+ ///
+ /// Replaces the default HTTP resilience pipeline used by the OpenIddict client services.
+ ///
+ /// The HTTP resilience pipeline.
+ ///
+ /// Note: this option has no effect when an HTTP error policy was explicitly configured
+ /// using .
+ ///
+ /// The instance.
+ public OpenIddictClientSystemNetHttpBuilder SetHttpResiliencePipeline(ResiliencePipeline pipeline)
+ {
+ if (pipeline is null)
+ {
+ throw new ArgumentNullException(nameof(pipeline));
+ }
+
+ return Configure(options => options.HttpResiliencePipeline = pipeline);
+ }
+#endif
+
///
/// Sets the product information used in the "User-Agent" header that is attached
/// to the backchannel HTTP requests sent to the authorization server.
diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
index 7910054d..15124752 100644
--- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
+++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
@@ -12,6 +12,10 @@ using Microsoft.Extensions.Http;
using Microsoft.Extensions.Options;
using Polly;
+#if SUPPORTS_HTTP_CLIENT_RESILIENCE
+using Microsoft.Extensions.Http.Resilience;
+#endif
+
namespace OpenIddict.Client.SystemNetHttp;
///
@@ -105,12 +109,23 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio
// To avoid that, cookies support is explicitly disabled here, for security reasons.
handler.UseCookies = false;
- // Unless the HTTP error policy was explicitly disabled in the options,
- // add the HTTP handler responsible for replaying failed HTTP requests.
+ // If applicable, add the handler responsible for replaying failed HTTP requests.
+ //
+ // Note: on .NET 8.0 and higher, the HTTP error policy is always set
+ // to null by default and an HTTP resilience pipeline is used instead.
if (options.CurrentValue.HttpErrorPolicy is IAsyncPolicy policy)
{
builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy));
}
+
+#if SUPPORTS_HTTP_CLIENT_RESILIENCE
+ else if (options.CurrentValue.HttpResiliencePipeline is ResiliencePipeline pipeline)
+ {
+#pragma warning disable EXTEXP0001
+ builder.AdditionalHandlers.Add(new ResilienceHandler(pipeline));
+#pragma warning restore EXTEXP0001
+ }
+#endif
});
// Register the user-defined HTTP client handler actions.
diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs
index 2cab2ef9..4547a9b8 100644
--- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs
+++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs
@@ -11,6 +11,10 @@ using System.Net.Mail;
using Polly;
using Polly.Extensions.Http;
+#if SUPPORTS_HTTP_CLIENT_RESILIENCE
+using Microsoft.Extensions.Http.Resilience;
+#endif
+
namespace OpenIddict.Client.SystemNetHttp;
///
@@ -21,10 +25,37 @@ public sealed class OpenIddictClientSystemNetHttpOptions
///
/// Gets or sets the HTTP Polly error policy used by the internal OpenIddict HTTP clients.
///
+ ///
+ /// Note: on .NET 8.0 and higher, this property is set to by default.
+ ///
public IAsyncPolicy? HttpErrorPolicy { get; set; }
+#if !SUPPORTS_HTTP_CLIENT_RESILIENCE
= HttpPolicyExtensions.HandleTransientHttpError()
- .OrResult(response => response.StatusCode == HttpStatusCode.NotFound)
- .WaitAndRetryAsync(4, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
+ .OrResult(static response => response.StatusCode is HttpStatusCode.NotFound)
+ .WaitAndRetryAsync(4, static attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
+#endif
+
+#if SUPPORTS_HTTP_CLIENT_RESILIENCE
+ ///
+ /// Gets or sets the HTTP resilience pipeline used by the internal OpenIddict HTTP clients.
+ ///
+ ///
+ /// Note: this property is not used when
+ /// is explicitly set to a non- value.
+ ///
+ public ResiliencePipeline? HttpResiliencePipeline { get; set; }
+ = new ResiliencePipelineBuilder()
+ .AddRetry(new HttpRetryStrategyOptions
+ {
+ DelayGenerator = static arguments => new(
+ TimeSpan.FromSeconds(Math.Pow(2, arguments.AttemptNumber))),
+ MaxRetryAttempts = 4,
+ ShouldHandle = static arguments => new(
+ HttpClientResiliencePredicates.IsTransient(arguments.Outcome) ||
+ arguments.Outcome.Result?.StatusCode is HttpStatusCode.NotFound)
+ })
+ .Build();
+#endif
///
/// Gets or sets the contact mail address used in the "From" header that is
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj b/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj
index 7063d908..cdb49383 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddict.Validation.SystemNetHttp.csproj
@@ -42,6 +42,11 @@
+
+
+
+
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs
index 4dd12524..c245e7fd 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs
@@ -129,6 +129,52 @@ public sealed class OpenIddictValidationSystemNetHttpBuilder
return Configure(options => options.HttpErrorPolicy = policy);
}
+#if SUPPORTS_HTTP_CLIENT_RESILIENCE
+ ///
+ /// Replaces the default HTTP resilience pipeline used by the OpenIddict client services.
+ ///
+ ///
+ /// The delegate used to configure the .
+ ///
+ ///
+ /// Note: this option has no effect when an HTTP error policy was explicitly configured
+ /// using .
+ ///
+ /// The instance.
+ public OpenIddictValidationSystemNetHttpBuilder SetHttpResiliencePipeline(
+ Action> configuration)
+ {
+ if (configuration is null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ var builder = new ResiliencePipelineBuilder();
+ configuration(builder);
+
+ return SetHttpResiliencePipeline(builder.Build());
+ }
+
+ ///
+ /// Replaces the default HTTP resilience pipeline used by the OpenIddict client services.
+ ///
+ /// The HTTP resilience pipeline.
+ ///
+ /// Note: this option has no effect when an HTTP error policy was explicitly configured
+ /// using .
+ ///
+ /// The instance.
+ public OpenIddictValidationSystemNetHttpBuilder SetHttpResiliencePipeline(ResiliencePipeline pipeline)
+ {
+ if (pipeline is null)
+ {
+ throw new ArgumentNullException(nameof(pipeline));
+ }
+
+ return Configure(options => options.HttpResiliencePipeline = pipeline);
+ }
+#endif
+
///
/// Sets the product information used in the "User-Agent" header that is attached
/// to the backchannel HTTP requests sent to the authorization server.
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
index cfbf83bc..0beba556 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
@@ -11,6 +11,10 @@ using Microsoft.Extensions.Http;
using Microsoft.Extensions.Options;
using Polly;
+#if SUPPORTS_HTTP_CLIENT_RESILIENCE
+using Microsoft.Extensions.Http.Resilience;
+#endif
+
namespace OpenIddict.Validation.SystemNetHttp;
///
@@ -98,12 +102,23 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO
// To avoid that, cookies support is explicitly disabled here, for security reasons.
handler.UseCookies = false;
- // Unless the HTTP error policy was explicitly disabled in the options,
- // add the HTTP handler responsible for replaying failed HTTP requests.
+ // If applicable, add the handler responsible for replaying failed HTTP requests.
+ //
+ // Note: on .NET 8.0 and higher, the HTTP error policy is always set
+ // to null by default and an HTTP resilience pipeline is used instead.
if (options.CurrentValue.HttpErrorPolicy is IAsyncPolicy policy)
{
builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy));
}
+
+#if SUPPORTS_HTTP_CLIENT_RESILIENCE
+ else if (options.CurrentValue.HttpResiliencePipeline is ResiliencePipeline pipeline)
+ {
+#pragma warning disable EXTEXP0001
+ builder.AdditionalHandlers.Add(new ResilienceHandler(pipeline));
+#pragma warning restore EXTEXP0001
+ }
+#endif
});
// Register the user-defined HTTP client handler actions.
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs
index 86f17b57..35ad1668 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs
@@ -11,6 +11,10 @@ using System.Net.Mail;
using Polly;
using Polly.Extensions.Http;
+#if SUPPORTS_HTTP_CLIENT_RESILIENCE
+using Microsoft.Extensions.Http.Resilience;
+#endif
+
namespace OpenIddict.Validation.SystemNetHttp;
///
@@ -21,10 +25,37 @@ public sealed class OpenIddictValidationSystemNetHttpOptions
///
/// Gets or sets the HTTP Polly error policy used by the internal OpenIddict HTTP clients.
///
+ ///
+ /// Note: on .NET 8.0 and higher, this property is set to by default.
+ ///
public IAsyncPolicy? HttpErrorPolicy { get; set; }
+#if !SUPPORTS_HTTP_CLIENT_RESILIENCE
= HttpPolicyExtensions.HandleTransientHttpError()
- .OrResult(response => response.StatusCode == HttpStatusCode.NotFound)
- .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
+ .OrResult(static response => response.StatusCode is HttpStatusCode.NotFound)
+ .WaitAndRetryAsync(4, static attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
+#endif
+
+#if SUPPORTS_HTTP_CLIENT_RESILIENCE
+ ///
+ /// Gets or sets the HTTP resilience pipeline used by the internal OpenIddict HTTP clients.
+ ///
+ ///
+ /// Note: this property is not used when
+ /// is explicitly set to a non- value.
+ ///
+ public ResiliencePipeline? HttpResiliencePipeline { get; set; }
+ = new ResiliencePipelineBuilder()
+ .AddRetry(new HttpRetryStrategyOptions
+ {
+ DelayGenerator = static arguments => new(
+ TimeSpan.FromSeconds(Math.Pow(2, arguments.AttemptNumber))),
+ MaxRetryAttempts = 4,
+ ShouldHandle = static arguments => new(
+ HttpClientResiliencePredicates.IsTransient(arguments.Outcome) ||
+ arguments.Outcome.Result?.StatusCode is HttpStatusCode.NotFound)
+ })
+ .Build();
+#endif
///
/// Gets or sets the contact mail address used in the "From" header that is