diff --git a/samples/Chaos/Chaos.csproj b/samples/Chaos/Chaos.csproj
new file mode 100644
index 0000000000..829f96fb11
--- /dev/null
+++ b/samples/Chaos/Chaos.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Chaos
+
+
+
+
+
+
+
+
diff --git a/samples/Chaos/ChaosManager.cs b/samples/Chaos/ChaosManager.cs
new file mode 100644
index 0000000000..862d45cc4a
--- /dev/null
+++ b/samples/Chaos/ChaosManager.cs
@@ -0,0 +1,45 @@
+using Polly;
+
+namespace Chaos;
+
+internal class ChaosManager(IWebHostEnvironment environment, IHttpContextAccessor contextAccessor) : IChaosManager
+{
+ private const string UserQueryParam = "user";
+
+ private const string TestUser = "test";
+
+ public ValueTask IsChaosEnabledAsync(ResilienceContext context)
+ {
+ if (environment.IsDevelopment())
+ {
+ return ValueTask.FromResult(true);
+ }
+
+ // This condition is demonstrative and not recommended to use in real apps.
+ if (environment.IsProduction() &&
+ contextAccessor.HttpContext is { } httpContext &&
+ httpContext.Request.Query.TryGetValue(UserQueryParam, out var values) &&
+ values == TestUser)
+ {
+ // Enable chaos for 'test' user even in production
+ return ValueTask.FromResult(true);
+ }
+
+ return ValueTask.FromResult(false);
+ }
+
+ public ValueTask GetInjectionRateAsync(ResilienceContext context)
+ {
+ if (environment.IsDevelopment())
+ {
+ return ValueTask.FromResult(0.05);
+ }
+
+ if (environment.IsProduction())
+ {
+ return ValueTask.FromResult(0.03);
+ }
+
+ return ValueTask.FromResult(0.0);
+ }
+}
\ No newline at end of file
diff --git a/samples/Chaos/IChaosManager.cs b/samples/Chaos/IChaosManager.cs
new file mode 100644
index 0000000000..86887373cc
--- /dev/null
+++ b/samples/Chaos/IChaosManager.cs
@@ -0,0 +1,13 @@
+using Polly;
+
+namespace Chaos;
+
+///
+/// Abstraction for controlling chaos injection.
+///
+public interface IChaosManager
+{
+ ValueTask IsChaosEnabledAsync(ResilienceContext context);
+
+ ValueTask GetInjectionRateAsync(ResilienceContext context);
+}
\ No newline at end of file
diff --git a/samples/Chaos/Program.cs b/samples/Chaos/Program.cs
new file mode 100644
index 0000000000..78a396e73a
--- /dev/null
+++ b/samples/Chaos/Program.cs
@@ -0,0 +1,77 @@
+using Chaos;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Http.Resilience;
+using Polly;
+using Polly.Simmy;
+using Polly.Simmy.Fault;
+using Polly.Simmy.Latency;
+using Polly.Simmy.Outcomes;
+
+var builder = WebApplication.CreateBuilder(args);
+var services = builder.Services;
+
+services.TryAddSingleton();
+services.AddHttpContextAccessor();
+
+var httpClientBuilder = services.AddHttpClient(client => client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com"));
+
+// Configure the standard resilience handler
+httpClientBuilder
+ .AddStandardResilienceHandler()
+ .Configure(options =>
+ {
+ // Update attempt timeout to 1 second
+ options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(1);
+
+ // Update circuit breaker to handle transient errors and InvalidOperationException
+ options.CircuitBreaker.ShouldHandle = args => args.Outcome switch
+ {
+ {} outcome when HttpClientResiliencePredicates.IsTransient(outcome) => PredicateResult.True(),
+ { Exception: InvalidOperationException } => PredicateResult.True(),
+ _ => PredicateResult.False()
+ };
+
+ // Update retry strategy to handle transient errors and InvalidOperationException
+ options.Retry.ShouldHandle = args => args.Outcome switch
+ {
+ {} outcome when HttpClientResiliencePredicates.IsTransient(outcome) => PredicateResult.True(),
+ { Exception: InvalidOperationException } => PredicateResult.True(),
+ _ => PredicateResult.False()
+ };
+ });
+
+// Configure the chaos injection
+httpClientBuilder.AddResilienceHandler("chaos", (builder, context) =>
+{
+ // Get IChaosManager from dependency injection
+ var chaosManager = context.ServiceProvider.GetRequiredService();
+
+ builder
+ .AddChaosLatency(new ChaosLatencyStrategyOptions
+ {
+ EnabledGenerator = args => chaosManager.IsChaosEnabledAsync(args.Context),
+ InjectionRateGenerator = args => chaosManager.GetInjectionRateAsync(args.Context),
+ Latency = TimeSpan.FromSeconds(5)
+ })
+ .AddChaosFault(new ChaosFaultStrategyOptions
+ {
+ EnabledGenerator = args => chaosManager.IsChaosEnabledAsync(args.Context),
+ InjectionRateGenerator = args => chaosManager.GetInjectionRateAsync(args.Context),
+ FaultGenerator = new FaultGenerator().AddException(() => new InvalidOperationException("Chaos strategy injection!"))
+ })
+ .AddChaosOutcome(new ChaosOutcomeStrategyOptions
+ {
+ EnabledGenerator = args => chaosManager.IsChaosEnabledAsync(args.Context),
+ InjectionRateGenerator = args => chaosManager.GetInjectionRateAsync(args.Context),
+ OutcomeGenerator = new OutcomeGenerator().AddResult(() => new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError))
+ });
+});
+
+// Run the app
+var app = builder.Build();
+app.MapGet("/", async (TodosClient client, HttpContext httpContext, CancellationToken cancellationToken) =>
+{
+ return await client.GetTodosAsync(cancellationToken);
+});
+
+app.Run();
diff --git a/samples/Chaos/Properties/launchSettings.json b/samples/Chaos/Properties/launchSettings.json
new file mode 100644
index 0000000000..05b07f84ba
--- /dev/null
+++ b/samples/Chaos/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "Chaos": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:62683;http://localhost:62684"
+ }
+ }
+}
diff --git a/samples/Chaos/README.md b/samples/Chaos/README.md
new file mode 100644
index 0000000000..909bc97915
--- /dev/null
+++ b/samples/Chaos/README.md
@@ -0,0 +1,10 @@
+# Chaos Example
+
+This example demonstrates how to use new [chaos engineering](https://www.pollydocs.org/chaos) tools in Polly to inject chaos into HTTP client communication.
+The HTTP client communicates with the `https://jsonplaceholder.typicode.com/todos` endpoint.
+
+To test the application:
+
+- Run the app using the `dotnet run` command.
+- Access the root endpoint `https://localhost:62683` and refresh it multiple times.
+- Observe the logs in out console window. You should see chaos injection and also mitigation of chaos by resilience strategies.
diff --git a/samples/Chaos/TodoModel.cs b/samples/Chaos/TodoModel.cs
new file mode 100644
index 0000000000..c973fa1949
--- /dev/null
+++ b/samples/Chaos/TodoModel.cs
@@ -0,0 +1,7 @@
+using System.Text.Json.Serialization;
+
+namespace Chaos;
+
+public record TodoModel(
+ [property: JsonPropertyName("id")] int Id,
+ [property: JsonPropertyName("title")] string Title);
\ No newline at end of file
diff --git a/samples/Chaos/TodosClient.cs b/samples/Chaos/TodosClient.cs
new file mode 100644
index 0000000000..6fb0fbb741
--- /dev/null
+++ b/samples/Chaos/TodosClient.cs
@@ -0,0 +1,7 @@
+namespace Chaos;
+
+public class TodosClient(HttpClient client)
+{
+ public async Task> GetTodosAsync(CancellationToken cancellationToken)
+ => await client.GetFromJsonAsync>("/todos", cancellationToken) ?? [];
+}
diff --git a/samples/Chaos/appsettings.Development.json b/samples/Chaos/appsettings.Development.json
new file mode 100644
index 0000000000..0c208ae918
--- /dev/null
+++ b/samples/Chaos/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/samples/Chaos/appsettings.json b/samples/Chaos/appsettings.json
new file mode 100644
index 0000000000..10f68b8c8b
--- /dev/null
+++ b/samples/Chaos/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/samples/README.md b/samples/README.md
index 56e59f13ce..4aa2934c25 100644
--- a/samples/README.md
+++ b/samples/README.md
@@ -7,5 +7,6 @@ This repository contains a solution with basic examples demonstrating the creati
- [`Retries`](./Retries) - This part explains how to configure a retry resilience strategy.
- [`Extensibility`](./Extensibility) - In this part, you can learn how Polly can be extended with custom resilience strategies.
- [`DependencyInjection`](./DependencyInjection) - This section demonstrates the integration of Polly with `IServiceCollection`.
+- [`Chaos`](./Chaos) - Simple web application that communicates with an external service using HTTP client. It uses chaos strategies to inject chaos into HTTP client calls.
These examples are designed as a quick-start guide to Polly. If you wish to explore more advanced scenarios and further enhance your learning, consider visiting the [Polly-Samples](https://github.com/App-vNext/Polly-Samples) repository.
diff --git a/samples/Samples.sln b/samples/Samples.sln
index 969194a8de..ade0ec3fc6 100644
--- a/samples/Samples.sln
+++ b/samples/Samples.sln
@@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
+ ..\Directory.Packages.props = ..\Directory.Packages.props
README.md = README.md
EndProjectSection
EndProject
@@ -21,6 +22,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Retries", "Retries\Retries.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjection", "DependencyInjection\DependencyInjection.csproj", "{9B8BFE03-4457-4C55-91AD-4096DDE622C3}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chaos", "Chaos\Chaos.csproj", "{A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -47,6 +50,10 @@ Global
{9B8BFE03-4457-4C55-91AD-4096DDE622C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B8BFE03-4457-4C55-91AD-4096DDE622C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B8BFE03-4457-4C55-91AD-4096DDE622C3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A296E17C-B95F-4B15-8B0D-9D6CC0929A1D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE