Skip to content

The PoliNorError.Extensions.DependencyInjection library extends the PoliNorError library to provide integration with Microsoft Dependency Injection.

License

Notifications You must be signed in to change notification settings

kolan72/PoliNorError.Extensions.DependencyInjection

Repository files navigation

PoliNorError.Extensions.DependencyInjection

PoliNorError.Extensions.DependencyInjection

The PoliNorError.Extensions.DependencyInjection package extends PoliNorError library to provide integration with Microsoft Dependency Injection.

⚡ Quick Start

Get up and running in 3 simple steps:

1. Register policies in DI

// Program.cs
services.AddPoliNorError(
	Assembly.GetExecutingAssembly());

This scans your assembly for all IPolicyBuilder<> implementations and wires up IPolicy<T> automatically.


2. Define your policy builders

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());
	}
}

3. Consume policies in your services

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.

✨ Key Features

  • 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’s Build() result.
  • Automatic DI Registration

    • AddPoliNorError() scans assemblies for all IPolicyBuilder<> implementations.
    • Registers them and wires up IPolicy<T>ProxyPolicy<T> automatically.

🧩 How It Works

  1. You create builder classes that implement IPolicyBuilder<TBuilder>.
  2. AddPoliNorError registers the open generic mapping IPolicy<> -> ProxyPolicy<>.
  3. When a consumer requests IPolicy<TBuilder>, DI resolves ProxyPolicy<TBuilder>.
  4. The proxy calls the builder’s Build() method to produce the actual policy.
  5. All calls (Handle, HandleAsync, etc.) are delegated to the built policy.

✅ Benefits

  • 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.

🔥 Advanced Usage: Separation of Concerns with Configurators and Builders

For more complex scenarios, PoliNorError.Extensions.DependencyInjection supports an advanced pattern that separates policy creation from policy configuration.

Key Concepts:

  • 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.

✅ Inheriting from PolicyConfigurator

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.


✅ Inheriting from PolicyBuilder<TPolicy, TConfigurator>

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


✅ When to Use This Pattern

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.

🧱 Example: Retry Policy With a Custom Configurator

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 RetryPolicy with 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");
}

✅ Benefits of this approach

  • 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

🏆 Samples

See samples folder for concrete example. CSharp


About

The PoliNorError.Extensions.DependencyInjection library extends the PoliNorError library to provide integration with Microsoft Dependency Injection.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages