diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index eb8f1d6dc80..c100f18aa05 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -7,6 +7,12 @@
"commands": [
"dotnet-cake"
]
+ },
+ "markdownsnippets.tool": {
+ "version": "25.1.0",
+ "commands": [
+ "mdsnippets"
+ ]
}
}
-}
+}
\ No newline at end of file
diff --git a/.github/workflows/on-push-do-docs.yml b/.github/workflows/on-push-do-docs.yml
new file mode 100644
index 00000000000..78bf38102f0
--- /dev/null
+++ b/.github/workflows/on-push-do-docs.yml
@@ -0,0 +1,96 @@
+name: on-push-do-docs
+
+on:
+ push:
+ branches: [main]
+ paths: [ "src/Snippets/**" ]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ update-docs:
+ name: update-docs
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
+
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
+
+ - name: Update documentation
+ id: update-docs
+ shell: pwsh
+ env:
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ run: |
+ $ErrorActionPreference = "Stop"
+ $ProgressPreference = "SilentlyContinue"
+
+ dotnet tool restore
+ dotnet mdsnippets
+
+ $GitStatus = (git status --porcelain)
+ if ([string]::IsNullOrEmpty($GitStatus)) {
+ Write-Host "No changes to commit."
+ exit 0
+ }
+
+ $TimeStamp = Get-Date -Format "yyyy-MM-dd-HH-mm"
+ $BranchName = "docs/update-docs-$TimeStamp"
+ "branchName=$BranchName" >> $env:GITHUB_OUTPUT
+
+ $GitEmail = "138034000+polly-updater-bot[bot]@users.noreply.github.com"
+ $GitUser = "polly-updater-bot[bot]"
+
+ git config user.email $GitEmail | Out-Null
+ git config user.name $GitUser | Out-Null
+ git remote set-url "${{ github.server_url }}/${{ github.repository }}.git" | Out-Null
+ git fetch origin | Out-Null
+ git rev-parse --verify --quiet ("remotes/origin/" + $BranchName) | Out-Null
+
+ if ($LASTEXITCODE -eq 0) {
+ Write-Host "Branch $BranchName already exists."
+ exit 0
+ }
+
+ git checkout -b $BranchName
+ git add .
+ git commit -m "Update the code-snippets in the documentation"
+ git push -u origin $BranchName
+ "updated-docs=true" >> $env:GITHUB_OUTPUT
+
+ - name: Generate GitHub application token
+ if: steps.update-docs.outputs.updated-docs == 'true'
+ id: generate-application-token
+ uses: peter-murray/workflow-application-token-action@8e1ba3bf1619726336414f1014e37f17fbadf1db # v2.1.0
+ with:
+ application_id: ${{ secrets.POLLY_UPDATER_BOT_APP_ID }}
+ application_private_key: ${{ secrets.POLLY_UPDATER_BOT_KEY }}
+ permissions: "contents:write, pull_requests:write"
+
+ - name: Create pull request
+ if: steps.update-docs.outputs.updated-docs == 'true'
+ uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1
+ with:
+ github-token: ${{ steps.generate-application-token.outputs.token }}
+ script: |
+ const { repo, owner } = context.repo;
+ const workflowUrl = `${{ github.server_url }}/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
+ const branchName = "${{ steps.update-docs.outputs.branchName }}";
+ const result = await github.rest.pulls.create({
+ title: 'Update the code-snippets in the documentation',
+ owner,
+ repo,
+ head: branchName,
+ base: 'main',
+ body: [
+ 'This PR updates the code-snippets in the documentation.',
+ '',
+ `This pull request was generated by [GitHub Actions](${workflowUrl}).`
+ ].join('\n')
+ });
diff --git a/Directory.Packages.props b/Directory.Packages.props
index cc64d160c98..8fbd4b6b9c6 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -25,7 +25,9 @@
+
+
diff --git a/Polly.sln b/Polly.sln
index 97851b37fda..bf30ece2e28 100644
--- a/Polly.sln
+++ b/Polly.sln
@@ -56,6 +56,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Testing", "src\Polly.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Testing.Tests", "test\Polly.Testing.Tests\Polly.Testing.Tests.csproj", "{D333B5CE-982D-4C11-BDAF-4217AA02306E}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snippets", "src\Snippets\Snippets.csproj", "{D812B941-79B0-4E1E-BB70-4FAE345B5234}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -114,6 +116,10 @@ Global
{D333B5CE-982D-4C11-BDAF-4217AA02306E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D333B5CE-982D-4C11-BDAF-4217AA02306E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D333B5CE-982D-4C11-BDAF-4217AA02306E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D812B941-79B0-4E1E-BB70-4FAE345B5234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D812B941-79B0-4E1E-BB70-4FAE345B5234}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D812B941-79B0-4E1E-BB70-4FAE345B5234}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D812B941-79B0-4E1E-BB70-4FAE345B5234}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -133,6 +139,7 @@ Global
{C04DEE61-C1EA-4028-B457-CDBD304B8ED9} = {A6CC41B9-E0B9-44F8-916B-3E4A78DA3BFB}
{9AD2D6AD-56E4-49D6-B6F1-EE975D5760B9} = {B7BF406B-B06F-4025-83E6-7219C53196A6}
{D333B5CE-982D-4C11-BDAF-4217AA02306E} = {A6CC41B9-E0B9-44F8-916B-3E4A78DA3BFB}
+ {D812B941-79B0-4E1E-BB70-4FAE345B5234} = {B7BF406B-B06F-4025-83E6-7219C53196A6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2E5D54CD-770A-4345-B585-1848FC2EA6F4}
diff --git a/bench/README.md b/bench/README.md
index 685b604fdcd..9f489c67a34 100644
--- a/bench/README.md
+++ b/bench/README.md
@@ -1,4 +1,4 @@
-# Benchmarks
+# Benchmarks
To run the benchmarks, use the `benchmarks.ps1` script in this repository:
diff --git a/build.cake b/build.cake
index 4d69f8d3725..e1d8143561a 100644
--- a/build.cake
+++ b/build.cake
@@ -233,6 +233,16 @@ Task("__CreateNuGetPackages")
}
});
+Task("__ValidateDocs")
+ .Does(() =>
+{
+ var result = StartProcess("dotnet", "mdsnippets --validate-content");
+ if (result != 0)
+ {
+ throw new InvalidOperationException($"Failed to validate the documentation snippets. Are the links correct?");
+ }
+});
+
//////////////////////////////////////////////////////////////////////
// BUILD TASKS
//////////////////////////////////////////////////////////////////////
@@ -240,6 +250,7 @@ Task("__CreateNuGetPackages")
Task("Build")
.IsDependentOn("__Clean")
.IsDependentOn("__RestoreNuGetPackages")
+ .IsDependentOn("__ValidateDocs")
.IsDependentOn("__BuildSolutions")
.IsDependentOn("__RunTests")
.IsDependentOn("__RunMutationTests")
diff --git a/mdsnippets.json b/mdsnippets.json
new file mode 100644
index 00000000000..6ebcb8e4a74
--- /dev/null
+++ b/mdsnippets.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/schema.json",
+ "ExcludeDirectories": [ "test", "artifacts", "bench", "eng" ],
+ "ExcludeSnippetDirectories": [ "src/Polly", "src/Polly.Core", "src/Polly.RateLimiting", "src/Polly.Testing", "src/Polly.Extensions", "artifacts", "test" ],
+ "OmitSnippetLinks": true,
+ "Convention": "InPlaceOverwrite"
+ }
\ No newline at end of file
diff --git a/src/Polly.Core/README.md b/src/Polly.Core/README.md
index a51ce884bc5..b3b20a2e041 100644
--- a/src/Polly.Core/README.md
+++ b/src/Polly.Core/README.md
@@ -71,24 +71,18 @@ The resilience pipeline may consist of one or more individual resilience strateg
Here's an example of a non-reactive strategy that executes a user-provided callback:
-```csharp
+
+```cs
internal class MyCustomStrategy : ResilienceStrategy
{
- private readonly TimeProvider _timeProvider;
-
- public MyCustomStrategy(TimeProvider timeProvider)
- {
- _timeProvider = timeProvider;
- }
-
- protected override async ValueTask ExecuteCore(
- Func>> callback,
- ResilienceContext context,
+ protected override async ValueTask> ExecuteCore(
+ Func>> callback,
+ ResilienceContext context,
TState state)
{
// Perform actions before execution
- var outcome = await callback(context, state).ContinueOnCapturedContext(context.ContinueOnCapturedContext);
+ var outcome = await callback(context, state).ConfigureAwait(context.ContinueOnCapturedContext);
// Perform actions after execution
@@ -96,6 +90,7 @@ internal class MyCustomStrategy : ResilienceStrategy
}
}
```
+
### About Synchronous and Asynchronous Executions
@@ -113,23 +108,27 @@ To construct a resilience pipeline, chain various extensions on the `ResilienceP
### Creating a non-generic pipeline
-```csharp
-var pipeline = new ResiliencePipelineBuilder()
+
+```cs
+ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddRetry(new())
.AddCircuitBreaker(new())
- .AddTimeout(new TimeoutStrategyOptions() { ... })
+ .AddTimeout(TimeSpan.FromSeconds(1))
.Build();
```
+
### Creating a generic pipeline
-```csharp
-var pipeline = new ResiliencePipelineBuilder()
+
+```cs
+ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddRetry(new())
.AddCircuitBreaker(new())
- .AddTimeout(new TimeoutStrategyOptions() { ... })
+ .AddTimeout(TimeSpan.FromSeconds(1))
.Build();
```
+
## Extensibility
@@ -137,13 +136,23 @@ Extending the resilience functionality is straightforward. You can create extens
Here's an example:
-```csharp
+
+```cs
public static TBuilder AddMyCustomStrategy(this TBuilder builder, MyCustomStrategyOptions options)
where TBuilder : ResiliencePipelineBuilderBase
{
return builder.AddStrategy(context => new MyCustomStrategy(), options);
}
+
+public class MyCustomStrategyOptions : ResilienceStrategyOptions
+{
+ public MyCustomStrategyOptions()
+ {
+ Name = "MyCustomStrategy";
+ }
+}
```
+
To gain insights into implementing custom resilience strategies, you can explore the following Polly strategy examples:
@@ -185,7 +194,8 @@ These delegates accept either `Args` or `Args` arguments, which encapsu
For non-reactive strategies, the `Args` structure might resemble:
-```csharp
+
+```cs
public readonly struct OnTimeoutArguments
{
public OnTimeoutArguments(ResilienceContext context, TimeSpan timeout)
@@ -195,39 +205,23 @@ public readonly struct OnTimeoutArguments
}
public ResilienceContext Context { get; } // Include the Context property
- public TimeSpan Timeout { get; } // Additional event-related properties
-}
-```
-For reactive strategies, `Args` could look like:
-
-```csharp
-public readonly struct OnRetryArguments
-{
- public OnRetryArguments(ResilienceContext context, Outcome outcome, int attemptNumber)
- {
- Context = context;
- Outcome = outcome;
- AttemptNumber = attemptNumber;
- }
-
- public ResilienceContext Context { get; } // Include the Context property
- public Outcome Outcome { get; } // Includes the outcome associated with the event
- public int AttemptNumber { get; }
+ public TimeSpan Timeout { get; } // Additional event-related properties
}
```
+
### Example: Usage of Delegates
Below are some examples illustrating the usage of these delegates:
-```csharp
+
+```cs
new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
-
// Non-Generic predicate for multiple result types
- ShouldHandle = args => args switch
+ ShouldHandle = args => args.Outcome switch
{
{ Exception: InvalidOperationException } => PredicateResult.True(),
{ Result: string result } when result == "Failure" => PredicateResult.True(),
@@ -236,22 +230,21 @@ new ResiliencePipelineBuilder()
},
})
.Build();
-```
-```csharp
new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
// Generic predicate for a single result type
- ShouldHandle = args => args switch
+ ShouldHandle = args => args.Outcome switch
{
{ Exception: InvalidOperationException } => PredicateResult.True(),
- { Result: result } when result == "Failure" => PredicateResult.True(),
+ { Result: { } result } when result == "Failure" => PredicateResult.True(),
_ => PredicateResult.False()
},
})
.Build();
```
+
## Telemetry
@@ -259,4 +252,4 @@ Each resilience strategy can generate telemetry data through the [`ResiliencePip
To leverage this telemetry data, users should assign a `TelemetryListener` instance to `ResiliencePipelineBuilder.TelemetryListener` and then consume the `TelemetryEventArguments`.
-For common scenarios, it is expected that users would make use of `Polly.Extensions`. This extension enables telemetry configuration through the `ResiliencePipelineBuilder.ConfigureTelemetry(...)` method, which processes `TelemetryEventArguments` to generate logs and metrics.
\ No newline at end of file
+For common scenarios, it is expected that users would make use of `Polly.Extensions`. This extension enables telemetry configuration through the `ResiliencePipelineBuilder.ConfigureTelemetry(...)` method, which processes `TelemetryEventArguments` to generate logs and metrics.
diff --git a/src/Polly.Extensions/README.md b/src/Polly.Extensions/README.md
index 98ad82ea4e4..8d227885e67 100644
--- a/src/Polly.Extensions/README.md
+++ b/src/Polly.Extensions/README.md
@@ -4,7 +4,8 @@
Below is an example illustrating the usage of `AddResiliencePipeline` extension method:
-``` csharp
+
+```cs
var services = new ServiceCollection();
// Define a resilience pipeline
@@ -13,13 +14,16 @@ services.AddResiliencePipeline(
builder => builder.AddTimeout(TimeSpan.FromSeconds(10)));
// Define a resilience pipeline with custom options
-services.AddResiliencePipeline(
- "my-timeout",
- (builder, context) =>
- {
- var myOptions = context.ServiceProvider.GetRequiredService>().Value;
- builder.AddTimeout(myOptions.Timeout);
- });
+services
+ .Configure(options => options.Timeout = TimeSpan.FromSeconds(10))
+ .AddResiliencePipeline(
+ "my-timeout",
+ (builder, context) =>
+ {
+ var myOptions = context.GetOptions();
+
+ builder.AddTimeout(myOptions.Timeout);
+ });
// Resolve the resilience pipeline
var serviceProvider = services.BuildServiceProvider();
@@ -27,8 +31,9 @@ var pipelineProvider = serviceProvider.GetRequiredService { ... });
+await pipeline.ExecuteAsync(async cancellation => await Task.Delay(100, cancellation));
```
+
> [!NOTE]
> Telemetry is enabled by default when utilizing the `AddResiliencePipeline` extension method.
@@ -37,11 +42,13 @@ await pipeline.ExecuteAsync(cancellation => { ... });
Upon invoking the `ConfigureTelemetry` extension method, Polly begins to emit logs and metrics. Here's an example:
-``` csharp
-var telemetryOptions = new TelemetryOptions();
-
-// Configure logging
-telemetryOptions.LoggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
+
+```cs
+var telemetryOptions = new TelemetryOptions
+{
+ // Configure logging
+ LoggerFactory = LoggerFactory.Create(builder => builder.AddConsole())
+};
// Configure enrichers
telemetryOptions.MeteringEnrichers.Add(new MyMeteringEnricher());
@@ -53,8 +60,12 @@ var builder = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(1))
.ConfigureTelemetry(telemetryOptions) // This method enables telemetry in the builder
.Build();
+```
+
-class MyTelemetryListener : TelemetryListener
+
+```cs
+internal class MyTelemetryListener : TelemetryListener
{
public override void Write(in TelemetryEventArguments args)
{
@@ -62,22 +73,23 @@ class MyTelemetryListener : TelemetryListener
}
}
-class MyMeteringEnricher : MeteringEnricher
+internal class MyMeteringEnricher : MeteringEnricher
{
public override void Enrich(in EnrichmentContext context)
{
- context.Tags.Add(new("my-custom-tag", "custom-value");
+ context.Tags.Add(new("my-custom-tag", "custom-value"));
}
}
```
+
Alternatively, you can use the `AddResiliencePipeline` extension which automatically adds telemetry:
-``` csharp
+
+```cs
var serviceCollection = new ServiceCollection()
.AddLogging(builder => builder.AddConsole())
.AddResiliencePipeline("my-strategy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1)))
- // Configure the default settings for TelemetryOptions
.Configure(options =>
{
// Configure enrichers
@@ -87,6 +99,7 @@ var serviceCollection = new ServiceCollection()
options.TelemetryListeners.Add(new MyTelemetryListener());
});
```
+
### Emitted Metrics
diff --git a/src/Polly.RateLimiting/README.md b/src/Polly.RateLimiting/README.md
index e86596e1d54..ff346fb90a9 100644
--- a/src/Polly.RateLimiting/README.md
+++ b/src/Polly.RateLimiting/README.md
@@ -8,7 +8,10 @@ The `Polly.RateLimiting` package adopts the [.NET Rate Limiting](https://devblog
Example:
-``` csharp
+
+```cs
+ResiliencePipelineBuilder builder = new ResiliencePipelineBuilder();
+
// Convenience extension method for ConcurrencyLimiter
builder.AddConcurrencyLimiter(permitLimit: 10, queueLimit: 10);
@@ -45,4 +48,4 @@ builder.AddRateLimiter(new RateLimiterStrategyOptions
}
});
```
-
+
diff --git a/src/Polly.Testing/README.md b/src/Polly.Testing/README.md
index 1593b34f83c..c2720652ce0 100644
--- a/src/Polly.Testing/README.md
+++ b/src/Polly.Testing/README.md
@@ -2,26 +2,28 @@
This package exposes APIs and utilities that can be used to assert on the composition of resilience pipelines.
-``` csharp
+
+```cs
// Build your resilience pipeline.
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
- RetryCount = 4
+ MaxRetryAttempts = 4
})
.AddTimeout(TimeSpan.FromSeconds(1))
.ConfigureTelemetry(NullLoggerFactory.Instance)
.Build();
// Retrieve inner strategies.
-ResiliencePipelineDescriptor descriptor = strategy.GetPipelineDescriptor();
+ResiliencePipelineDescriptor descriptor = pipeline.GetPipelineDescriptor();
// Assert the composition.
Assert.Equal(2, descriptor.Strategies.Count);
var retryOptions = Assert.IsType(descriptor.Strategies[0].Options);
-Assert.Equal(4, retryOptions.RetryCount);
+Assert.Equal(4, retryOptions.MaxRetryAttempts);
var timeoutOptions = Assert.IsType(descriptor.Strategies[0].Options);
Assert.Equal(TimeSpan.FromSeconds(1), timeoutOptions.Timeout);
```
+
diff --git a/src/Snippets/Core/Snippets.cs b/src/Snippets/Core/Snippets.cs
new file mode 100644
index 00000000000..526835b52b6
--- /dev/null
+++ b/src/Snippets/Core/Snippets.cs
@@ -0,0 +1,143 @@
+using Polly;
+using Polly.Retry;
+
+namespace Snippets.Core;
+
+internal static class Snippets
+{
+ public static void NonGenericPipeline()
+ {
+ #region create-non-generic-pipeline
+
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddRetry(new())
+ .AddCircuitBreaker(new())
+ .AddTimeout(TimeSpan.FromSeconds(1))
+ .Build();
+
+ #endregion
+ }
+
+ public static void GenericPipeline()
+ {
+ #region create-generic-pipeline
+
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddRetry(new())
+ .AddCircuitBreaker(new())
+ .AddTimeout(TimeSpan.FromSeconds(1))
+ .Build();
+
+ #endregion
+ }
+
+ #region on-retry-args
+
+ public readonly struct OnRetryArguments
+ {
+ public OnRetryArguments(ResilienceContext context, Outcome outcome, int attemptNumber)
+ {
+ Context = context;
+ Outcome = outcome;
+ AttemptNumber = attemptNumber;
+ }
+
+ public ResilienceContext Context { get; } // Include the Context property
+
+ public Outcome Outcome { get; } // Includes the outcome associated with the event
+
+ public int AttemptNumber { get; }
+ }
+
+ #endregion
+
+ #region on-timeout-args
+
+ public readonly struct OnTimeoutArguments
+ {
+ public OnTimeoutArguments(ResilienceContext context, TimeSpan timeout)
+ {
+ Context = context;
+ Timeout = timeout;
+ }
+
+ public ResilienceContext Context { get; } // Include the Context property
+
+ public TimeSpan Timeout { get; } // Additional event-related properties
+ }
+
+ #endregion
+
+ #region add-my-custom-strategy
+
+ public static TBuilder AddMyCustomStrategy(this TBuilder builder, MyCustomStrategyOptions options)
+ where TBuilder : ResiliencePipelineBuilderBase
+ {
+ return builder.AddStrategy(context => new MyCustomStrategy(), options);
+ }
+
+ public class MyCustomStrategyOptions : ResilienceStrategyOptions
+ {
+ public MyCustomStrategyOptions()
+ {
+ Name = "MyCustomStrategy";
+ }
+ }
+
+ #endregion
+
+ #region my-custom-strategy
+
+ internal class MyCustomStrategy : ResilienceStrategy
+ {
+ protected override async ValueTask> ExecuteCore(
+ Func>> callback,
+ ResilienceContext context,
+ TState state)
+ {
+ // Perform actions before execution
+
+ var outcome = await callback(context, state).ConfigureAwait(context.ContinueOnCapturedContext);
+
+ // Perform actions after execution
+
+ return outcome;
+ }
+ }
+
+ #endregion
+
+ public static void DelegateUsage()
+ {
+ #region delegate-usage
+
+ new ResiliencePipelineBuilder()
+ .AddRetry(new RetryStrategyOptions
+ {
+ // Non-Generic predicate for multiple result types
+ ShouldHandle = args => args.Outcome switch
+ {
+ { Exception: InvalidOperationException } => PredicateResult.True(),
+ { Result: string result } when result == "Failure" => PredicateResult.True(),
+ { Result: int result } when result == -1 => PredicateResult.True(),
+ _ => PredicateResult.False()
+ },
+ })
+ .Build();
+
+ new ResiliencePipelineBuilder()
+ .AddRetry(new RetryStrategyOptions
+ {
+ // Generic predicate for a single result type
+ ShouldHandle = args => args.Outcome switch
+ {
+ { Exception: InvalidOperationException } => PredicateResult.True(),
+ { Result: { } result } when result == "Failure" => PredicateResult.True(),
+ _ => PredicateResult.False()
+ },
+ })
+ .Build();
+
+ #endregion;
+ }
+}
diff --git a/src/Snippets/Extensions/Snippets.cs b/src/Snippets/Extensions/Snippets.cs
new file mode 100644
index 00000000000..41a8dd1327b
--- /dev/null
+++ b/src/Snippets/Extensions/Snippets.cs
@@ -0,0 +1,111 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Polly;
+using Polly.Registry;
+using Polly.Telemetry;
+
+namespace Snippets.Extensions;
+
+internal static class Snippets
+{
+ public static void ConfigureTelemetry()
+ {
+ #region configure-telemetry
+
+ var telemetryOptions = new TelemetryOptions
+ {
+ // Configure logging
+ LoggerFactory = LoggerFactory.Create(builder => builder.AddConsole())
+ };
+
+ // Configure enrichers
+ telemetryOptions.MeteringEnrichers.Add(new MyMeteringEnricher());
+
+ // Configure telemetry listeners
+ telemetryOptions.TelemetryListeners.Add(new MyTelemetryListener());
+
+ var builder = new ResiliencePipelineBuilder()
+ .AddTimeout(TimeSpan.FromSeconds(1))
+ .ConfigureTelemetry(telemetryOptions) // This method enables telemetry in the builder
+ .Build();
+
+ #endregion
+ }
+
+ public static async Task AddResiliencePipeline()
+ {
+ #region add-resilience-pipeline
+
+ var services = new ServiceCollection();
+
+ // Define a resilience pipeline
+ services.AddResiliencePipeline(
+ "my-key",
+ builder => builder.AddTimeout(TimeSpan.FromSeconds(10)));
+
+ // Define a resilience pipeline with custom options
+ services
+ .Configure(options => options.Timeout = TimeSpan.FromSeconds(10))
+ .AddResiliencePipeline(
+ "my-timeout",
+ (builder, context) =>
+ {
+ var myOptions = context.GetOptions();
+
+ builder.AddTimeout(myOptions.Timeout);
+ });
+
+ // Resolve the resilience pipeline
+ var serviceProvider = services.BuildServiceProvider();
+ var pipelineProvider = serviceProvider.GetRequiredService>();
+ var pipeline = pipelineProvider.GetPipeline("my-key");
+
+ // Use it
+ await pipeline.ExecuteAsync(async cancellation => await Task.Delay(100, cancellation));
+
+ #endregion
+ }
+
+ public static void AddResiliencePipelineWithTelemetry()
+ {
+ #region add-resilience-pipeline-with-telemetry
+
+ var serviceCollection = new ServiceCollection()
+ .AddLogging(builder => builder.AddConsole())
+ .AddResiliencePipeline("my-strategy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1)))
+ .Configure(options =>
+ {
+ // Configure enrichers
+ options.MeteringEnrichers.Add(new MyMeteringEnricher());
+
+ // Configure telemetry listeners
+ options.TelemetryListeners.Add(new MyTelemetryListener());
+ });
+ #endregion
+ }
+
+ private class MyTimeoutOptions
+ {
+ public TimeSpan Timeout { get; set; }
+ }
+
+ #region telemetry-listeners
+
+ internal class MyTelemetryListener : TelemetryListener
+ {
+ public override void Write(in TelemetryEventArguments args)
+ {
+ Console.WriteLine($"Telemetry event occurred: {args.Event.EventName}");
+ }
+ }
+
+ internal class MyMeteringEnricher : MeteringEnricher
+ {
+ public override void Enrich(in EnrichmentContext context)
+ {
+ context.Tags.Add(new("my-custom-tag", "custom-value"));
+ }
+ }
+
+ #endregion
+}
diff --git a/src/Snippets/README.md b/src/Snippets/README.md
new file mode 100644
index 00000000000..b9acd526644
--- /dev/null
+++ b/src/Snippets/README.md
@@ -0,0 +1,7 @@
+# Snippets
+
+Various code-snippets used in the Polly documentation. Run the following command in the root directory to update the snippets in the docs:
+
+```powershell
+dotnet mdsnippets
+```
diff --git a/src/Snippets/RateLimiting/Snippets.cs b/src/Snippets/RateLimiting/Snippets.cs
new file mode 100644
index 00000000000..0cd2c9b0a66
--- /dev/null
+++ b/src/Snippets/RateLimiting/Snippets.cs
@@ -0,0 +1,53 @@
+using System.Threading.RateLimiting;
+using Polly;
+using Polly.RateLimiting;
+
+namespace Snippets.RateLimiting;
+
+internal static class Snippets
+{
+ public static void Usage()
+ {
+ #region rate-limiter-usage
+
+ ResiliencePipelineBuilder builder = new ResiliencePipelineBuilder();
+
+ // Convenience extension method for ConcurrencyLimiter
+ builder.AddConcurrencyLimiter(permitLimit: 10, queueLimit: 10);
+
+ // Convenience extension method for ConcurrencyLimiter with callback
+ builder.AddConcurrencyLimiter(
+ new ConcurrencyLimiterOptions
+ {
+ PermitLimit = 10,
+ QueueLimit = 10
+ });
+
+ // Convenience extension method with custom limiter creation
+ builder.AddRateLimiter(
+ new ConcurrencyLimiter(new ConcurrencyLimiterOptions
+ {
+ PermitLimit = 10,
+ QueueLimit = 10
+ }));
+
+ // Add rate limiter using the RateLimiterStrategyOptions
+ var limiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions
+ {
+ PermitLimit = 10,
+ QueueLimit = 10
+ });
+
+ builder.AddRateLimiter(new RateLimiterStrategyOptions
+ {
+ RateLimiter = args => limiter.AcquireAsync(1, args.Context.CancellationToken),
+ OnRejected = _ =>
+ {
+ Console.WriteLine("Rate limiter rejected!");
+ return default;
+ }
+ });
+
+ #endregion
+ }
+}
diff --git a/src/Snippets/Snippets.csproj b/src/Snippets/Snippets.csproj
new file mode 100644
index 00000000000..cedda64e529
--- /dev/null
+++ b/src/Snippets/Snippets.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Library
+ net7.0
+ enable
+ enable
+ Library
+ false
+ false
+ $(NoWarn);SA1123;SA1515;CA2000;CA2007;CA1303;IDE0021
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Snippets/Testing/Snippets.cs b/src/Snippets/Testing/Snippets.cs
new file mode 100644
index 00000000000..11e2a6539fb
--- /dev/null
+++ b/src/Snippets/Testing/Snippets.cs
@@ -0,0 +1,40 @@
+using Microsoft.Extensions.Logging.Abstractions;
+using Polly;
+using Polly.Retry;
+using Polly.Testing;
+using Polly.Timeout;
+using Xunit;
+
+namespace Snippets.Testing;
+
+internal static class Snippets
+{
+ public static void GetPipelineDescriptor()
+ {
+ #region get-pipeline-descriptor
+
+ // Build your resilience pipeline.
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddRetry(new RetryStrategyOptions
+ {
+ MaxRetryAttempts = 4
+ })
+ .AddTimeout(TimeSpan.FromSeconds(1))
+ .ConfigureTelemetry(NullLoggerFactory.Instance)
+ .Build();
+
+ // Retrieve inner strategies.
+ ResiliencePipelineDescriptor descriptor = pipeline.GetPipelineDescriptor();
+
+ // Assert the composition.
+ Assert.Equal(2, descriptor.Strategies.Count);
+
+ var retryOptions = Assert.IsType(descriptor.Strategies[0].Options);
+ Assert.Equal(4, retryOptions.MaxRetryAttempts);
+
+ var timeoutOptions = Assert.IsType(descriptor.Strategies[0].Options);
+ Assert.Equal(TimeSpan.FromSeconds(1), timeoutOptions.Timeout);
+
+ #endregion
+ }
+}