diff --git a/dotnet-client-libraries/README.md b/dotnet-client-libraries/README.md
index ada6c460e..1f5b8dfac 100644
--- a/dotnet-client-libraries/README.md
+++ b/dotnet-client-libraries/README.md
@@ -58,3 +58,4 @@ This section contains the topics about the client libraries in .NET.
- [Multi-Source Data Integration With Strawberry Shake Subscriptions](https://code-maze.com/dotnetcore-multi-source-data-integration-with-strawberry-shake-subscriptions/)
- [Introduction to the Wolverine Library in .NET](https://code-maze.com/dotnet-wolverine-library/)
- [Comparison of Rebus, NServiceBus, and MassTransit in .NET](https://code-maze.com/aspnetcore-comparison-of-rebus-nservicebus-and-masstransit/)
+- [Polly in .NET: Retry, Circuit Breaker, and Fallback](https://code-maze.com/creating-resilient-microservices-in-net-with-polly/)
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/AuthorsService.csproj b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/AuthorsService.csproj
new file mode 100644
index 000000000..c5245308a
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/AuthorsService.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Controllers/AuthorsController.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Controllers/AuthorsController.cs
new file mode 100644
index 000000000..4dba469a2
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Controllers/AuthorsController.cs
@@ -0,0 +1,13 @@
+using AuthorsService.Data;
+using AuthorsService.Models;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AuthorsService.Controllers;
+
+[ApiController]
+[Route("[controller]")]
+public class AuthorsController(Repository repository) : ControllerBase
+{
+ [HttpGet]
+ public Task> Get() => repository.GetAuthorsAsync();
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Data/Repository.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Data/Repository.cs
new file mode 100644
index 000000000..e85b66d32
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Data/Repository.cs
@@ -0,0 +1,34 @@
+using AuthorsService.Models;
+
+namespace AuthorsService.Data;
+
+public class Repository
+{
+ private readonly IEnumerable _authors =
+ [
+ new Author { AuthorId = 1, Name = "John Doe", Country = "Australia" },
+ new Author { AuthorId = 2, Name = "Jane Smith", Country = "United States" }
+ ];
+
+ private readonly DateTime _startTime = DateTime.UtcNow;
+ private bool _shouldFail = true;
+
+ public async Task> GetAuthorsAsync()
+ {
+ if (_shouldFail)
+ {
+ _shouldFail = false;
+
+ throw new InvalidOperationException("Oops!");
+ }
+
+ if (_startTime.AddMinutes(1) > DateTime.UtcNow)
+ {
+ await Task.Delay(TimeSpan.FromSeconds(5));
+
+ throw new TimeoutException("Timeout!");
+ }
+
+ return _authors;
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Models/Author.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Models/Author.cs
new file mode 100644
index 000000000..786249947
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Models/Author.cs
@@ -0,0 +1,8 @@
+namespace AuthorsService.Models;
+
+public class Author
+{
+ public int AuthorId { get; set; }
+ public required string Name { get; set; }
+ public required string Country { get; set; }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Program.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Program.cs
new file mode 100644
index 000000000..5809f0d7c
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Program.cs
@@ -0,0 +1,13 @@
+using AuthorsService.Data;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddSingleton();
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+app.UseHttpsRedirection();
+app.MapControllers();
+
+app.Run();
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Properties/launchSettings.json b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Properties/launchSettings.json
new file mode 100644
index 000000000..92175fd20
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "AuthorsService": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/appsettings.Development.json b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/appsettings.Development.json
new file mode 100644
index 000000000..8983e0fc1
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/appsettings.json b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/appsettings.json
new file mode 100644
index 000000000..d9d9a9bff
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/AuthorsService/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/BooksService.csproj b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/BooksService.csproj
new file mode 100644
index 000000000..c5245308a
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/BooksService.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Controllers/BooksController.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Controllers/BooksController.cs
new file mode 100644
index 000000000..8b1a70a3d
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Controllers/BooksController.cs
@@ -0,0 +1,13 @@
+using BooksService.Data;
+using BooksService.Models;
+using Microsoft.AspNetCore.Mvc;
+
+namespace BooksService.Controllers;
+
+[ApiController]
+[Route("[controller]")]
+public class BooksController(Repository repository) : ControllerBase
+{
+ [HttpGet]
+ public IEnumerable Get() => repository.GetBooks();
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Data/Repository.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Data/Repository.cs
new file mode 100644
index 000000000..8873a17a6
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Data/Repository.cs
@@ -0,0 +1,15 @@
+using BooksService.Models;
+
+namespace BooksService.Data;
+
+public class Repository
+{
+ private readonly IEnumerable _books =
+ [
+ new Book { BookId = 1, AuthorId = 1, Name = "The Fallen Shore", NumberOfPages = 123 },
+ new Book { BookId = 2, AuthorId = 1, Name = "Harmony of Joy", NumberOfPages = 211 },
+ new Book { BookId = 3, AuthorId = 2, Name = "Aliens vs Robots", NumberOfPages = 345 }
+ ];
+
+ public IEnumerable GetBooks() => _books;
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Models/Book.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Models/Book.cs
new file mode 100644
index 000000000..71bee9ec7
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Models/Book.cs
@@ -0,0 +1,9 @@
+namespace BooksService.Models;
+
+public class Book
+{
+ public int BookId { get; set; }
+ public int AuthorId { get; set; }
+ public required string Name { get; set; }
+ public int NumberOfPages { get; set; }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Program.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Program.cs
new file mode 100644
index 000000000..7d0bba4d9
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Program.cs
@@ -0,0 +1,13 @@
+using BooksService.Data;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddSingleton();
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+app.UseHttpsRedirection();
+app.MapControllers();
+
+app.Run();
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Properties/launchSettings.json b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Properties/launchSettings.json
new file mode 100644
index 000000000..880d77cb8
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "BooksService": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "applicationUrl": "https://localhost:6001;http://localhost:6000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/appsettings.Development.json b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/appsettings.Development.json
new file mode 100644
index 000000000..8983e0fc1
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/appsettings.json b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/appsettings.json
new file mode 100644
index 000000000..d9d9a9bff
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/BooksService/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Monolith.csproj b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Monolith.csproj
new file mode 100644
index 000000000..31f84661f
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Monolith.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Program.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Program.cs
new file mode 100644
index 000000000..eecdadcd1
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Program.cs
@@ -0,0 +1,21 @@
+using Microsoft.AspNetCore.Mvc;
+using Monolith.Resilience;
+using Polly;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddCors(options => options.AddDefaultPolicy(policy => policy.AllowAnyOrigin()));
+builder.Services.AddHttpClient();
+builder.Services.AddControllers();
+
+builder.Services.AddResiliencePipeline(
+ ProxyPipeline.Name,
+ (pipeline, _) => ProxyPipeline.Configure(pipeline));
+
+var app = builder.Build();
+
+app.UseHttpsRedirection();
+app.UseCors();
+app.MapControllers();
+
+app.Run();
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Properties/launchSettings.json b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Properties/launchSettings.json
new file mode 100644
index 000000000..b3afac33b
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "Monolith": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "applicationUrl": "https://localhost:7001",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/ProxyController.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/ProxyController.cs
new file mode 100644
index 000000000..9daa90060
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/ProxyController.cs
@@ -0,0 +1,32 @@
+using Microsoft.AspNetCore.Mvc;
+using Monolith.Resilience;
+using Polly;
+using Polly.Registry;
+
+namespace Monolith;
+
+[Route("[action]")]
+[ApiController]
+public class ProxyController : ControllerBase
+{
+ private readonly HttpClient _httpClient;
+ private readonly ResiliencePipeline _pipeline;
+
+ public ProxyController(IHttpClientFactory httpClientFactory,
+ ResiliencePipelineProvider pipelineProvider)
+ {
+ _httpClient = httpClientFactory.CreateClient();
+ _pipeline = pipelineProvider.GetPipeline(ProxyPipeline.Name);
+ }
+
+ [HttpGet]
+ public Task Books() => ProxyTo("https://localhost:6001/books");
+
+ [HttpGet]
+ public Task Authors() => ProxyTo("https://localhost:5001/authors");
+
+ private async Task ProxyTo(string url)
+ => await _pipeline.ExecuteAsync(
+ async token => (IActionResult)Content(await _httpClient.GetStringAsync(url, token)),
+ HttpContext.RequestAborted);
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Resilience/ProxyPipeline.cs b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Resilience/ProxyPipeline.cs
new file mode 100644
index 000000000..fa06dd908
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/Resilience/ProxyPipeline.cs
@@ -0,0 +1,38 @@
+using Microsoft.AspNetCore.Mvc;
+using Polly;
+using Polly.CircuitBreaker;
+using Polly.Fallback;
+using Polly.Retry;
+
+namespace Monolith.Resilience;
+
+public static class ProxyPipeline
+{
+ public const string Name = "proxy";
+
+ public const string FallbackMessage =
+ "Sorry, we are currently experiencing issues. Please try again later";
+
+ public static void Configure(ResiliencePipelineBuilder builder) =>
+ builder
+ .AddFallback(new FallbackStrategyOptions
+ {
+ ShouldHandle = new PredicateBuilder().Handle(),
+ FallbackAction = static _ => Outcome.FromResultAsValueTask(
+ new ContentResult { Content = FallbackMessage })
+ })
+ .AddRetry(new RetryStrategyOptions
+ {
+ ShouldHandle = new PredicateBuilder().Handle(),
+ MaxRetryAttempts = 1,
+ Delay = TimeSpan.Zero
+ })
+ .AddCircuitBreaker(new CircuitBreakerStrategyOptions
+ {
+ ShouldHandle = new PredicateBuilder().Handle(),
+ FailureRatio = 1.0,
+ MinimumThroughput = 2,
+ SamplingDuration = TimeSpan.FromSeconds(30),
+ BreakDuration = TimeSpan.FromMinutes(1)
+ });
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/appsettings.Development.json b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/appsettings.Development.json
new file mode 100644
index 000000000..8983e0fc1
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/appsettings.json b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/appsettings.json
new file mode 100644
index 000000000..d9d9a9bff
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/Monolith/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/consumer.html b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/consumer.html
new file mode 100644
index 000000000..208247c34
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/FinishedCode/consumer.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotnet-client-libraries/ResilienceWithPolly/README.md b/dotnet-client-libraries/ResilienceWithPolly/README.md
new file mode 100644
index 000000000..2967a94f1
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/README.md
@@ -0,0 +1,29 @@
+## Polly in .NET: Retry, Circuit Breaker, and Fallback
+
+Source code for [Polly in .NET: Retry, Circuit Breaker, and Fallback](https://code-maze.com/creating-resilient-microservices-in-net-with-polly/).
+
+The sample is an API gateway (`Monolith`) in front of two microservices,
+`AuthorsService` and `BooksService`. The gateway proxies requests to whichever
+service the URL names, so stopping a service is enough to simulate a dependency
+failure.
+
+| Folder | What it is |
+| - | - |
+| `StarterCode` | The starting point — the gateway and the two services, with no resilience at all. Clone this one first if you want to follow the article step by step. |
+| `FinishedCode` | The finished sample — the gateway executes every proxied call through a Polly v8 `ResiliencePipeline` with fallback, retry, and circuit-breaker strategies, and the Authors service simulates transient and slow failures. |
+| `Tests` | Tests over the finished pipeline: the retry recovers a single transient failure, the fallback message is returned when every attempt fails, and the circuit opens and stops calling the dependency. |
+
+Everything targets .NET 10 and Polly 8.
+
+```
+dotnet build ResilienceWithPolly.sln
+dotnet test ResilienceWithPolly.sln
+```
+
+### Running the sample
+
+Run all three projects — `AuthorsService` on `https://localhost:5001`,
+`BooksService` on `https://localhost:6001`, and `Monolith` on
+`https://localhost:7001` — then open `consumer.html` from the same folder in a
+browser and use its two buttons. Stopping a service while the page is open is
+what produces the failures the article walks through.
diff --git a/dotnet-client-libraries/ResilienceWithPolly/ResilienceWithPolly.sln b/dotnet-client-libraries/ResilienceWithPolly/ResilienceWithPolly.sln
new file mode 100644
index 000000000..31a494ec0
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/ResilienceWithPolly.sln
@@ -0,0 +1,130 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StarterCode", "StarterCode", "{B51ECAE0-892D-0853-1CAF-3BF2C7EB192B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthorsService", "StarterCode\AuthorsService\AuthorsService.csproj", "{437AED6E-2BD5-463F-A1C2-529B73F77513}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BooksService", "StarterCode\BooksService\BooksService.csproj", "{AB313368-5534-41E3-BB83-20B60BA33277}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Monolith", "StarterCode\Monolith\Monolith.csproj", "{C1DDFC6B-53CD-435E-A70C-28D4224E83F8}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FinishedCode", "FinishedCode", "{427DD017-79F4-E6E7-884D-AD9B36065000}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthorsService", "FinishedCode\AuthorsService\AuthorsService.csproj", "{39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BooksService", "FinishedCode\BooksService\BooksService.csproj", "{34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Monolith", "FinishedCode\Monolith\Monolith.csproj", "{66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Debug|x64.Build.0 = Debug|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Debug|x86.Build.0 = Debug|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Release|Any CPU.Build.0 = Release|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Release|x64.ActiveCfg = Release|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Release|x64.Build.0 = Release|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Release|x86.ActiveCfg = Release|Any CPU
+ {437AED6E-2BD5-463F-A1C2-529B73F77513}.Release|x86.Build.0 = Release|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Debug|x64.Build.0 = Debug|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Debug|x86.Build.0 = Debug|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Release|x64.ActiveCfg = Release|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Release|x64.Build.0 = Release|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Release|x86.ActiveCfg = Release|Any CPU
+ {AB313368-5534-41E3-BB83-20B60BA33277}.Release|x86.Build.0 = Release|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Debug|x64.Build.0 = Debug|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Debug|x86.Build.0 = Debug|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Release|x64.ActiveCfg = Release|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Release|x64.Build.0 = Release|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Release|x86.ActiveCfg = Release|Any CPU
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8}.Release|x86.Build.0 = Release|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Debug|x64.Build.0 = Debug|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Debug|x86.Build.0 = Debug|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Release|x64.ActiveCfg = Release|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Release|x64.Build.0 = Release|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Release|x86.ActiveCfg = Release|Any CPU
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B}.Release|x86.Build.0 = Release|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Debug|x64.Build.0 = Debug|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Debug|x86.Build.0 = Debug|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Release|x64.ActiveCfg = Release|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Release|x64.Build.0 = Release|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Release|x86.ActiveCfg = Release|Any CPU
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A}.Release|x86.Build.0 = Release|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Debug|x64.Build.0 = Debug|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Debug|x86.Build.0 = Debug|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Release|x64.ActiveCfg = Release|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Release|x64.Build.0 = Release|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Release|x86.ActiveCfg = Release|Any CPU
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD}.Release|x86.Build.0 = Release|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Debug|x64.Build.0 = Debug|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Debug|x86.Build.0 = Debug|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Release|x64.ActiveCfg = Release|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Release|x64.Build.0 = Release|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Release|x86.ActiveCfg = Release|Any CPU
+ {9A2E4D71-6750-4A11-9ABA-6BE6F1E0EF22}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {437AED6E-2BD5-463F-A1C2-529B73F77513} = {B51ECAE0-892D-0853-1CAF-3BF2C7EB192B}
+ {AB313368-5534-41E3-BB83-20B60BA33277} = {B51ECAE0-892D-0853-1CAF-3BF2C7EB192B}
+ {C1DDFC6B-53CD-435E-A70C-28D4224E83F8} = {B51ECAE0-892D-0853-1CAF-3BF2C7EB192B}
+ {39DF074F-A20D-45C1-AE3F-BB4CD36CBB6B} = {427DD017-79F4-E6E7-884D-AD9B36065000}
+ {34B9EC20-6B29-43C2-83FB-A2B5DDA65E6A} = {427DD017-79F4-E6E7-884D-AD9B36065000}
+ {66EA99B8-AC93-4DC3-B1C6-C08C01B541DD} = {427DD017-79F4-E6E7-884D-AD9B36065000}
+ EndGlobalSection
+EndGlobal
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/AuthorsService.csproj b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/AuthorsService.csproj
new file mode 100644
index 000000000..c5245308a
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/AuthorsService.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Controllers/AuthorsController.cs b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Controllers/AuthorsController.cs
new file mode 100644
index 000000000..f665c8dcf
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Controllers/AuthorsController.cs
@@ -0,0 +1,13 @@
+using AuthorsService.Data;
+using AuthorsService.Models;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AuthorsService.Controllers;
+
+[ApiController]
+[Route("[controller]")]
+public class AuthorsController(Repository repository) : ControllerBase
+{
+ [HttpGet]
+ public IEnumerable Get() => repository.GetAuthors();
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Data/Repository.cs b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Data/Repository.cs
new file mode 100644
index 000000000..0e04b31cc
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Data/Repository.cs
@@ -0,0 +1,14 @@
+using AuthorsService.Models;
+
+namespace AuthorsService.Data;
+
+public class Repository
+{
+ private readonly IEnumerable _authors =
+ [
+ new Author { AuthorId = 1, Name = "John Doe", Country = "Australia" },
+ new Author { AuthorId = 2, Name = "Jane Smith", Country = "United States" }
+ ];
+
+ public IEnumerable GetAuthors() => _authors;
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Models/Author.cs b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Models/Author.cs
new file mode 100644
index 000000000..786249947
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Models/Author.cs
@@ -0,0 +1,8 @@
+namespace AuthorsService.Models;
+
+public class Author
+{
+ public int AuthorId { get; set; }
+ public required string Name { get; set; }
+ public required string Country { get; set; }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Program.cs b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Program.cs
new file mode 100644
index 000000000..5809f0d7c
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Program.cs
@@ -0,0 +1,13 @@
+using AuthorsService.Data;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddSingleton();
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+app.UseHttpsRedirection();
+app.MapControllers();
+
+app.Run();
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Properties/launchSettings.json b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Properties/launchSettings.json
new file mode 100644
index 000000000..92175fd20
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "AuthorsService": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/appsettings.Development.json b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/appsettings.Development.json
new file mode 100644
index 000000000..8983e0fc1
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/appsettings.json b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/appsettings.json
new file mode 100644
index 000000000..d9d9a9bff
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/AuthorsService/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/BooksService.csproj b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/BooksService.csproj
new file mode 100644
index 000000000..c5245308a
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/BooksService.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Controllers/BooksController.cs b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Controllers/BooksController.cs
new file mode 100644
index 000000000..8b1a70a3d
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Controllers/BooksController.cs
@@ -0,0 +1,13 @@
+using BooksService.Data;
+using BooksService.Models;
+using Microsoft.AspNetCore.Mvc;
+
+namespace BooksService.Controllers;
+
+[ApiController]
+[Route("[controller]")]
+public class BooksController(Repository repository) : ControllerBase
+{
+ [HttpGet]
+ public IEnumerable Get() => repository.GetBooks();
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Data/Repository.cs b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Data/Repository.cs
new file mode 100644
index 000000000..8873a17a6
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Data/Repository.cs
@@ -0,0 +1,15 @@
+using BooksService.Models;
+
+namespace BooksService.Data;
+
+public class Repository
+{
+ private readonly IEnumerable _books =
+ [
+ new Book { BookId = 1, AuthorId = 1, Name = "The Fallen Shore", NumberOfPages = 123 },
+ new Book { BookId = 2, AuthorId = 1, Name = "Harmony of Joy", NumberOfPages = 211 },
+ new Book { BookId = 3, AuthorId = 2, Name = "Aliens vs Robots", NumberOfPages = 345 }
+ ];
+
+ public IEnumerable GetBooks() => _books;
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Models/Book.cs b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Models/Book.cs
new file mode 100644
index 000000000..71bee9ec7
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Models/Book.cs
@@ -0,0 +1,9 @@
+namespace BooksService.Models;
+
+public class Book
+{
+ public int BookId { get; set; }
+ public int AuthorId { get; set; }
+ public required string Name { get; set; }
+ public int NumberOfPages { get; set; }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Program.cs b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Program.cs
new file mode 100644
index 000000000..7d0bba4d9
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Program.cs
@@ -0,0 +1,13 @@
+using BooksService.Data;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddSingleton();
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+app.UseHttpsRedirection();
+app.MapControllers();
+
+app.Run();
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Properties/launchSettings.json b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Properties/launchSettings.json
new file mode 100644
index 000000000..880d77cb8
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "BooksService": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "applicationUrl": "https://localhost:6001;http://localhost:6000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/appsettings.Development.json b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/appsettings.Development.json
new file mode 100644
index 000000000..8983e0fc1
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/appsettings.json b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/appsettings.json
new file mode 100644
index 000000000..d9d9a9bff
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/BooksService/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/Monolith.csproj b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/Monolith.csproj
new file mode 100644
index 000000000..c5245308a
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/Monolith.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/Program.cs b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/Program.cs
new file mode 100644
index 000000000..802fa2abb
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/Program.cs
@@ -0,0 +1,13 @@
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddCors(options => options.AddDefaultPolicy(policy => policy.AllowAnyOrigin()));
+builder.Services.AddHttpClient();
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+app.UseHttpsRedirection();
+app.UseCors();
+app.MapControllers();
+
+app.Run();
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/Properties/launchSettings.json b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/Properties/launchSettings.json
new file mode 100644
index 000000000..b3afac33b
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "Monolith": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "applicationUrl": "https://localhost:7001",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/ProxyController.cs b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/ProxyController.cs
new file mode 100644
index 000000000..96b941f0d
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/ProxyController.cs
@@ -0,0 +1,19 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace Monolith;
+
+[Route("[action]")]
+[ApiController]
+public class ProxyController(IHttpClientFactory httpClientFactory) : ControllerBase
+{
+ private readonly HttpClient _httpClient = httpClientFactory.CreateClient();
+
+ [HttpGet]
+ public Task Books() => ProxyTo("https://localhost:6001/books");
+
+ [HttpGet]
+ public Task Authors() => ProxyTo("https://localhost:5001/authors");
+
+ private async Task ProxyTo(string url)
+ => Content(await _httpClient.GetStringAsync(url));
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/appsettings.Development.json b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/appsettings.Development.json
new file mode 100644
index 000000000..8983e0fc1
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/appsettings.json b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/appsettings.json
new file mode 100644
index 000000000..d9d9a9bff
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/Monolith/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/StarterCode/consumer.html b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/consumer.html
new file mode 100644
index 000000000..c7990d2fc
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/StarterCode/consumer.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotnet-client-libraries/ResilienceWithPolly/Tests/ProxyPipelineTests.cs b/dotnet-client-libraries/ResilienceWithPolly/Tests/ProxyPipelineTests.cs
new file mode 100644
index 000000000..6b568f9be
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/Tests/ProxyPipelineTests.cs
@@ -0,0 +1,117 @@
+using Microsoft.AspNetCore.Mvc;
+using Monolith.Resilience;
+using Polly;
+using Polly.CircuitBreaker;
+
+namespace Tests;
+
+[TestClass]
+public class ProxyPipelineTests
+{
+ private static ResiliencePipeline BuildPipeline()
+ {
+ var builder = new ResiliencePipelineBuilder();
+ ProxyPipeline.Configure(builder);
+
+ return builder.Build();
+ }
+
+ private static string? ContentOf(IActionResult result) =>
+ ((ContentResult)result).Content;
+
+ [TestMethod]
+ public async Task GivenACallThatSucceeds_WhenExecutedThroughThePipeline_ThenItReturnsTheResult()
+ {
+ var pipeline = BuildPipeline();
+
+ var result = await pipeline.ExecuteAsync(static _ =>
+ ValueTask.FromResult(new ContentResult { Content = "authors" }));
+
+ Assert.AreEqual("authors", ContentOf(result));
+ }
+
+ [TestMethod]
+ public async Task GivenACallThatFailsOnce_WhenExecutedThroughThePipeline_ThenTheRetryRecoversIt()
+ {
+ var pipeline = BuildPipeline();
+ var attempts = 0;
+
+ var result = await pipeline.ExecuteAsync(_ =>
+ {
+ attempts++;
+
+ if (attempts == 1)
+ {
+ throw new InvalidOperationException("Oops!");
+ }
+
+ return ValueTask.FromResult(new ContentResult { Content = "authors" });
+ });
+
+ Assert.AreEqual(2, attempts);
+ Assert.AreEqual("authors", ContentOf(result));
+ }
+
+ [TestMethod]
+ public async Task GivenACallThatAlwaysFails_WhenExecutedThroughThePipeline_ThenTheFallbackMessageIsReturned()
+ {
+ var pipeline = BuildPipeline();
+
+ var result = await pipeline.ExecuteAsync(static _ =>
+ throw new InvalidOperationException("Oops!"));
+
+ Assert.AreEqual(ProxyPipeline.FallbackMessage, ContentOf(result));
+ }
+
+ [TestMethod]
+ public async Task GivenACallThatAlwaysFails_WhenTheThroughputThresholdIsReached_ThenTheCircuitOpensAndStopsCallingTheDependency()
+ {
+ var pipeline = BuildPipeline();
+ var attempts = 0;
+
+ // One execution is two attempts -- the call plus a single retry -- which
+ // meets MinimumThroughput at a failure ratio of 1.0, so the circuit opens.
+ await pipeline.ExecuteAsync(_ =>
+ {
+ attempts++;
+
+ throw new InvalidOperationException("Oops!");
+ });
+
+ Assert.AreEqual(2, attempts);
+
+ var result = await pipeline.ExecuteAsync(_ =>
+ {
+ attempts++;
+
+ throw new InvalidOperationException("Oops!");
+ });
+
+ Assert.AreEqual(2, attempts, "The open circuit must short-circuit without calling the dependency again.");
+ Assert.AreEqual(ProxyPipeline.FallbackMessage, ContentOf(result));
+ }
+
+ [TestMethod]
+ public async Task GivenAnOpenCircuit_WhenTheFallbackIsRemoved_ThenABrokenCircuitExceptionSurfaces()
+ {
+ var pipeline = new ResiliencePipelineBuilder()
+ .AddCircuitBreaker(new CircuitBreakerStrategyOptions
+ {
+ ShouldHandle = new PredicateBuilder().Handle(),
+ FailureRatio = 1.0,
+ MinimumThroughput = 2,
+ SamplingDuration = TimeSpan.FromSeconds(30),
+ BreakDuration = TimeSpan.FromMinutes(1)
+ })
+ .Build();
+
+ for (var i = 0; i < 2; i++)
+ {
+ await Assert.ThrowsExactlyAsync(async () =>
+ await pipeline.ExecuteAsync(static _ => throw new InvalidOperationException("Oops!")));
+ }
+
+ await Assert.ThrowsExactlyAsync(async () =>
+ await pipeline.ExecuteAsync(static _ => ValueTask.CompletedTask));
+ }
+}
diff --git a/dotnet-client-libraries/ResilienceWithPolly/Tests/Tests.csproj b/dotnet-client-libraries/ResilienceWithPolly/Tests/Tests.csproj
new file mode 100644
index 000000000..bd887ec03
--- /dev/null
+++ b/dotnet-client-libraries/ResilienceWithPolly/Tests/Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+