- 
                Notifications
    You must be signed in to change notification settings 
- Fork 841
Description
Background and motivation
.NET provides the ConfigureHttpClientDefaults method that allows you to configure the default behavior of the HttpClient. With the help of this method you can register the StandardResiliencePipeline as part of the default configuration of the HttpClient:
services.ConfigureHttpClientDefaults(builder => builder.AddStandardResilienceHandler());This is convenient, since you don't need to configure the resilience pipeline for each HttpClient. But with the API we currently provide users face a few issues/challenges when they need to change the default configuration. Below are a couple of known issues/challanges.
Issue 1: Removing the existing default resilience pipeline and adding a custom one
For example, a user registers the StandardResilienceHandler as default configuration, but at the same time he/she wants to use the StandardHedgingHandler for a particular named HttpClient:
services.ConfigureHttpClientDefaults(builder => builder.AddStandardResilienceHandler());
// For a named HttpClient "custom" we want to remove the StandardResilienceHandler and add instead the StandardHedgingHandler.
services.AddHttpClient("custom")./*Remove StandardResilienceHandler*/.AddStandardHedgingHandler();Currently, there is no API that allows users to remove the StandardResilienceHandler and register a new one. Now users can work that around as described here #4814 (comment).
Issue 2: Override configuration of the default resilience pipeline for a particular HttpClient
A user registers the StandardResilienceHandler as default configuration, and wants to provide custom configuration for a particular HttpClient:
services.ConfigureHttpClientDefaults(builder => builder.AddStandardResilienceHandler());
// For a named HttpClient "custom" we want to customize settings of the StandardResilienceHandler.
services.AddHttpClient("custom")./*Configure default StandardResilienceHandler*/;Currently, there is no API to override the default configuration, see #4814.
Issue 3: Replace the default resilience pipeline with a custom one
In oppose to the "Issue 1" a user wants to replace the default resilience pipeline with a custom one. He/she wants to replace it, because it might be important to keep the order where the default resilience pipeline was registered, for example, it could be important for metering purposes.
services.ConfigureHttpClientDefaults(builder => builder.AddStandardResilienceHandler());
// For a named HttpClient "custom" we want to replace the StandardResilienceHandler with a custom one.
services.AddHttpClient("custom")./*We want to replace default StandardResilienceHandler, i.e. the new pipeline will be in the same order as the default one*/;Supporting such scenario could be useful to fix issues like the following #5021. Because the default resilience pipeline and its state are shared by all HttpClient instances it could lead to issues, like the one mentioned in the previous sentence. And having an ability to replace the default pipeline with a custom one for a particular HttpClient could be a solution to that issue.
API Proposal
Remove all resilience handlers:
namespace Microsoft.Extensions.DependencyInjection;
public static class ResilienceHttpClientBuilderExtensions
{
+   /// <summary>
+   /// Removes all resilience handlers registered earlier.
+   /// </summary>
+   /// <param name="builder">The HTTP client builder.</param>
+   /// <returns>The value of <paramref name="builder"/>.</returns>
+   public static IHttpClientBuilder RemoveAllResilienceHandlers(this IHttpClientBuilder builder);
}Add or replace the StandardResilienceHandler:
namespace Microsoft.Extensions.DependencyInjection;
public static class ResilienceHttpClientBuilderExtensions
{
+    /// <summary>
+    /// If there is no standard Resilience or Hedging handlers registered,
+    /// then the method adds the standard Resilience handler to the HttpClient request pipeline.
+    /// Otherwise, the existing standard Resilience or Hedging handler is replaced with the standard Resilience handler.
+    /// </summary>
+    /// <param name="builder">The HTTP client builder.</param>
+    /// <returns>A <see cref="IHttpStandardResiliencePipelineBuilder"/> instance that can be used to configure the standard resilience handler behavior.</returns>
+    public static IHttpStandardResiliencePipelineBuilder AddOrReplaceStandardResilienceHandler(
+        this IHttpClientBuilder builder);
+    /// <summary>
+    /// If there is no standard Resilience or Hedging handlers registered,
+    /// then the method adds the standard Resilience handler to the HttpClient request pipeline.
+    /// Otherwise, the existing standard Resilience or Hedging handler is replaced with the standard Resilience handler.
+    /// </summary>
+    /// <param name="builder">The HTTP client builder.</param>
+    /// <param name="section">The section that the options will bind against.</param>
+    /// <returns>A <see cref="IHttpStandardResiliencePipelineBuilder"/> instance that can be used to configure the standard resilience handler behavior.</returns>
+    public static IHttpStandardResiliencePipelineBuilder AddOrReplaceStandardResilienceHandler(
+        this IHttpClientBuilder builder,
+        IConfigurationSection section);
+    /// <summary>
+    /// If there is no standard Resilience or Hedging handlers registered,
+    /// then the method adds the standard Resilience handler to the HttpClient request pipeline.
+    /// Otherwise, the existing standard Resilience or Hedging handler is replaced with the standard Resilience handler.
+    /// </summary>
+    /// <param name="builder">The HTTP client builder.</param>
+    /// <param name="configure">The callback that configures the options.</param>
+    /// <returns>A <see cref="IHttpStandardResiliencePipelineBuilder"/> instance that can be used to configure the standard resilience handler behavior.</returns>
+    public static IHttpStandardResiliencePipelineBuilder AddOrReplaceStandardResilienceHandler(
+        this IHttpClientBuilder builder,
+        Action<HttpStandardResilienceOptions> configure);
}Add or replace the StandardHedgingHandler:
namespace Microsoft.Extensions.DependencyInjection;
public static partial class ResilienceHttpClientBuilderExtensions
{
+    /// <summary>
+    /// If there is no standard Resilience or Hedging handlers registered,
+    /// then the method adds the standard Hedging handler to the HttpClient request pipeline.
+    /// Otherwise, the existing standard Resilience or Hedging handler is replaced with the standard Hedging handler.
+    /// </summary>
+    /// <param name="builder">The HTTP client builder.</param>
+    /// <returns>A <see cref="IStandardHedgingHandlerBuilder"/> instance that can be used to configure the standard hedging behavior.</returns>
+    public static IStandardHedgingHandlerBuilder AddOrReplaceStandardHedgingHandler(
+        this IHttpClientBuilder builder);
+    /// <summary>
+    /// If there is no standard Resilience or Hedging handlers registered,
+    /// then the method adds the standard Hedging handler to the HttpClient request pipeline.
+    /// Otherwise, the existing standard Resilience or Hedging handler is replaced with the standard Hedging handler.
+    /// </summary>
+    /// <param name="builder">The HTTP client builder.</param>
+    /// <param name="configure">Configures the routing strategy associated with this handler.</param>
+    /// <returns>A <see cref="IStandardHedgingHandlerBuilder"/> instance that can be used to configure the standard hedging behavior.</returns>
+    public static IStandardHedgingHandlerBuilder AddOrReplaceStandardHedgingHandler(
+        this IHttpClientBuilder builder,
+        Action<IRoutingStrategyBuilder> configure);
}API Usage
With the help of the proposed API the issues described in the beginning could be fixed as following.
Issue 1: Removing the existing default resilience pipeline and adding a custom one
services.ConfigureHttpClientDefaults(builder => builder.AddStandardResilienceHandler());
// For a named HttpClient "custom" we want to remove the StandardResilienceHandler and add instead the StandardHedgingHandler.
services.AddHttpClient("custom")
    .RemoveAllResilienceHandlers()
    .AddStandardHedgingHandler();Issue 2: Override configuration of the default resilience pipeline for a particular HttpClient
services.ConfigureHttpClientDefaults(builder => builder.AddStandardResilienceHandler());
// For a named HttpClient "custom" we want to customize settings of the StandardResilienceHandler.
services.AddHttpClient("custom")
    .AddOrReplaceStandardResilienceHandler(options =>
    {
        // Configure options here.
    });Issue 3: Replace the default resilience pipeline with a custom one
services.ConfigureHttpClientDefaults(builder => builder.AddStandardResilienceHandler());
// For a named HttpClient "custom" we want to replace the StandardResilienceHandler with a custom one. There are 2 cases:
// Case 1: we replace the original StandardResilienceHandler with a new StandardResilienceHandler with custom configuration options.
// This case is similar to Issue 2.
services.AddHttpClient("custom")
    .AddOrReplaceStandardResilienceHandler(options =>
    {
        // Configure options here.
    });
// Case 2: we replace the original StandardResilienceHandler with the StandardHedgingHandler.
services.AddHttpClient("custom").AddOrReplaceStandardHedgingHandler();Alternative Designs
The API design above considers HttpClient's delegating handlers as a list (the order of the items is important), therefore it introduces the AddOrReplace methods that implicitly "say" that the standard resilience handler (which is a delegating handler) will be replaced if any. Otherwise, it will be added to the end of the list.
The alternative design suggests not to introduce AddOrReplace methods, but instead to implement AddOrReplace semantics in the existing Add methods. The justification behind that is: having more than one standard resilience handlers in the HttpClient is INCORRECT.
Since it is not correct to have more than one standard resilience handlers in the HttpClient, we consider reasonable to implement AddOrReplace semantics in the existing Add methods:
- if there is already a standard resilience handler in the HttpClient, then we replace it and reconfigure
- otherwise, we add a new handler to the HttpClient
With such an approach we'll need to introduce the only method RemoveAllResilienceHandlers mentioned above, and then the issues described above could be fixed as following:
Issue 1: Removing the existing default resilience pipeline and adding a custom one
services.ConfigureHttpClientDefaults(builder => builder.AddStandardResilienceHandler());
// For a named HttpClient "custom" we want to remove the StandardResilienceHandler and add instead the StandardHedgingHandler.
services.AddHttpClient("custom")
    .RemoveAllResilienceHandlers()
    .AddStandardHedgingHandler();Issue 2: Override configuration of the default resilience pipeline for a particular HttpClient
services.ConfigureHttpClientDefaults(builder => builder.AddStandardResilienceHandler());
// For a named HttpClient "custom" we want to customize settings of the StandardResilienceHandler.
services.AddHttpClient("custom")
    .AddStandardResilienceHandler(options =>
    {
        // Configure options here.
    });Issue 3: Replace the default resilience pipeline with a custom one
services.ConfigureHttpClientDefaults(builder => builder.AddStandardResilienceHandler());
// For a named HttpClient "custom" we want to replace the StandardResilienceHandler with a custom one. There are 2 cases:
// Case 1: we replace the original StandardResilienceHandler with a new StandardResilienceHandler with custom configuration options.
// This case is similar to Issue 2.
services.AddHttpClient("custom")
    .AddStandardResilienceHandler(options =>
    {
        // Configure options here.
    });
// Case 2: we replace the original StandardResilienceHandler with the StandardHedgingHandler.
services.AddHttpClient("custom").AddStandardHedgingHandler();Risks. It could be confusing for customers that Add methods has AddOrReplace semantics. We'll need to properly document that.
Risks
There are a few risks:
1. Names of the methods AddOrReplace... are not perfect
Names AddOrReplaceStandardResilienceHandler and AddOrReplaceStandardHedgingHandler are not perfect. The suffixes ReplaceStandardResilienceHandler and ReplaceStandardHedgingHandler might bring confusion that the methods replace only the corresponding standard resilience handler type, i.e. either the Resilience handler or the Hedging handler. In fact each method replaces any of the standard handlers. So the names of the methods don't fully describe what the methods do. The name that will fully describe the methods would be AddStandardResilienceHandler_Or_ReplaceAnyStandardHandlerWithStandardResilienceHandler, but it is too long, therefore we have to find some balance.
2. We'll need to introduce a breaking change to the behavior of the existing methods registering standard Resilience and Hedging handlers
In order to introduce AddOrReplace methods we'll need to ensure that HttpClient request pipeline has only one Resilience or Hedging handler. For that we'll need to change the behavior of the existing methods registering Resilience and Hedging handlers in HttpClient request pipeline. In particular, the new behavior will throw an exception when a user attempts to register one of those handlers twice. Currently, we don't throw an exception but register the handler twice which is also not correct. Therefore this risk could be not so high/important.
3. Additional effort to maintaining new AddOrReplace methods
- 
With the introduction of new AddOrReplacemethods we'll have to maintain them, meaning that adding some capability to existingAddStandardResilienceHandlermethods would result in mirroring it to theAddOrReplacemethods.
- 
Currently we have 2 standard handlers: Resilience and Hedging. Probably, introducing a new standard handler would lead to creating similar AddOrReplacemethods to have feature parity.