From d548d651b781de01f54d4756171beb1ed56e7770 Mon Sep 17 00:00:00 2001 From: Peter Csala Date: Tue, 15 Apr 2025 12:21:37 +0200 Subject: [PATCH 1/5] Add HTTP client integration samples --- .github/wordlist.txt | 4 + Directory.Packages.props | 5 +- docs/community/http-client-integrations.md | 200 ++++++++++++++++++++ docs/community/toc.yml | 2 + src/Snippets/Docs/HttpClientIntegrations.cs | 129 +++++++++++++ src/Snippets/Snippets.csproj | 3 + 6 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 docs/community/http-client-integrations.md create mode 100644 src/Snippets/Docs/HttpClientIntegrations.cs diff --git a/.github/wordlist.txt b/.github/wordlist.txt index fe5f2dacbd8..ca4bec396bb 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -12,6 +12,7 @@ circuitbreaker comparer contrib deserialization +dependencyinjection dotnet dotnetrocks durations @@ -21,6 +22,8 @@ extensibility flurl fs hangfire +httpclient +httpclientfactory interop jetbrains jitter @@ -50,6 +53,7 @@ rebase rebased rebasing resharper +restsharp rethrow rethrows retryable diff --git a/Directory.Packages.props b/Directory.Packages.props index c570377cf69..c30d83d3ae0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,6 +11,7 @@ + @@ -37,7 +38,9 @@ + + @@ -49,4 +52,4 @@ - + \ No newline at end of file diff --git a/docs/community/http-client-integrations.md b/docs/community/http-client-integrations.md new file mode 100644 index 00000000000..39986b10c73 --- /dev/null +++ b/docs/community/http-client-integrations.md @@ -0,0 +1,200 @@ +# HTTP client integration samples + +The transient failures are inevitable for HTTP based communication as well. It is not a surprise that many developers want to use Polly with some HTTP client. + +Here we have collected some of the most commonly used HTTP client libraries and how to integrate them with Polly. + +## Setting the stage + +In the examples below we will register HTTP clients into a Dependency Injection container. + +Each time the same resilience strategy will be used to keep the samples focused on the HTTP client library integration. + + +```cs +private static ValueTask HandleTransientHttpError(Outcome outcome) +=> outcome switch +{ + { Exception: HttpRequestException } => PredicateResult.True(), + { Result.StatusCode: HttpStatusCode.RequestTimeout } => PredicateResult.True(), + { Result.StatusCode: >= HttpStatusCode.InternalServerError } => PredicateResult.True(), + _ => PredicateResult.False() +}; + +private static RetryStrategyOptions GetRetryOptions() +=> new() +{ + ShouldHandle = args => HandleTransientHttpError(args.Outcome), + MaxRetryAttempts = 3, + BackoffType = DelayBackoffType.Exponential, + Delay = TimeSpan.FromSeconds(2) +}; +``` + + +Here we create a strategy which will retry the HTTP request if the status code is either 408 or greater than 500 or an `HttpRequestException` was thrown. + +The `HandleTransientHttpError` is a V8 port of the [`HttpPolicyExtensions.HandleTransientHttpError`](https://github.com/App-vNext/Polly.Extensions.Http/tree/master). + +## HttpClient based + +We use the [`AddResilienceHandler`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.resiliencehttpclientbuilderextensions.addresiliencehandler) method to register our resilience strategy on the built-in [`HttpClient`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient). + + +```cs +ServiceCollection services = new(); + +// Register a named HttpClient and decorate with a resilience pipeline +services.AddHttpClient(string.Empty) + .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + .AddResilienceHandler("httpclient_based_pipeline", + builder => builder.AddRetry(GetRetryOptions())); + +var provider = services.BuildServiceProvider(); + +// Resolve the named HttpClient +var httpClientFactory = provider.GetRequiredService(); +var httpClient = httpClientFactory.CreateClient(); + +// Use the HttpClient by making a request +var response = await httpClient.GetAsync(new Uri("/408")); +``` + + +> [!NOTE] +> The following packages are required to the above example: +> +> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection structures +> - [Microsoft.Extensions.Http](https://www.nuget.org/packages/Microsoft.Extensions.Http/): Required for the `AddHttpClient` extension +> - [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience): Required for the `AddResilienceHandler` extension + +### Further readings for HttpClient + +- [Build resilient HTTP apps: Key development patterns](https://learn.microsoft.com/en-us/dotnet/core/resilience/http-resilience) +- [Building resilient cloud services with .NET 8](https://devblogs.microsoft.com/dotnet/building-resilient-cloud-services-with-dotnet-8/) + +## Flurl based + +The named `HttpClient` registration and its decoration with our resilience strategy are the same as the built-in `HttpClient`. + +Here we create a `FlurlClient` which uses the decorated, named `HttpClient` to perform HTTP communication. + + +```cs +ServiceCollection services = new(); + +// Register a named HttpClient and decorate with a resilience pipeline +services.AddHttpClient(string.Empty) + .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + .AddResilienceHandler("flurl_based_pipeline", + builder => builder.AddRetry(GetRetryOptions())); + +var provider = services.BuildServiceProvider(); + +// Resolve the named HttpClient and create a new FlurlClient +var httpClientFactory = provider.GetRequiredService(); +var apiClient = new FlurlClient(httpClientFactory.CreateClient()); + +// Use the FlurlClient by making a request +var res = await apiClient.Request("/408").GetAsync(); +``` + + +> [!NOTE] +> The following packages are required to the above example: +> +> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection structures +> - [Microsoft.Extensions.Http](https://www.nuget.org/packages/Microsoft.Extensions.Http/): Required for the `AddHttpClient` extension +> - [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience): Required for the `AddResilienceHandler` extension +> - [Flurl.Http](https://www.nuget.org/packages/Flurl.Http/): Required for the `FlurlClient` + +### Further readings for Flurl + +- [Flurl home page](https://flurl.dev/) + +## Refit based + +First let's define the API interface: + + +```cs +public interface IHttpStatusApi +{ + [Get("/408")] + Task GetRequestTimeoutEndpointAsync(); +} +``` + + +Then use the `AddRefitClient` to register the interface as typed HttpClient. Finally call `AddResilienceHandler` to decorate the underlying `HttpClient` with our resilience strategy. + + +```cs +ServiceCollection services = new(); + +// Register a refit generated typed HttpClient and decorate with a resilience pipeline +services.AddRefitClient() + .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + .AddResilienceHandler("refit_based_pipeline", + builder => builder.AddRetry(GetRetryOptions())); + +// Resolve the typed HttpClient +var provider = services.BuildServiceProvider(); +var apiClient = provider.GetRequiredService(); + +// Use the refit generated typed HttpClient by making a request +var response = await apiClient.GetRequestTimeoutEndpointAsync(); +``` + + +> [!NOTE] +> The following packages are required to the above example: +> +> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection structures +> - [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience): Required for the `AddResilienceHandler` extension +> - [Refit.HttpClientFactory](https://www.nuget.org/packages/Refit.HttpClientFactory): Required for the `AddRefitClient` extension + +### Further readings for Refit + +- [Using ASP.NET Core 2.1's HttpClientFactory with Refit's REST library](https://www.hanselman.com/blog/using-aspnet-core-21s-httpclientfactory-with-refits-rest-library) +- [Refit in .NET: Building Robust API Clients in C#](https://www.milanjovanovic.tech/blog/refit-in-dotnet-building-robust-api-clients-in-csharp) +- [Understand the Refit in .NET Core](https://medium.com/@jaimin_99136/understand-the-refit-in-net-core-ba0097c5e620) + +## RestSharp based + +The named `HttpClient` registration and its decoration with our resilience strategy are the same as the built-in `HttpClient`. + +Here we create a `RestClient` which uses the decorated, named `HttpClient` to perform HTTP communication. + + +```cs +ServiceCollection services = new(); + +// Register a named HttpClient and decorate with a resilience pipeline +services.AddHttpClient(string.Empty) + .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + .AddResilienceHandler("restsharp_based_pipeline", + builder => builder.AddRetry(GetRetryOptions())); + +var provider = services.BuildServiceProvider(); + +// Resolve the named HttpClient and create a RestClient +var httpClientFactory = provider.GetRequiredService(); +var restClient = new RestClient(httpClientFactory.CreateClient()); + +// Use the RestClient by making a request +var request = new RestRequest("/408", Method.Get); +var response = await restClient.ExecuteAsync(request); +``` + + +> [!NOTE] +> The following packages are required to the above example: +> +> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection structures +> - [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience): Required for the `AddResilienceHandler` extension +> - [RestSharp](https://www.nuget.org/packages/RestSharp): Required for the `RestClient`, `RestRequest`, `RestResponse`, etc. structures + +### Further readings for RestSharp + +- [RestSharp home page](https://restsharp.dev/) diff --git a/docs/community/toc.yml b/docs/community/toc.yml index 0d91ca86bf3..dd539a2fcd2 100644 --- a/docs/community/toc.yml +++ b/docs/community/toc.yml @@ -8,3 +8,5 @@ href: git-workflow.md - name: Cheat sheets href: cheat-sheets.md +- name: HTTP client integration samples + href: http-client-integrations.md diff --git a/src/Snippets/Docs/HttpClientIntegrations.cs b/src/Snippets/Docs/HttpClientIntegrations.cs new file mode 100644 index 00000000000..a724a317841 --- /dev/null +++ b/src/Snippets/Docs/HttpClientIntegrations.cs @@ -0,0 +1,129 @@ +using System.Net; +using System.Net.Http; +using Flurl.Http; +using Microsoft.Extensions.DependencyInjection; +using Polly.Retry; +using Refit; +using RestSharp; + +namespace Snippets.Docs; + +internal static class HttpClientIntegrations +{ + private static readonly Uri DownstreamUri = new("https://httpstat.us/408"); + + #region http-client-integrations-handle-transient-errors + private static ValueTask HandleTransientHttpError(Outcome outcome) + => outcome switch + { + { Exception: HttpRequestException } => PredicateResult.True(), + { Result.StatusCode: HttpStatusCode.RequestTimeout } => PredicateResult.True(), + { Result.StatusCode: >= HttpStatusCode.InternalServerError } => PredicateResult.True(), + _ => PredicateResult.False() + }; + + private static RetryStrategyOptions GetRetryOptions() + => new() + { + ShouldHandle = args => HandleTransientHttpError(args.Outcome), + MaxRetryAttempts = 3, + BackoffType = DelayBackoffType.Exponential, + Delay = TimeSpan.FromSeconds(2) + }; + #endregion + + public static async Task HttpClientExample() + { + #region http-client-integrations-httpclient + ServiceCollection services = new(); + + // Register a named HttpClient and decorate with a resilience pipeline + services.AddHttpClient(string.Empty) + .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + .AddResilienceHandler("httpclient_based_pipeline", + builder => builder.AddRetry(GetRetryOptions())); + + var provider = services.BuildServiceProvider(); + + // Resolve the named HttpClient + var httpClientFactory = provider.GetRequiredService(); + var httpClient = httpClientFactory.CreateClient(); + + // Use the HttpClient by making a request + var response = await httpClient.GetAsync(new Uri("/408")); + #endregion + } + + public static async Task RefitExample() + { + #region http-client-integrations-refit + ServiceCollection services = new(); + + // Register a refit generated typed HttpClient and decorate with a resilience pipeline + services.AddRefitClient() + .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + .AddResilienceHandler("refit_based_pipeline", + builder => builder.AddRetry(GetRetryOptions())); + + // Resolve the typed HttpClient + var provider = services.BuildServiceProvider(); + var apiClient = provider.GetRequiredService(); + + // Use the refit generated typed HttpClient by making a request + var response = await apiClient.GetRequestTimeoutEndpointAsync(); + #endregion + } + + public static async Task FlurlExample() + { + #region http-client-integrations-flurl + ServiceCollection services = new(); + + // Register a named HttpClient and decorate with a resilience pipeline + services.AddHttpClient(string.Empty) + .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + .AddResilienceHandler("flurl_based_pipeline", + builder => builder.AddRetry(GetRetryOptions())); + + var provider = services.BuildServiceProvider(); + + // Resolve the named HttpClient and create a new FlurlClient + var httpClientFactory = provider.GetRequiredService(); + var apiClient = new FlurlClient(httpClientFactory.CreateClient()); + + // Use the FlurlClient by making a request + var res = await apiClient.Request("/408").GetAsync(); + #endregion + } + + public static async Task RestSharpExample() + { + #region http-client-integrations-restsharp + ServiceCollection services = new(); + + // Register a named HttpClient and decorate with a resilience pipeline + services.AddHttpClient(string.Empty) + .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + .AddResilienceHandler("restsharp_based_pipeline", + builder => builder.AddRetry(GetRetryOptions())); + + var provider = services.BuildServiceProvider(); + + // Resolve the named HttpClient and create a RestClient + var httpClientFactory = provider.GetRequiredService(); + var restClient = new RestClient(httpClientFactory.CreateClient()); + + // Use the RestClient by making a request + var request = new RestRequest("/408", Method.Get); + var response = await restClient.ExecuteAsync(request); + #endregion + } +} + +#region http-client-integrations-refit-interface +public interface IHttpStatusApi +{ + [Get("/408")] + Task GetRequestTimeoutEndpointAsync(); +} +#endregion diff --git a/src/Snippets/Snippets.csproj b/src/Snippets/Snippets.csproj index a5432022f89..759b81122b7 100644 --- a/src/Snippets/Snippets.csproj +++ b/src/Snippets/Snippets.csproj @@ -22,6 +22,9 @@ + + + From 742a57b5f8265cc440e498b73fb21a9ebf45cf36 Mon Sep 17 00:00:00 2001 From: peter-csala <57183693+peter-csala@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:13:12 +0200 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Martin Costello --- Directory.Packages.props | 6 +-- docs/community/http-client-integrations.md | 55 ++++++++++++---------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c30d83d3ae0..4d116a52045 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + @@ -38,9 +38,9 @@ - + - + diff --git a/docs/community/http-client-integrations.md b/docs/community/http-client-integrations.md index 39986b10c73..ca74cb62287 100644 --- a/docs/community/http-client-integrations.md +++ b/docs/community/http-client-integrations.md @@ -1,14 +1,14 @@ # HTTP client integration samples -The transient failures are inevitable for HTTP based communication as well. It is not a surprise that many developers want to use Polly with some HTTP client. +Transient failures are inevitable for HTTP based communication as well. It is not a surprise that many developers use Polly with an HTTP client to make there applications more robust. -Here we have collected some of the most commonly used HTTP client libraries and how to integrate them with Polly. +Here we have collected some popular HTTP client libraries and show how to integrate them with Polly. ## Setting the stage In the examples below we will register HTTP clients into a Dependency Injection container. -Each time the same resilience strategy will be used to keep the samples focused on the HTTP client library integration. +The same resilience strategy will be used each time to keep the samples focused on the HTTP client library integration. ```cs @@ -32,13 +32,13 @@ private static RetryStrategyOptions GetRetryOptions() ``` -Here we create a strategy which will retry the HTTP request if the status code is either 408 or greater than 500 or an `HttpRequestException` was thrown. +Here we create a strategy which will retry the HTTP request if the status code is either `408`, greater than or equal to `500`, or an `HttpRequestException` is thrown. -The `HandleTransientHttpError` is a V8 port of the [`HttpPolicyExtensions.HandleTransientHttpError`](https://github.com/App-vNext/Polly.Extensions.Http/tree/master). +The `HandleTransientHttpError` is method is equivalent to the [`HttpPolicyExtensions.HandleTransientHttpError`](https://github.com/App-vNext/Polly.Extensions.Http/blob/93b91c4359f436bda37f870c4453f25555b9bfd8/src/Polly.Extensions.Http/HttpPolicyExtensions.cs) method in the [App-vNext/Polly.Extensions.Http](https://github.com/App-vNext/Polly.Extensions.Http) repository. -## HttpClient based +## With HttpClient -We use the [`AddResilienceHandler`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.resiliencehttpclientbuilderextensions.addresiliencehandler) method to register our resilience strategy on the built-in [`HttpClient`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient). +We use the [`AddResilienceHandler`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.resiliencehttpclientbuilderextensions.addresiliencehandler) method to register our resilience strategy with the built-in [`HttpClient`](https://learn.microsoft.com/dotnet/api/system.net.http.httpclient). ```cs @@ -64,20 +64,21 @@ var response = await httpClient.GetAsync(new Uri("/408")); > [!NOTE] > The following packages are required to the above example: > -> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection structures -> - [Microsoft.Extensions.Http](https://www.nuget.org/packages/Microsoft.Extensions.Http/): Required for the `AddHttpClient` extension +> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection functionality > - [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience): Required for the `AddResilienceHandler` extension -### Further readings for HttpClient +### Further reading for HttpClient -- [Build resilient HTTP apps: Key development patterns](https://learn.microsoft.com/en-us/dotnet/core/resilience/http-resilience) +- [Build resilient HTTP apps: Key development patterns](https://learn.microsoft.com/dotnet/core/resilience/http-resilience) - [Building resilient cloud services with .NET 8](https://devblogs.microsoft.com/dotnet/building-resilient-cloud-services-with-dotnet-8/) -## Flurl based +## With Flurl + +[Flurl](https://flurl.dev/) is a URL builder and HTTP client library for .NET. The named `HttpClient` registration and its decoration with our resilience strategy are the same as the built-in `HttpClient`. -Here we create a `FlurlClient` which uses the decorated, named `HttpClient` to perform HTTP communication. +Here we create a `FlurlClient` which uses the decorated, named `HttpClient` for HTTP requests. ```cs @@ -103,16 +104,17 @@ var res = await apiClient.Request("/408").GetAsync(); > [!NOTE] > The following packages are required to the above example: > -> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection structures -> - [Microsoft.Extensions.Http](https://www.nuget.org/packages/Microsoft.Extensions.Http/): Required for the `AddHttpClient` extension +> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection functionality > - [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience): Required for the `AddResilienceHandler` extension > - [Flurl.Http](https://www.nuget.org/packages/Flurl.Http/): Required for the `FlurlClient` -### Further readings for Flurl +### Further reading for Flurl - [Flurl home page](https://flurl.dev/) -## Refit based +## With Refit + +[Refit](https://github.com/reactiveui/refit) is an automatic type-safe REST library for .NET. First let's define the API interface: @@ -126,7 +128,7 @@ public interface IHttpStatusApi ``` -Then use the `AddRefitClient` to register the interface as typed HttpClient. Finally call `AddResilienceHandler` to decorate the underlying `HttpClient` with our resilience strategy. +Then use the `AddRefitClient` method to register the interface as a typed `HttpClient`. Finally we call `AddResilienceHandler` to decorate the underlying `HttpClient` with our resilience strategy. ```cs @@ -150,21 +152,22 @@ var response = await apiClient.GetRequestTimeoutEndpointAsync(); > [!NOTE] > The following packages are required to the above example: > -> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection structures -> - [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience): Required for the `AddResilienceHandler` extension +> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection functionality > - [Refit.HttpClientFactory](https://www.nuget.org/packages/Refit.HttpClientFactory): Required for the `AddRefitClient` extension ### Further readings for Refit - [Using ASP.NET Core 2.1's HttpClientFactory with Refit's REST library](https://www.hanselman.com/blog/using-aspnet-core-21s-httpclientfactory-with-refits-rest-library) - [Refit in .NET: Building Robust API Clients in C#](https://www.milanjovanovic.tech/blog/refit-in-dotnet-building-robust-api-clients-in-csharp) -- [Understand the Refit in .NET Core](https://medium.com/@jaimin_99136/understand-the-refit-in-net-core-ba0097c5e620) +- [Understand Refit in .NET Core](https://medium.com/@jaimin_99136/understand-the-refit-in-net-core-ba0097c5e620) + +## With RestSharp -## RestSharp based +[RestSharp](https://restsharp.dev/) is a simple REST and HTTP API Client for .NET. The named `HttpClient` registration and its decoration with our resilience strategy are the same as the built-in `HttpClient`. -Here we create a `RestClient` which uses the decorated, named `HttpClient` to perform HTTP communication. +Here we create a `RestClient` which uses the decorated, named `HttpClient` for HTTP requests. ```cs @@ -191,10 +194,10 @@ var response = await restClient.ExecuteAsync(request); > [!NOTE] > The following packages are required to the above example: > -> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection structures +> - [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/microsoft.extensions.dependencyinjection): Required for the dependency injection functionality > - [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience): Required for the `AddResilienceHandler` extension -> - [RestSharp](https://www.nuget.org/packages/RestSharp): Required for the `RestClient`, `RestRequest`, `RestResponse`, etc. structures +> - [RestSharp](https://www.nuget.org/packages/RestSharp): Required for the `RestClient`, `RestRequest`, `RestResponse`, etc. types -### Further readings for RestSharp +### Further reading for RestSharp - [RestSharp home page](https://restsharp.dev/) From 531ed8fde15af8cbe8debefd087d28f6bec6c873 Mon Sep 17 00:00:00 2001 From: Peter Csala Date: Thu, 17 Apr 2025 15:24:17 +0200 Subject: [PATCH 3/5] Apply review suggestions --- Directory.Packages.props | 2 +- docs/community/http-client-integrations.md | 52 +++++++++---------- src/Snippets/Docs/HttpClientIntegrations.cs | 57 +++++++++++---------- 3 files changed, 57 insertions(+), 54 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 4d116a52045..f335137a768 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -52,4 +52,4 @@ - \ No newline at end of file + diff --git a/docs/community/http-client-integrations.md b/docs/community/http-client-integrations.md index ca74cb62287..d58e002bd11 100644 --- a/docs/community/http-client-integrations.md +++ b/docs/community/http-client-integrations.md @@ -12,8 +12,8 @@ The same resilience strategy will be used each time to keep the samples focused ```cs -private static ValueTask HandleTransientHttpError(Outcome outcome) -=> outcome switch +private static ValueTask HandleTransientHttpError(Outcome outcome) => +outcome switch { { Exception: HttpRequestException } => PredicateResult.True(), { Result.StatusCode: HttpStatusCode.RequestTimeout } => PredicateResult.True(), @@ -21,8 +21,8 @@ private static ValueTask HandleTransientHttpError(Outcome PredicateResult.False() }; -private static RetryStrategyOptions GetRetryOptions() -=> new() +private static RetryStrategyOptions GetRetryOptions() => +new() { ShouldHandle = args => HandleTransientHttpError(args.Outcome), MaxRetryAttempts = 3, @@ -42,22 +42,22 @@ We use the [`AddResilienceHandler`](https://learn.microsoft.com/dotnet/api/micro ```cs -ServiceCollection services = new(); +var services = new ServiceCollection(); // Register a named HttpClient and decorate with a resilience pipeline -services.AddHttpClient(string.Empty) - .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) +services.AddHttpClient(HttpClientName) + .ConfigureHttpClient(client => client.BaseAddress = BaseAddress) .AddResilienceHandler("httpclient_based_pipeline", builder => builder.AddRetry(GetRetryOptions())); -var provider = services.BuildServiceProvider(); +using var provider = services.BuildServiceProvider(); // Resolve the named HttpClient var httpClientFactory = provider.GetRequiredService(); -var httpClient = httpClientFactory.CreateClient(); +var httpClient = httpClientFactory.CreateClient(HttpClientName); // Use the HttpClient by making a request -var response = await httpClient.GetAsync(new Uri("/408")); +var response = await httpClient.GetAsync("/408"); ``` @@ -82,22 +82,22 @@ Here we create a `FlurlClient` which uses the decorated, named `HttpClient` for ```cs -ServiceCollection services = new(); +var services = new ServiceCollection(); // Register a named HttpClient and decorate with a resilience pipeline -services.AddHttpClient(string.Empty) - .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) +services.AddHttpClient(HttpClientName) + .ConfigureHttpClient(client => client.BaseAddress = BaseAddress) .AddResilienceHandler("flurl_based_pipeline", builder => builder.AddRetry(GetRetryOptions())); -var provider = services.BuildServiceProvider(); +using var provider = services.BuildServiceProvider(); // Resolve the named HttpClient and create a new FlurlClient var httpClientFactory = provider.GetRequiredService(); -var apiClient = new FlurlClient(httpClientFactory.CreateClient()); +var flurlClient = new FlurlClient(httpClientFactory.CreateClient(HttpClientName)); // Use the FlurlClient by making a request -var res = await apiClient.Request("/408").GetAsync(); +var response = await flurlClient.Request("/408").GetAsync(); ``` @@ -132,19 +132,19 @@ Then use the `AddRefitClient` method to register the interface as a typed `HttpC ```cs -ServiceCollection services = new(); +var services = new ServiceCollection(); -// Register a refit generated typed HttpClient and decorate with a resilience pipeline +// Register a Refit generated typed HttpClient and decorate with a resilience pipeline services.AddRefitClient() - .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + .ConfigureHttpClient(client => client.BaseAddress = BaseAddress) .AddResilienceHandler("refit_based_pipeline", builder => builder.AddRetry(GetRetryOptions())); // Resolve the typed HttpClient -var provider = services.BuildServiceProvider(); +using var provider = services.BuildServiceProvider(); var apiClient = provider.GetRequiredService(); -// Use the refit generated typed HttpClient by making a request +// Use the Refit generated typed HttpClient by making a request var response = await apiClient.GetRequestTimeoutEndpointAsync(); ``` @@ -171,19 +171,19 @@ Here we create a `RestClient` which uses the decorated, named `HttpClient` for H ```cs -ServiceCollection services = new(); +var services = new ServiceCollection(); // Register a named HttpClient and decorate with a resilience pipeline -services.AddHttpClient(string.Empty) - .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) +services.AddHttpClient(HttpClientName) + .ConfigureHttpClient(client => client.BaseAddress = BaseAddress) .AddResilienceHandler("restsharp_based_pipeline", builder => builder.AddRetry(GetRetryOptions())); -var provider = services.BuildServiceProvider(); +using var provider = services.BuildServiceProvider(); // Resolve the named HttpClient and create a RestClient var httpClientFactory = provider.GetRequiredService(); -var restClient = new RestClient(httpClientFactory.CreateClient()); +var restClient = new RestClient(httpClientFactory.CreateClient(HttpClientName)); // Use the RestClient by making a request var request = new RestRequest("/408", Method.Get); diff --git a/src/Snippets/Docs/HttpClientIntegrations.cs b/src/Snippets/Docs/HttpClientIntegrations.cs index a724a317841..7f23eaa7193 100644 --- a/src/Snippets/Docs/HttpClientIntegrations.cs +++ b/src/Snippets/Docs/HttpClientIntegrations.cs @@ -10,11 +10,12 @@ namespace Snippets.Docs; internal static class HttpClientIntegrations { - private static readonly Uri DownstreamUri = new("https://httpstat.us/408"); + private const string HttpClientName = "httpclient"; + private static readonly Uri BaseAddress = new("https://httpstat.us/"); #region http-client-integrations-handle-transient-errors - private static ValueTask HandleTransientHttpError(Outcome outcome) - => outcome switch + private static ValueTask HandleTransientHttpError(Outcome outcome) => + outcome switch { { Exception: HttpRequestException } => PredicateResult.True(), { Result.StatusCode: HttpStatusCode.RequestTimeout } => PredicateResult.True(), @@ -22,8 +23,8 @@ private static ValueTask HandleTransientHttpError(Outcome PredicateResult.False() }; - private static RetryStrategyOptions GetRetryOptions() - => new() + private static RetryStrategyOptions GetRetryOptions() => + new() { ShouldHandle = args => HandleTransientHttpError(args.Outcome), MaxRetryAttempts = 3, @@ -32,44 +33,46 @@ private static RetryStrategyOptions GetRetryOptions() }; #endregion +#pragma warning disable CA2234 public static async Task HttpClientExample() { #region http-client-integrations-httpclient - ServiceCollection services = new(); + var services = new ServiceCollection(); // Register a named HttpClient and decorate with a resilience pipeline - services.AddHttpClient(string.Empty) - .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + services.AddHttpClient(HttpClientName) + .ConfigureHttpClient(client => client.BaseAddress = BaseAddress) .AddResilienceHandler("httpclient_based_pipeline", builder => builder.AddRetry(GetRetryOptions())); - var provider = services.BuildServiceProvider(); + using var provider = services.BuildServiceProvider(); // Resolve the named HttpClient var httpClientFactory = provider.GetRequiredService(); - var httpClient = httpClientFactory.CreateClient(); + var httpClient = httpClientFactory.CreateClient(HttpClientName); // Use the HttpClient by making a request - var response = await httpClient.GetAsync(new Uri("/408")); + var response = await httpClient.GetAsync("/408"); #endregion } +#pragma warning restore CA2234 public static async Task RefitExample() { #region http-client-integrations-refit - ServiceCollection services = new(); + var services = new ServiceCollection(); - // Register a refit generated typed HttpClient and decorate with a resilience pipeline + // Register a Refit generated typed HttpClient and decorate with a resilience pipeline services.AddRefitClient() - .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + .ConfigureHttpClient(client => client.BaseAddress = BaseAddress) .AddResilienceHandler("refit_based_pipeline", builder => builder.AddRetry(GetRetryOptions())); // Resolve the typed HttpClient - var provider = services.BuildServiceProvider(); + using var provider = services.BuildServiceProvider(); var apiClient = provider.GetRequiredService(); - // Use the refit generated typed HttpClient by making a request + // Use the Refit generated typed HttpClient by making a request var response = await apiClient.GetRequestTimeoutEndpointAsync(); #endregion } @@ -77,41 +80,41 @@ public static async Task RefitExample() public static async Task FlurlExample() { #region http-client-integrations-flurl - ServiceCollection services = new(); + var services = new ServiceCollection(); // Register a named HttpClient and decorate with a resilience pipeline - services.AddHttpClient(string.Empty) - .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + services.AddHttpClient(HttpClientName) + .ConfigureHttpClient(client => client.BaseAddress = BaseAddress) .AddResilienceHandler("flurl_based_pipeline", builder => builder.AddRetry(GetRetryOptions())); - var provider = services.BuildServiceProvider(); + using var provider = services.BuildServiceProvider(); // Resolve the named HttpClient and create a new FlurlClient var httpClientFactory = provider.GetRequiredService(); - var apiClient = new FlurlClient(httpClientFactory.CreateClient()); + var flurlClient = new FlurlClient(httpClientFactory.CreateClient(HttpClientName)); // Use the FlurlClient by making a request - var res = await apiClient.Request("/408").GetAsync(); + var response = await flurlClient.Request("/408").GetAsync(); #endregion } public static async Task RestSharpExample() { #region http-client-integrations-restsharp - ServiceCollection services = new(); + var services = new ServiceCollection(); // Register a named HttpClient and decorate with a resilience pipeline - services.AddHttpClient(string.Empty) - .ConfigureHttpClient(client => client.BaseAddress = DownstreamUri) + services.AddHttpClient(HttpClientName) + .ConfigureHttpClient(client => client.BaseAddress = BaseAddress) .AddResilienceHandler("restsharp_based_pipeline", builder => builder.AddRetry(GetRetryOptions())); - var provider = services.BuildServiceProvider(); + using var provider = services.BuildServiceProvider(); // Resolve the named HttpClient and create a RestClient var httpClientFactory = provider.GetRequiredService(); - var restClient = new RestClient(httpClientFactory.CreateClient()); + var restClient = new RestClient(httpClientFactory.CreateClient(HttpClientName)); // Use the RestClient by making a request var request = new RestRequest("/408", Method.Get); From f361af66378fb8c9d1dc9a3785573705739f1965 Mon Sep 17 00:00:00 2001 From: Peter Csala Date: Thu, 17 Apr 2025 15:30:10 +0200 Subject: [PATCH 4/5] Apply review suggestions --- docs/community/http-client-integrations.md | 2 +- src/Snippets/Snippets.csproj | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/community/http-client-integrations.md b/docs/community/http-client-integrations.md index d58e002bd11..57843f628cc 100644 --- a/docs/community/http-client-integrations.md +++ b/docs/community/http-client-integrations.md @@ -34,7 +34,7 @@ new() Here we create a strategy which will retry the HTTP request if the status code is either `408`, greater than or equal to `500`, or an `HttpRequestException` is thrown. -The `HandleTransientHttpError` is method is equivalent to the [`HttpPolicyExtensions.HandleTransientHttpError`](https://github.com/App-vNext/Polly.Extensions.Http/blob/93b91c4359f436bda37f870c4453f25555b9bfd8/src/Polly.Extensions.Http/HttpPolicyExtensions.cs) method in the [App-vNext/Polly.Extensions.Http](https://github.com/App-vNext/Polly.Extensions.Http) repository. +The `HandleTransientHttpError` method is equivalent to the [`HttpPolicyExtensions.HandleTransientHttpError`](https://github.com/App-vNext/Polly.Extensions.Http/blob/93b91c4359f436bda37f870c4453f25555b9bfd8/src/Polly.Extensions.Http/HttpPolicyExtensions.cs) method in the [App-vNext/Polly.Extensions.Http](https://github.com/App-vNext/Polly.Extensions.Http) repository. ## With HttpClient diff --git a/src/Snippets/Snippets.csproj b/src/Snippets/Snippets.csproj index 759b81122b7..78f29eae35e 100644 --- a/src/Snippets/Snippets.csproj +++ b/src/Snippets/Snippets.csproj @@ -22,9 +22,9 @@ - - - + + + From 5695a95501506706b371fd154643ccbf2fb5a8dd Mon Sep 17 00:00:00 2001 From: Peter Csala Date: Thu, 17 Apr 2025 15:32:57 +0200 Subject: [PATCH 5/5] Fix linting issue --- .github/wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/wordlist.txt b/.github/wordlist.txt index ca4bec396bb..d199af4bbdd 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -77,6 +77,7 @@ ui unhandled uwp valuetask +vnext waitandretry wpf xunit