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 + + + + + + + + + + + + + + + + + +