- 
                Notifications
    You must be signed in to change notification settings 
- Fork 6.1k
Resiliency in .NET content #37270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
    
      
        
          +710
        
        
          −0
        
        
          
        
      
    
  
  
     Merged
                    Resiliency in .NET content #37270
Changes from 24 commits
      Commits
    
    
            Show all changes
          
          
            41 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      42af17e
              
                Initial bits
              
              
                IEvangelist db2524b
              
                Trying to explore the resilience libs
              
              
                IEvangelist a8cd975
              
                Updates to resiliency
              
              
                IEvangelist 2d43e0a
              
                More updates
              
              
                IEvangelist af43e96
              
                Updates to docs
              
              
                IEvangelist b45429e
              
                More fixes and updates
              
              
                IEvangelist 35bcf66
              
                Fix heading
              
              
                IEvangelist c4ae1cb
              
                More HTTP bits
              
              
                IEvangelist 2971d2f
              
                Apply suggestions from code review
              
              
                IEvangelist deb606a
              
                More tweaks
              
              
                IEvangelist 0436637
              
                Added image
              
              
                IEvangelist 84fad50
              
                Additions
              
              
                IEvangelist c5385c6
              
                More updates
              
              
                IEvangelist 7da24db
              
                Apply suggestions from code review
              
              
                IEvangelist 0cda35c
              
                Clean up and updates
              
              
                IEvangelist a3b83b4
              
                Add context
              
              
                IEvangelist 6f870bd
              
                Retitle a bit
              
              
                IEvangelist b48519a
              
                A quick fix
              
              
                IEvangelist 6f5d134
              
                Resilience in TOC
              
              
                IEvangelist 1fb59e2
              
                Updates to headings
              
              
                IEvangelist 16f50c3
              
                Fix TOC entry
              
              
                IEvangelist 45f1814
              
                Added a bit more detail
              
              
                IEvangelist d254367
              
                Update diagram
              
              
                IEvangelist ac69fce
              
                Updates to HTTP resiliency content
              
              
                IEvangelist 90e16c0
              
                Add more configuration bits
              
              
                IEvangelist c5cef6a
              
                From peer feedback
              
              
                IEvangelist 0201cdc
              
                Clean up
              
              
                IEvangelist 62081e1
              
                Apply suggestions from code review
              
              
                IEvangelist 7d3f28a
              
                Update image
              
              
                IEvangelist 01a0a61
              
                Apply suggestions from code review
              
              
                IEvangelist af85439
              
                Updates from peer review
              
              
                IEvangelist a92ec18
              
                Major updates to the resilience bits
              
              
                IEvangelist 690a0bb
              
                A bit closer
              
              
                IEvangelist 3b3c1da
              
                The id's should work
              
              
                IEvangelist f4095d4
              
                There we go
              
              
                IEvangelist 47088c2
              
                code bits for resilience
              
              
                IEvangelist 6befcd0
              
                Update source with markers
              
              
                IEvangelist 6076f8c
              
                Apply suggestions from code review
              
              
                IEvangelist c079b2f
              
                Apply suggestions from code review
              
              
                IEvangelist 8027a21
              
                Address all feedback
              
              
                IEvangelist 5aff14f
              
                More clean up, final touches
              
              
                IEvangelist File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
      
      Loading
      
  Sorry, something went wrong. Reload?
      Sorry, we cannot display this file.
      Sorry, this file is invalid so it cannot be displayed.
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,264 @@ | ||
| --- | ||
| title: "Build resilient HTTP apps: Key development patterns" | ||
| description: | ||
| author: IEvangelist | ||
| ms.author: dapine | ||
| ms.date: 09/29/2023 | ||
| --- | ||
|  | ||
| # Build resilient HTTP apps: Key development patterns | ||
|  | ||
| Building robust HTTP apps that can recover from transient fault errors is a common requirement. To help build resilient HTTP apps, the [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience) NuGet package provides resilience mechanisms specifically for the <xref:System.Net.Http.HttpClient>. This NuGet package is built on top of _Polly_, which is a popular open-source project. For more information, see [Polly](https://github.com/App-vNext/Polly). | ||
|  | ||
| ## Get started | ||
|  | ||
| To use resilience-patterns in HTTP apps, install the [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience) NuGet package. | ||
|  | ||
| ### [.NET CLI](#tab/dotnet-cli) | ||
|  | ||
| ```dotnetcli | ||
| dotnet add package Microsoft.Extensions.Http.Resilience --version 8.0.0 | ||
| ``` | ||
|  | ||
| ### [PackageReference](#tab/package-reference) | ||
|  | ||
| ```xml | ||
| <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.0.0" /> | ||
| ``` | ||
|  | ||
| --- | ||
|  | ||
| For more information, see [dotnet add package](../tools/dotnet-add-package.md) or [Manage package dependencies in .NET applications](../tools/dependencies.md). | ||
|  | ||
| ## Add resilience to an HTTP client | ||
|         
                  IEvangelist marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
|  | ||
| To add resilience to an <xref:System.Net.Http.HttpClient>, you chain a call on the <xref:Microsoft.Extensions.DependencyInjection.IHttpClientBuilder> type that is returned from calling any of the available <xref:Microsoft.Extensions.DependencyInjection.HttpClientFactoryServiceCollectionExtensions.AddHttpClient%2A> methods. There are several resilience-centric extensions available, some are standard employing various industry best practices, and others are more customizable. | ||
|  | ||
| While the following examples use the `AddHttpClient` extension method, from the [Microsoft.Extensions.Http](https://www.nuget.org/packages/Microsoft.Extensions.Http). | ||
|  | ||
| ## Add standard resilience handler | ||
|  | ||
| The standard resilience handler uses multiple resilience strategies with default options to send the requests and handle any transient errors. The standard resilience handler is added by calling the `AddStandardResilienceHandler` extension method. | ||
|  | ||
| ```csharp | ||
| // A service collection is manually instantiated for this example. | ||
| // If you're using a generic host, you'll instead use its service collection: | ||
| // | ||
| // var host = Host.CreateApplicationBuilder(args); | ||
| // host.Services.AddHttpClient<ExampleClient>(client => { ... }); | ||
|  | ||
| var services = new ServiceCollection(); | ||
|  | ||
| var builder = services.AddHttpClient<ExampleClient>( | ||
| configureClient: static client => | ||
| { | ||
| client.BaseAddress = new("https://jsonplaceholder.typicode.com"); | ||
| }) | ||
| .AddStandardResilienceHandler(); | ||
| ``` | ||
|  | ||
| The preceding code: | ||
|  | ||
| - Adds an <xref:System.Net.Http.HttpClient> for the `ExampleClient` type to the service container. | ||
| - Configures the <xref:System.Net.Http.HttpClient> to use `"https://jsonplaceholder.typicode.com"` as the base address. | ||
| - Adds the standard resilience handler to the <xref:System.Net.Http.HttpClient>. | ||
| - Declares a `builder` (of type `IHttpStandardResilienceHandlerBuilder`), which is used to configure the standard resilience handler. There are extension methods to configure the standard resilience handler. | ||
|  | ||
| ### Standard resilience handler defaults | ||
|  | ||
| The default configuration chains five resilience strategies in the following order (from the outermost to the innermost): | ||
|  | ||
| | Order | Strategy | Description | | ||
| |--:|--|--| | ||
| | **1** | Rate limiter | The rate limiter pipeline limits the maximum number of concurrent requests being sent to the dependency. | | ||
| | **2** | Total request timeout | The total request timeout pipeline applies an overall timeout to the execution, ensuring that the request, including retry attempts, doesn't exceed the configured limit. | | ||
| | **3** | Retry | The retry pipeline retries the request in case the dependency is slow or returns a transient error. | | ||
| | **4** | Circuit breaker | The circuit breaker blocks the execution if too many direct failures or timeouts are detected. | | ||
| | **5** | Attempt timeout | The attempt timeout pipeline limits each request attempt duration and throws if it's exceeded. | | ||
|  | ||
| ## Add standard hedging handler | ||
|  | ||
| The standard hedging handler wraps the execution of the request with a standard hedging mechanism. Hedging retries slow requests in parallel. The standard hedging handler is added by calling the `AddStandardHedgingHandler` extension method. | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| To use the standard hedging handler, call `AddStandardHedgingHandler` extension method. The following example configures the `ExampleClient` to use the standard hedging handler. | ||
|  | ||
| ```csharp | ||
| // A service collection is manually instantiated for this example. | ||
| // If you're using a generic host, you'll instead use its service collection: | ||
| // | ||
| // var host = Host.CreateApplicationBuilder(args); | ||
| // host.Services.AddHttpClient<ExampleClient>(client => { ... }); | ||
|  | ||
| var services = new ServiceCollection(); | ||
|  | ||
| var builder = services.AddHttpClient<ExampleClient>( | ||
| configureClient: static client => | ||
| { | ||
| client.BaseAddress = new("https://jsonplaceholder.typicode.com"); | ||
| }) | ||
| .AddStandardHedgingHandler(); | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| ``` | ||
|  | ||
| The preceding code: | ||
|  | ||
| - Adds an <xref:System.Net.Http.HttpClient> for the `ExampleClient` type to the service container. | ||
| - Configures the <xref:System.Net.Http.HttpClient> to use `"https://jsonplaceholder.typicode.com"` as the base address. | ||
| - Adds the standard hedging resilience handler to the <xref:System.Net.Http.HttpClient>. | ||
| - Declares a `builder` (of type `IStandardHedgingHandlerBuilder`), which is used to configure the standard hedging resilience handler. There are extension methods to configure the standard hedging resilience handler. | ||
|  | ||
| The `ExampleClient` is defined as follows: | ||
|  | ||
| :::code source="snippets/http-resilience/ExampleClient.cs"::: | ||
|  | ||
| The preceding code: | ||
|  | ||
| - Defines an `ExampleClient` type that has a constructor that accepts an <xref:System.Net.Http.HttpClient>. | ||
| - Exposes a `GetCommentsAsync` method that sends a GET request to the `"/comments"` endpoint and returns the response. | ||
|  | ||
| The `Comment` type is defined as follows: | ||
|  | ||
| :::code source="snippets/http-resilience/Comment.cs"::: | ||
|  | ||
| ### Standard hedging handler defaults | ||
|  | ||
| The standard hedging uses a pool of circuit breakers to ensure that unhealthy endpoints aren't hedged against. By default, the selection from the pool is based on the URL authority (scheme + host + port). | ||
|  | ||
| > [!TIP] | ||
| > It's recommended that you configure the way the strategies are selected by calling `StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority`. | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| The preceding code adds the standard hedging handler to the <xref:Microsoft.Extensions.DependencyInjection.IHttpClientBuilder>. The default configuration chains five resilience strategies in the following order (from the outermost to the innermost): | ||
|  | ||
| | Order | Strategy | Description | | ||
| |--:|--|--| | ||
| | **1** | Total request timeout | The total request timeout pipeline applies an overall timeout to the execution, ensuring that the request, including hedging attempts, doesn't exceed the configured limit. | | ||
| | **2** | Hedging | The hedging strategy executes the requests against multiple endpoints in case the dependency is slow or returns a transient error. Routing is options, by default it just hedges the URL provided by the original <xref:System.Net.Http.HttpRequestMessage>. | | ||
| | **3** | Rate limiter (per endpoint) | The rate limiter pipeline limits the maximum number of concurrent requests being sent to the dependency. | | ||
| | **4** | Circuit breaker (per endpoint) | The circuit breaker blocks the execution if too many direct failures or timeouts are detected. | | ||
| | **5** | Attempt timeout (per endpoint) | The attempt timeout pipeline limits each request attempt duration and throws if it's exceeded. | | ||
|  | ||
| ## Add custom resilience handlers | ||
|  | ||
| For finite control, you can customize the resilience handlers by calling the `AddResilienceHandler` extension method. This method takes a delegate that configures the `ResiliencePipelineBuilder<HttpResponseMessage>` instance that is used to create the resilience strategies. | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| To configure a named resilience handler, call the `AddResilienceHandler` extension method with the name of the handler. The following example configures a named resilience handler called `"CustomPipeline"`. | ||
|  | ||
| ```csharp | ||
| // The builder is of type, IHttpClientBuilder. | ||
| builder.AddResilienceHandler( | ||
| "CustomPipeline", | ||
| static (ResiliencePipelineBuilder<HttpResponseMessage> builder, | ||
| ResilienceHandlerContext context) => | ||
| { | ||
| // See: https://www.pollydocs.org/strategies/retry.html | ||
| builder.AddRetry(new HttpRetryStrategyOptions | ||
| { | ||
| BackoffType = DelayBackoffType.Exponential, | ||
|         
                  martintmk marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| MaxRetryAttempts = 5, | ||
| UseJitter = true | ||
| }); | ||
|  | ||
| // See: https://www.pollydocs.org/strategies/circuit-breaker.html | ||
| builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions | ||
| { | ||
| SamplingDuration = TimeSpan.FromSeconds(10), | ||
| FailureRatio = 0.2, | ||
| MinimumThroughput = 3, | ||
| ShouldHandle = static args => | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| { | ||
| // Handles HTTP status codes 408 and 429. | ||
| return ValueTask.FromResult(args is | ||
| { | ||
| Outcome.Result.StatusCode: | ||
| HttpStatusCode.RequestTimeout or HttpStatusCode.TooManyRequests | ||
| }); | ||
| } | ||
| }); | ||
|  | ||
| // See: https://www.pollydocs.org/strategies/timeout.html | ||
| builder.AddTimeout(TimeSpan.FromSeconds(5)); | ||
| }); | ||
| ``` | ||
|  | ||
| The preceding code: | ||
|  | ||
| - Adds a resilience handler with the name `"CustomPipeline"` as the `pipelineName` to the service container. | ||
| - Adds a retry strategy with exponential backoff, five retries, and jitter preference to the resilience builder. | ||
| - Adds a circuit breaker strategy with a sampling duration of 10 seconds, a failure ratio of 0.2 (20%), a minimum throughput of three, and a predicate that handles `RequestTimeout` and `TooManyRequests` HTTP status codes to the resilience builder. | ||
| - Adds a timeout strategy with a timeout of five seconds to the resilience builder. | ||
|  | ||
| There are many options available for each of the resilience strategies. For more information, see the [Polly docs: Strategies](https://www.pollydocs.org/strategies). | ||
|  | ||
| ### Dynamic reload | ||
|  | ||
| Polly supports dynamic reload of the resilience strategies. This means that you can change the configuration of the resilience strategies at runtime. To enable dynamic reload, use the appropriate `AddResilienceHandler` overload that exposes the `ResilienceHandlerContext`. Given the context, call `EnableReloads` of the corresponding resilience strategy options: | ||
|  | ||
| ```csharp | ||
| builder.AddResilienceHandler( | ||
| "AdvancedPipeline", | ||
| static (ResiliencePipelineBuilder<HttpResponseMessage> builder, | ||
| ResilienceHandlerContext context) => | ||
| { | ||
| // Enable the reloads whenever the named options change | ||
| context.EnableReloads<RetryStrategyOptions>("my-retry-options"); | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| // Retrieve the named options | ||
| var retryOptions = | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| context.ServiceProvider | ||
| .GetRequiredService<IOptionsMonitor<RetryStrategyOptions<HttpResponseMessage>>>() | ||
| .Get("my-retry-options"); | ||
|  | ||
| // Add retries using the resolved options | ||
| builder.AddRetry(retryOptions); | ||
| }); | ||
| ``` | ||
|  | ||
| The preceding code: | ||
|  | ||
| - Adds a resilience handler with the name `"AdvancedPipeline"` as the `pipelineName` to the service container. | ||
| - Enables the reloads of the `RetryStrategyOptions` named `"my-retry-options"` whenever the named options change. | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| - Retrieves the named options from the <xref:Microsoft.Extensions.Options.IOptionsMonitor%601> service. | ||
| - Adds a retry strategy with the retrieved options to the resilience builder. | ||
|  | ||
| For more information, see [Polly docs: Advanced dependency injection](https://www.pollydocs.org/advanced/dependency-injection.html). | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| ## Example usage | ||
|  | ||
| Your app relies on [dependency injection](../extensions/dependency-injection.md) to resolve the `ExampleClient` and its corresponding <xref:System.Net.Http.HttpClient> but, for this example, since the <xref:Microsoft.Extensions.DependencyInjection.ServiceCollection> was manually instantiated, this code builds the <xref:System.IServiceProvider> and resolves the `ExampleClient` from it. | ||
|  | ||
| ```csharp | ||
| var provider = services.BuildServiceProvider(); | ||
|  | ||
| var client = provider.GetRequiredService<ExampleClient>(); | ||
|  | ||
| await foreach (var comment in client.GetCommentsAsync()) | ||
| { | ||
| Console.WriteLine(comment); | ||
| } | ||
| ``` | ||
|  | ||
| The preceding code: | ||
|  | ||
| - Builds the <xref:System.IServiceProvider> from the <xref:Microsoft.Extensions.DependencyInjection.ServiceCollection>. | ||
| - Resolves the `ExampleClient` from the <xref:System.IServiceProvider>. | ||
| - Calls the `GetCommentsAsync` method on the `ExampleClient` to get the comments. | ||
| - Writes each comment to the console. | ||
|  | ||
| <!-- | ||
| Mermaid diagram generated from the following code: | ||
|  | ||
| https://mermaid.live/edit#pako:eNptU01v2zAM_SuEe2mBOAly2FANHZCvtpcCQ5NbswMt0bFWWfIkGkmQ5L9PtpOgCaYTyff4SFHUPpFOUSKS3LiNLNAzLGcrC_GM7z9wg5pBGk2W-y_EU1eW0QzjsLPy94MQYgNPTz9hcp-jyDHNnGF4XS5_wct8CQN5oj90gpOGe-iYa-MySrEkryUGKJirIAaDP8HZyqCkwhlFvs-7SjcN9qPUAab7fZfdhOA15rxTqJwN9EYh4JqOx67StK3UNrJg5DrANGYIGA2HB5idmg1_a_SUyoLkJyxqKaMGDOCduPYWzldtLhnOqmn6P1XrbNoqP5-U2Wu0a0MpbaXBElk7C3PvnY_645pdE5JozK6p5ndNDYK0n16mQ-hhviVZM0VK0M0LSIplPTKtNYXDuOsp6ocwoxw2kGtjxF2e5z9uILpASip1i4YTqh4j_niLZqY-p2ff1bd8dItDYO8-SdwNh8NeZ6cbrbgQo2r7VQ3GkN24UftrZHJNmF67s2v3GbKkl8T9KVGruL_7BlwlXFBJq0REM8MQrZU9Rh7GoS_iziaCfU29pK5UHORM49pjmcSRm3CJzpVm5y9Bat237pe0n-X4DxJYDvk | ||
|  | ||
| --> | ||
|  | ||
| Imagine a situation where the network was to go down, or the server was to become unresponsive. The following diagram shows how the resilience strategies would handle the situation, given the `ExampleClient` and the `GetCommentsAsync` method: | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
|  | ||
| :::image type="content" source="assets/http-get-comments-flow.png" lightbox="assets/http-get-comments-flow.png" alt-text="Example HTTP GET work flow with resilience pipeline."::: | ||
|  | ||
| The preceding diagram depicts: | ||
|  | ||
| - The `ExampleClient` sends an HTTP GET request to the `"/comments"` endpoint. | ||
|         
                  IEvangelist marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| - The <xref:System.Net.Http.HttpResponseMessage> is evaluated: | ||
| - If the response is successful (HTTP 200), the response is returned. | ||
| - If the response is unsuccessful (HTTP non-200), the resilience pipeline employs the configured resilience strategies. | ||
|  | ||
| While this is a simple example, it demonstrates how the resilience strategies can be used to handle transient errors. For more information, see [Polly docs: Strategies](https://www.pollydocs.org/strategies). | ||
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.