diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 3bc9ad43e50..750a03c4571 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -18,4 +18,7 @@ updates:
# Ignore the libraries which are pinned
- dependency-name: "System.ComponentModel.Annotations"
- dependency-name: "System.Threading.Tasks.Extensions"
- - dependency-name: "System.ValueTuplens"
+ - dependency-name: "System.ValueTuple"
+ - dependency-name: "Microsoft.Extensions.Options"
+ - dependency-name: "Microsoft.Extensions.Logging.Abstractions"
+ - dependency-name: "System.Diagnostics.DiagnosticSource"
diff --git a/build.cake b/build.cake
index de503ef98d5..e3345dd6288 100644
--- a/build.cake
+++ b/build.cake
@@ -216,6 +216,7 @@ Task("__RunMutationTests")
.Does(() =>
{
TestProject(File("./src/Polly.Core/Polly.Core.csproj"), File("./src/Polly.Core.Tests/Polly.Core.Tests.csproj"), "Polly.Core.csproj");
+ TestProject(File("./src/Polly.Extensions/Polly.Extensions.csproj"), File("./src/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj"), "Polly.Extensions.csproj");
TestProject(File("./src/Polly/Polly.csproj"), File("./src/Polly.Specs/Polly.Specs.csproj"), "Polly.csproj");
void TestProject(FilePath proj, FilePath testProj, string project)
@@ -264,6 +265,9 @@ Task("__CreateSignedNuGetPackages")
Information("Building Polly.{0}.nupkg", nugetVersion);
DotNetPack(System.IO.Path.Combine(srcDir, "Polly", "Polly.csproj"), dotNetPackSettings);
+
+ Information("Building Polly.Extensions.{0}.nupkg", nugetVersion);
+ DotNetPack(System.IO.Path.Combine(srcDir, "Polly.Extensions", "Polly.Extensions.csproj"), dotNetPackSettings);
});
//////////////////////////////////////////////////////////////////////
diff --git a/eng/Common.targets b/eng/Common.targets
index e4b94f0dba5..3883e0b8f0d 100644
--- a/eng/Common.targets
+++ b/eng/Common.targets
@@ -14,7 +14,6 @@
-
+
-
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 26be7691315..6c7f5f4d342 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -6,6 +6,7 @@
+
@@ -13,9 +14,22 @@
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Polly.Core.Tests/Builder/ResilienceStrategyBuilderOptionsTests.cs b/src/Polly.Core.Tests/Builder/ResilienceStrategyBuilderOptionsTests.cs
index 5564e29921d..1b7b1952816 100644
--- a/src/Polly.Core.Tests/Builder/ResilienceStrategyBuilderOptionsTests.cs
+++ b/src/Polly.Core.Tests/Builder/ResilienceStrategyBuilderOptionsTests.cs
@@ -1,3 +1,4 @@
+using Moq;
using Polly.Builder;
using Polly.Telemetry;
using Polly.Utils;
@@ -16,4 +17,25 @@ public void Ctor_EnsureDefaults()
options.TimeProvider.Should().Be(TimeProvider.System);
options.TelemetryFactory.Should().Be(NullResilienceTelemetryFactory.Instance);
}
+
+ [Fact]
+ public void Ctor_Copy_EnsureCopied()
+ {
+ var options = new ResilienceStrategyBuilderOptions
+ {
+ BuilderName = "test",
+ TelemetryFactory = Mock.Of(),
+ TimeProvider = new FakeTimeProvider().Object
+ };
+
+ options.Properties.Set(new ResiliencePropertyKey("A"), 1);
+ options.Properties.Set(new ResiliencePropertyKey("B"), 2);
+
+ var other = new ResilienceStrategyBuilderOptions(options);
+
+ other.BuilderName.Should().Be("test");
+ other.TelemetryFactory.Should().BeSameAs(options.TelemetryFactory);
+ other.TimeProvider.Should().BeSameAs(options.TimeProvider);
+ other.Properties.Should().BeEquivalentTo(options.Properties);
+ }
}
diff --git a/src/Polly.Core.Tests/Utils/TimeProviderExtensionsTests.cs b/src/Polly.Core.Tests/Utils/TimeProviderExtensionsTests.cs
index b85d0b5dceb..0335a4de968 100644
--- a/src/Polly.Core.Tests/Utils/TimeProviderExtensionsTests.cs
+++ b/src/Polly.Core.Tests/Utils/TimeProviderExtensionsTests.cs
@@ -38,12 +38,28 @@ await TestUtils.AssertWithTimeoutAsync(async () =>
}
}
+ [Fact]
+ public async Task DelayAsync_SystemSynchronous_Ok()
+ {
+ var delay = TimeSpan.FromMilliseconds(5);
+ var context = ResilienceContext.Get();
+ context.Initialize(isSynchronous: true);
+
+ await TestUtils.AssertWithTimeoutAsync(async () =>
+ {
+ var watch = Stopwatch.StartNew();
+ await TimeProvider.System.DelayAsync(delay, context);
+ var elapsed = watch.Elapsed;
+ elapsed.Should().BeGreaterThanOrEqualTo(delay);
+ });
+ }
+
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
[Theory]
- public async Task DelayAsync_CancellationRequestedbefore_Throws(bool synchronous, bool mocked)
+ public async Task DelayAsync_CancellationRequestedBefore_Throws(bool synchronous, bool mocked)
{
using var tcs = new CancellationTokenSource();
tcs.Cancel();
diff --git a/src/Polly.Core/Builder/ResilienceStrategyBuilderOptions.cs b/src/Polly.Core/Builder/ResilienceStrategyBuilderOptions.cs
index eb514f0e30e..c0eabecc027 100644
--- a/src/Polly.Core/Builder/ResilienceStrategyBuilderOptions.cs
+++ b/src/Polly.Core/Builder/ResilienceStrategyBuilderOptions.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Polly.Telemetry;
@@ -8,6 +9,34 @@ namespace Polly.Builder;
///
public class ResilienceStrategyBuilderOptions
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ResilienceStrategyBuilderOptions()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The options to copy the values from.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ResilienceStrategyBuilderOptions(ResilienceStrategyBuilderOptions other)
+ {
+ Guard.NotNull(other);
+
+ BuilderName = other.BuilderName;
+ TelemetryFactory = other.TelemetryFactory;
+ TimeProvider = other.TimeProvider;
+
+ IDictionary props = Properties;
+
+ foreach (KeyValuePair pair in other.Properties)
+ {
+ props.Add(pair.Key, pair.Value);
+ }
+ }
+
///
/// Gets or sets the name of the builder.
///
diff --git a/src/Polly.Core/Polly.Core.csproj b/src/Polly.Core/Polly.Core.csproj
index 6c649319ef2..74de2dfa1f0 100644
--- a/src/Polly.Core/Polly.Core.csproj
+++ b/src/Polly.Core/Polly.Core.csproj
@@ -1,7 +1,7 @@
- net7.0;net6.0;netstandard2.0;net472;net461
+ net7.0;net6.0;netstandard2.0;net472;net462
Polly.Core
Polly
enable
@@ -11,6 +11,7 @@
true
100
true
+ true
diff --git a/src/Polly.Extensions.Tests/DependencyInjection/PollyDependencyInjectionKeysTests.cs b/src/Polly.Extensions.Tests/DependencyInjection/PollyDependencyInjectionKeysTests.cs
new file mode 100644
index 00000000000..a85028dfcfc
--- /dev/null
+++ b/src/Polly.Extensions.Tests/DependencyInjection/PollyDependencyInjectionKeysTests.cs
@@ -0,0 +1,12 @@
+using Polly.Extensions.DependencyInjection;
+
+namespace Polly.Extensions.Tests.DependencyInjection;
+
+public class PollyDependencyInjectionKeysTests
+{
+ [Fact]
+ public void ServiceProvider_Ok()
+ {
+ PollyDependencyInjectionKeys.ServiceProvider.Key.Should().Be("Polly.DependencyInjection.ServiceProvider");
+ }
+}
diff --git a/src/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs b/src/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs
new file mode 100644
index 00000000000..ddb785911d5
--- /dev/null
+++ b/src/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs
@@ -0,0 +1,208 @@
+using System.Globalization;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+using Polly.Builder;
+using Polly.Extensions.DependencyInjection;
+using Polly.Registry;
+using Polly.Telemetry;
+
+namespace Polly.Extensions.Tests.DependencyInjection;
+
+public class PollyServiceCollectionExtensionTests
+{
+ private const string Key = "my-strategy";
+ private ServiceCollection _services;
+
+ public PollyServiceCollectionExtensionTests() => _services = new ServiceCollection();
+
+ [Fact]
+ public void AddResilienceStrategy_ArgValidation()
+ {
+ _services = null!;
+ Assert.Throws(() => AddResilienceStrategy(Key));
+
+ _services = new ServiceCollection();
+ Assert.Throws(() => _services.AddResilienceStrategy(Key, null!));
+ }
+
+ [Fact]
+ public void AddResilienceStrategy_EnsureRegisteredServices()
+ {
+ AddResilienceStrategy(Key);
+
+ var serviceProvider = _services.BuildServiceProvider();
+
+ serviceProvider.GetServices().Should().NotBeNull();
+ serviceProvider.GetServices>().Should().NotBeNull();
+ serviceProvider.GetServices>().Should().NotBeNull();
+ serviceProvider.GetServices().Should().NotBeSameAs(serviceProvider.GetServices());
+ }
+
+ [Fact]
+ public void AddResilienceStrategy_MultipleRegistries_Ok()
+ {
+ AddResilienceStrategy(Key);
+ _services.AddResilienceStrategy(10, context => context.Builder.AddStrategy(new TestStrategy()));
+
+ var serviceProvider = _services.BuildServiceProvider();
+
+ serviceProvider.GetRequiredService>().Get(Key).Should().NotBeNull();
+ serviceProvider.GetRequiredService>().Get(10).Should().NotBeNull();
+ }
+
+ [Fact]
+ public void AddResilienceStrategy_EnsureContextFilled()
+ {
+ var asserted = false;
+
+ _services.AddResilienceStrategy(Key, context =>
+ {
+ context.Key.Should().Be(Key);
+ context.Builder.Should().NotBeNull();
+ context.ServiceProvider.Should().NotBeNull();
+ context.Builder.AddStrategy(new TestStrategy());
+ asserted = true;
+ });
+
+ CreateProvider().Get(Key);
+
+ asserted.Should().BeTrue();
+ }
+
+ [Fact]
+ public void AddResilienceStrategy_EnsureResilienceStrategyBuilderOptionsApplied()
+ {
+ var telemetry = Mock.Of();
+ var telemetryFactory = Mock.Of(v => v.Create(It.IsAny()) == telemetry);
+ var asserted = false;
+ var key = new ResiliencePropertyKey("A");
+ ResilienceStrategyBuilderOptions? globalOptions = null;
+
+ _services.Configure(options =>
+ {
+ options.BuilderName = "dummy";
+ options.TelemetryFactory = telemetryFactory;
+ options.Properties.Set(key, 123);
+ globalOptions = options;
+ });
+
+ AddResilienceStrategy(Key, context =>
+ {
+ context.BuilderProperties.Should().NotBeSameAs(globalOptions!.Properties);
+ context.BuilderName.Should().Be("dummy");
+ context.Telemetry.Should().Be(telemetry);
+ context.BuilderProperties.TryGetValue(key, out var val).Should().BeTrue();
+ val.Should().Be(123);
+ asserted = true;
+ });
+
+ CreateProvider().Get(Key);
+
+ asserted.Should().BeTrue();
+ }
+
+ [Fact]
+ public void AddResilienceStrategy_EnsureServicesNotAddedTwice()
+ {
+ AddResilienceStrategy(Key);
+ var count = _services.Count;
+
+ AddResilienceStrategy(Key);
+
+ _services.Count.Should().Be(count + 1);
+ }
+
+ [Fact]
+ public void AddResilienceStrategy_Single_Ok()
+ {
+ AddResilienceStrategy(Key);
+
+ var provider = CreateProvider();
+
+ var strategy = provider.Get(Key);
+ strategy.Should().BeOfType();
+ provider.Get("my-strategy").Should().BeSameAs(provider.Get("my-strategy"));
+ }
+
+ [Fact]
+ public void AddResilienceStrategy_Twice_LastOneWins()
+ {
+ var firstCalled = false;
+ var secondCalled = false;
+
+ AddResilienceStrategy(Key, _ => firstCalled = true);
+ AddResilienceStrategy(Key, _ => secondCalled = true);
+
+ CreateProvider().Get(Key);
+
+ firstCalled.Should().BeFalse();
+ secondCalled.Should().BeTrue();
+ }
+
+ [Fact]
+ public void AddResilienceStrategy_Multiple_Ok()
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ AddResilienceStrategy(i.ToString(CultureInfo.InvariantCulture));
+ }
+
+ var provider = CreateProvider();
+
+ Enumerable.Range(0, 10).Select(i => i.ToString(CultureInfo.InvariantCulture)).Distinct().Should().HaveCount(10);
+ }
+
+ [Fact]
+ public void AddResilienceStrategy_CustomTelemetryFactory_EnsureUsed()
+ {
+ var telemetry = new Mock(MockBehavior.Strict);
+ var factory = new Mock(MockBehavior.Strict);
+ factory.Setup(v => v.Create(It.IsAny())).Returns(telemetry.Object);
+
+ var asserted = false;
+
+ _services.AddSingleton(factory.Object);
+ _services.AddResilienceStrategy(
+ Key,
+ context =>
+ {
+ context.Builder.Options.TelemetryFactory.Should().Be(factory.Object);
+ context.Builder.AddStrategy(context =>
+ {
+ context.Telemetry.Should().Be(telemetry.Object);
+
+ asserted = true;
+ return new TestStrategy();
+ });
+ });
+
+ CreateProvider().Get(Key);
+
+ asserted.Should().BeTrue();
+ }
+
+ private void AddResilienceStrategy(string key, Action? onBuilding = null)
+ {
+ _services.AddResilienceStrategy(key, context =>
+ {
+ context.Builder.AddStrategy(context =>
+ {
+ onBuilding?.Invoke(context);
+ return new TestStrategy();
+ });
+ });
+ }
+
+ private ResilienceStrategyProvider CreateProvider()
+ {
+ return _services.BuildServiceProvider().GetRequiredService>();
+ }
+
+ private class TestStrategy : ResilienceStrategy
+ {
+ protected override ValueTask ExecuteCoreAsync(
+ Func> callback,
+ ResilienceContext context,
+ TState state) => throw new NotSupportedException();
+ }
+}
diff --git a/src/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj b/src/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj
new file mode 100644
index 00000000000..2611628c5ef
--- /dev/null
+++ b/src/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj
@@ -0,0 +1,20 @@
+
+
+ net7.0;net6.0
+ $(TargetFrameworks);net481
+ Test
+ true
+ enable
+ true
+ 100
+ $(NoWarn);SA1600;SA1204
+ [Polly.Extensions]*
+
+
+
+
+
+
+
+
+
diff --git a/src/Polly.Extensions/DependencyInjection/AddResilienceStrategyContext.cs b/src/Polly.Extensions/DependencyInjection/AddResilienceStrategyContext.cs
new file mode 100644
index 00000000000..4f75fef7fa9
--- /dev/null
+++ b/src/Polly.Extensions/DependencyInjection/AddResilienceStrategyContext.cs
@@ -0,0 +1,33 @@
+using Polly.Builder;
+
+namespace Polly.Extensions.DependencyInjection;
+
+///
+/// Represents the context for adding a resilience strategy with the specified key.
+///
+/// The type of the key used to identify the resilience strategy.
+public sealed class AddResilienceStrategyContext
+ where TKey : notnull
+{
+ internal AddResilienceStrategyContext(TKey key, ResilienceStrategyBuilder builder, IServiceProvider serviceProvider)
+ {
+ Key = key;
+ Builder = builder;
+ ServiceProvider = serviceProvider;
+ }
+
+ ///
+ /// Gets the key used to identify the resilience strategy.
+ ///
+ public TKey Key { get; }
+
+ ///
+ /// Gets the used to build the resilience strategy.
+ ///
+ public ResilienceStrategyBuilder Builder { get; }
+
+ ///
+ /// Gets the that provides access to the dependency injection container.
+ ///
+ public IServiceProvider ServiceProvider { get; }
+}
diff --git a/src/Polly.Extensions/DependencyInjection/ConfigureResilienceStrategyRegistryOptions.cs b/src/Polly.Extensions/DependencyInjection/ConfigureResilienceStrategyRegistryOptions.cs
new file mode 100644
index 00000000000..d5d143f1493
--- /dev/null
+++ b/src/Polly.Extensions/DependencyInjection/ConfigureResilienceStrategyRegistryOptions.cs
@@ -0,0 +1,9 @@
+namespace Polly.Extensions.DependencyInjection;
+
+internal sealed class ConfigureResilienceStrategyRegistryOptions
+ where TKey : notnull
+{
+ public List Actions { get; } = new();
+
+ public record Entry(TKey Key, Action> Configure);
+}
diff --git a/src/Polly.Extensions/DependencyInjection/PollyDependencyInjectionKeys.cs b/src/Polly.Extensions/DependencyInjection/PollyDependencyInjectionKeys.cs
new file mode 100644
index 00000000000..ac81a61f614
--- /dev/null
+++ b/src/Polly.Extensions/DependencyInjection/PollyDependencyInjectionKeys.cs
@@ -0,0 +1,12 @@
+namespace Polly.Extensions.DependencyInjection;
+
+///
+/// The resilience keys used in the dependency injection scenarios.
+///
+public static class PollyDependencyInjectionKeys
+{
+ ///
+ /// The key used to store and access the from .
+ ///
+ public static readonly ResiliencePropertyKey ServiceProvider = new("Polly.DependencyInjection.ServiceProvider");
+}
diff --git a/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs b/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs
new file mode 100644
index 00000000000..295b68cf843
--- /dev/null
+++ b/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs
@@ -0,0 +1,126 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
+using Polly.Builder;
+using Polly.Registry;
+using Polly.Telemetry;
+using Polly.Utils;
+
+namespace Polly.Extensions.DependencyInjection;
+
+///
+/// Provides extension methods for registering resilience strategies using the .
+///
+public static class PollyServiceCollectionExtensions
+{
+ ///
+ /// Adds a resilience strategy to service collection.
+ ///
+ /// The type of the key used to identify the resilience strategy.
+ /// The to add the resilience strategy to.
+ /// The key used to identify the resilience strategy.
+ /// An action that configures the resilience strategy.
+ /// The updated with the registered resilience strategy.
+ /// Thrown if the resilience strategy builder with the provided key has already been added to the registry.
+ ///
+ /// You can retrieve the registered strategy by resolving the class from the dependency injection container.
+ ///
+ public static IServiceCollection AddResilienceStrategy(
+ this IServiceCollection services,
+ TKey key,
+ Action> configure)
+ where TKey : notnull
+ {
+ Guard.NotNull(services);
+ Guard.NotNull(configure);
+
+ services.AddOptions();
+ services.Configure>(options =>
+ {
+ options.Actions.Add(new ConfigureResilienceStrategyRegistryOptions.Entry(key, configure));
+ });
+
+ // check marker to ensure the APIs bellow are called only once for each TKey type
+ // this prevents polluting the service collection with unnecessary Configure calls
+ if (services.Contains(RegistryMarker.ServiceDescriptor))
+ {
+ return services;
+ }
+
+ services.Add(RegistryMarker.ServiceDescriptor);
+ services.AddResilienceStrategyBuilder();
+ services.AddResilienceStrategyRegistry();
+
+ return services;
+ }
+
+ private static IServiceCollection AddResilienceStrategyRegistry(this IServiceCollection services)
+ where TKey : notnull
+ {
+ services.TryAddSingleton(serviceProvider =>
+ {
+ var options = serviceProvider.GetRequiredService>>().Value;
+ var configureActions = serviceProvider.GetRequiredService>>().Value.Actions;
+ var registry = new ResilienceStrategyRegistry(options);
+
+ foreach (var entry in configureActions)
+ {
+ // the last added builder with the same key wins, this allows overriding the builders
+ registry.RemoveBuilder(entry.Key);
+ registry.TryAddBuilder(entry.Key, (key, builder) =>
+ {
+ var context = new AddResilienceStrategyContext(key, builder, serviceProvider);
+ entry.Configure(context);
+ });
+ }
+
+ return registry;
+ });
+
+ services.TryAddSingleton>(serviceProvider =>
+ {
+ return serviceProvider.GetRequiredService>();
+ });
+
+ // configure options
+ services
+ .AddOptions>()
+ .Configure((options, serviceProvider) =>
+ {
+ options.BuilderFactory = () => serviceProvider.GetRequiredService();
+ });
+
+ return services;
+ }
+
+ private static void AddResilienceStrategyBuilder(this IServiceCollection services)
+ {
+ services
+ .AddOptions()
+ .PostConfigure((options, serviceProvider) =>
+ {
+ if (options.TelemetryFactory == NullResilienceTelemetryFactory.Instance &&
+ serviceProvider.GetService() is ResilienceTelemetryFactory factory)
+ {
+ options.TelemetryFactory = factory;
+ }
+
+ options.Properties.Set(PollyDependencyInjectionKeys.ServiceProvider, serviceProvider);
+ });
+
+ services.TryAddTransient(serviceProvider =>
+ {
+ var globalOptions = serviceProvider.GetRequiredService>().Value;
+
+ return new ResilienceStrategyBuilder
+ {
+ Options = new ResilienceStrategyBuilderOptions(globalOptions)
+ };
+ });
+ }
+
+ private class RegistryMarker
+ {
+ public static readonly ServiceDescriptor ServiceDescriptor = ServiceDescriptor.Singleton(new RegistryMarker());
+ }
+}
diff --git a/src/Polly.Extensions/Polly.Extensions.csproj b/src/Polly.Extensions/Polly.Extensions.csproj
new file mode 100644
index 00000000000..b501fcc02ae
--- /dev/null
+++ b/src/Polly.Extensions/Polly.Extensions.csproj
@@ -0,0 +1,31 @@
+
+
+ net7.0;net6.0;netstandard2.0;net472;net462
+ Polly.Extensions
+ Polly.Extensions
+ enable
+ true
+ Library
+ true
+ true
+ 100
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Polly.Extensions/README.md b/src/Polly.Extensions/README.md
new file mode 100644
index 00000000000..a4eb701cf61
--- /dev/null
+++ b/src/Polly.Extensions/README.md
@@ -0,0 +1,33 @@
+# About Polly.Hosting
+
+The `Polly.Hosting` enables the following features:
+
+
+- Integrates Polly with the standard `IServiceCollection` Dependency Injection (DI) container.
+- Implements `ResilienceTelemetryFactory` that adds [logging](https://learn.microsoft.com/dotnet/core/extensions/logging?tabs=command-line) and [metering](https://learn.microsoft.com/dotnet/core/diagnostics/metrics) for all strategies created using the DI extension points.
+
+Example:
+
+``` csharp
+var services = new ServiceCollection();
+
+// Define your strategy
+services.AddResilienceStrategy(
+ "my-key",
+ context => context.Builder.AddTimeout(TimeSpan.FromSeconds(10)));
+
+// Define your strategy using custom options
+services.AddResilienceStrategy(
+ "my-timeout",
+ context =>
+ {
+ var myOptions = context.ServiceProvider.GetRequiredService>().Value;
+ context.Builder.AddTimeout(myOptions.Timeout);
+ });
+
+// Use your strategy
+var serviceProvider = services.BuildServiceProvider();
+var strategyProvider = serviceProvider.GetRequiredService>();
+var resilienceStrategy = strategyProvider.Get("my-key");
+```
+
diff --git a/src/Polly.sln b/src/Polly.sln
index 3551a409021..9508b82d578 100644
--- a/src/Polly.sln
+++ b/src/Polly.sln
@@ -9,8 +9,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\eng\Analyzers.targets = ..\eng\Analyzers.targets
..\build.cake = ..\build.cake
..\.github\workflows\build.yml = ..\.github\workflows\build.yml
+ ..\.github\dependabot.yml = ..\.github\dependabot.yml
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
+ Directory.Packages.props = Directory.Packages.props
..\GitVersionConfig.yaml = ..\GitVersionConfig.yaml
..\global.json = ..\global.json
..\README.md = ..\README.md
@@ -37,6 +39,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eng", "eng", "{04E3C7C5-31F
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Core.Benchmarks", "Polly.Core.Benchmarks\Polly.Core.Benchmarks.csproj", "{CC306C35-E3BC-4F0B-AB8C-B9D4C82DC3DE}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Extensions", "Polly.Extensions\Polly.Extensions.csproj", "{F2FDE6BF-DA86-4DDE-A55C-E2A064CD30D8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Extensions.Tests", "Polly.Extensions.Tests\Polly.Extensions.Tests.csproj", "{BB2843CA-B518-48A1-BAD9-B63238F21608}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -67,6 +73,14 @@ Global
{CC306C35-E3BC-4F0B-AB8C-B9D4C82DC3DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC306C35-E3BC-4F0B-AB8C-B9D4C82DC3DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC306C35-E3BC-4F0B-AB8C-B9D4C82DC3DE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F2FDE6BF-DA86-4DDE-A55C-E2A064CD30D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2FDE6BF-DA86-4DDE-A55C-E2A064CD30D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2FDE6BF-DA86-4DDE-A55C-E2A064CD30D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2FDE6BF-DA86-4DDE-A55C-E2A064CD30D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BB2843CA-B518-48A1-BAD9-B63238F21608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BB2843CA-B518-48A1-BAD9-B63238F21608}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BB2843CA-B518-48A1-BAD9-B63238F21608}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BB2843CA-B518-48A1-BAD9-B63238F21608}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Polly/Polly.csproj b/src/Polly/Polly.csproj
index dd4d4255bb6..9cdd550c45b 100644
--- a/src/Polly/Polly.csproj
+++ b/src/Polly/Polly.csproj
@@ -1,7 +1,7 @@
- netstandard2.0;net472;net461;
+ netstandard2.0;net472;net462;
Polly
Library
70