The PoliNorError.Extensions.DependencyInjection package extends PoliNorError library to provide integration with Microsoft Dependency Injection.
Get up and running in 3 simple steps:
// Program.cs
services.AddPoliNorError(
Assembly.GetExecutingAssembly());This scans your assembly for all IPolicyBuilder<> implementations and wires up IPolicy<T> automatically.
public class SomePolicyBuilder : IPolicyBuilder<SomePolicyBuilder>
{
private readonly ILogger<SomePolicyBuilder> _logger;
public SomePolicyBuilder(ILogger<SomePolicyBuilder> logger)
{
_logger = logger;
}
public IPolicyBase Build()
{
return new RetryPolicy(3)
.WithErrorProcessor(new RetryLoggingErrorProcessor(_logger))
.WithWait(new TimeSpan(0, 0, 3));
}
}Another example:
public class AnotherPolicyBuilder : IPolicyBuilder<AnotherPolicyBuilder>
{
private readonly ILogger<AnotherPolicyBuilder> _logger;
public AnotherPolicyBuilder(ILogger<AnotherPolicyBuilder> logger)
{
_logger = logger;
}
public IPolicyBase Build()
{
return new RetryPolicy(2)
.WithErrorProcessor(new RetryLoggingErrorProcessor(_logger))
.WithWait(new TimeSpan(0, 0, 1));
}
}, where RetryLoggingErrorProcessor:
public class RetryLoggingErrorProcessor : ErrorProcessor
{
private readonly ILogger _logger;
public RetryLoggingErrorProcessor(ILogger logger)
{
_logger = logger;
}
public override void Execute(Exception error,
ProcessingErrorInfo? catchBlockProcessErrorInfo = null,
CancellationToken token = default)
{
_logger.LogError(error,
"An error occurred while doing work on {Attempt} attempt.",
catchBlockProcessErrorInfo.GetAttemptCount());
}
}public class Worker
{
private readonly IPolicy<SomePolicyBuilder> _somePolicy;
private readonly IPolicy<AnotherPolicyBuilder> _anotherPolicy;
public Worker(IPolicy<SomePolicyBuilder> somePolicy,
IPolicy<AnotherPolicyBuilder> anotherPolicy)
{
_somePolicy = somePolicy;
_anotherPolicy = anotherPolicy;
}
public async Task DoWorkAsync(CancellationToken token)
{
await _somePolicy.HandleAsync(MightThrowAsync, token);
await _anotherPolicy.HandleAsync(MightThrowAsync, token);
}
private async Task MightThrowAsync(CancellationToken token)
{
await Task.Delay(100, token);
throw new SomeException("Something went wrong.");
}
}✅ That’s it!
- Builders encapsulate configuration.
- Consumers inject
IPolicy<T>and just use it. - DI takes care of wiring everything together.
-
IPolicyBuilder
- Implemented only in your builders.
- A builder abstraction for creating policies.
- Encapsulates configuration (retry count, wait strategy, error processors, etc.).
- Registered automatically into DI via assembly scanning.
-
IPolicy
- Consumed only in your services.
- A closed generic wrapper that represents a policy built by a specific builder.
- Resolved directly from DI, giving consumers a type-safe handle to the correct policy.
- Internally backed by
ProxyPolicy<T>which delegates to the builder’sBuild()result.
-
Automatic DI Registration
AddPoliNorError()scans assemblies for allIPolicyBuilder<>implementations.- Registers them and wires up
IPolicy<T>→ProxyPolicy<T>automatically.
- You create builder classes that implement
IPolicyBuilder<TBuilder>. AddPoliNorErrorregisters the open generic mappingIPolicy<> -> ProxyPolicy<>.- When a consumer requests
IPolicy<TBuilder>, DI resolvesProxyPolicy<TBuilder>. - The proxy calls the builder’s
Build()method to produce the actual policy. - All calls (
Handle,HandleAsync, etc.) are delegated to the built policy.
- Type-safe DI: No string keys or manual lookups.
- Separation of concerns: Builders configure, consumers execute.
- Discoverable: Constructor injection makes dependencies explicit.
- Testable: Swap out builders or inject fake policies in tests.
- Extensible: Add new PoliNorError policies by just adding new builders.
For more complex scenarios, PoliNorError.Extensions.DependencyInjection supports an advanced pattern that separates policy creation from policy configuration.
PolicyConfigurator<TPolicy>— an abstract base class for encapsulating cross‑cutting configuration logic (logging, enrichment, etc.).PolicyBuilder<TPolicy, TConfigurator>— an abstract base class that encapsulates policy creation and optional configurator wiring.
Create a subclass of PolicyConfigurator<TPolicy> and override the Configure method, where TPolicy is a policy from PoliNorError library.
Inheritors of PolicyConfigurator are automatically resolved from DI.
Create a subclass of PolicyBuilder<TPolicy, TConfigurator> and override the CreatePolicy method, where TPolicy is a policy from PoliNorError library, and TConfigurator inherits from PolicyConfigurator<TPolicy>.
Use custom builders + configurators when:
- You want policy creation and policy configuration to be separate concerns.
- You want to reuse the same configurator across multiple builders.
- You want to keep your builder classes minimal and declarative.
public class RetryPolicyConfigurator : PolicyConfigurator<RetryPolicy>
{
private readonly ILoggerFactory _loggerFactory;
public RetryPolicyConfigurator(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public override void Configure(RetryPolicy policy)
=> policy.WithErrorProcessor(
new RetryLoggingErrorProcessor(_loggerFactory.CreateLogger(policy.PolicyName)));
}This configurator:
- Receives dependencies via DI (here:
ILoggerFactory) - Adds a logging error processor to the policy
- Uses the policy name to create a dedicated logger
public class SomePolicyBuilder : PolicyBuilder<RetryPolicy, RetryPolicyConfigurator>, IPolicyBuilder<SomePolicyBuilder>
{
protected override RetryPolicy CreatePolicy() =>
new RetryPolicy(3, retryDelay: ConstantRetryDelay.Create(new TimeSpan(0, 0, 3)))
.WithPolicyName("SomeRetryPolicy");
}This builder:
- Creates a
RetryPolicywith a fixed delay. - Assigns a policy name (used later by the configurator).
- Delegates configuration to
RetryPolicyConfigurator.
Once created, the configurator (a subclass of PolicyConfigurator) can be shared across multiple builders:
public class AnotherPolicyBuilder : PolicyBuilder<RetryPolicy, RetryPolicyConfigurator>, IPolicyBuilder<AnotherPolicyBuilder>
{
protected override RetryPolicy CreatePolicy() =>
new RetryPolicy(2, retryDelay: ConstantRetryDelay.Create(new TimeSpan(0, 0, 1)))
.WithPolicyName("AnotherRetryPolicy");
}- Single Responsibility Principle: Each class has one clear responsibility
- Reusability: Configurators can be shared across multiple policy builders
- Testability: Configurators and builders can be tested independently
- Maintainability: Changes to configuration logic don't affect creation logic and vice versa
See samples folder for concrete example.
