Codebelt

Cuemon.Extensions.DependencyInjection

Registration helpers for Microsoft DI that forward one implementation across related contracts and coordinate options setup.

.NET 10.0 / .NET 9.0 MIT v10.5.3 95,155 downloads

Overview

Cuemon.Extensions.DependencyInjection extends Microsoft.Extensions.DependencyInjection with registration helpers that combine service lifetime selection, implementation factories, and optional options setup in a single call. Its most distinctive addition is nested type forwarding, where one primary registration can also satisfy the interfaces or marker contracts implemented by the same service.

The package also adds coordination helpers around the options pipeline and the built-in service provider. TryConfigure prevents duplicate IConfigureOptions<TOptions> registrations, PostConfigureAllOf can apply one post-configuration action across related option types, and GetServiceDescriptors exposes the descriptor list behind the default Microsoft provider when you need to inspect what was registered.

Key APIs

Add is the main overload family for registering services by implementation type or factory while choosing an explicit ServiceLifetime. The generic overloads also accept Action<TOptions> so a service registration and its options setup can be declared together.

TryAdd mirrors the Add overload family but routes through the underlying TryAddScoped, TryAddSingleton, and TryAddTransient semantics from Microsoft DI. Use it when the first matching registration should win, including the overloads that pair the service registration with an options setup delegate.

TypeForwardServiceOptions controls the forwarding behavior used by the Add<TService>(Action<TypeForwardServiceOptions>) and TryAdd<TService>(Action<TypeForwardServiceOptions>) overloads. It inherits the Lifetime setting from ServiceOptions, enables nested type forwarding by default, selects implemented interfaces by default, and validates that both selector delegates are present.

IDependencyInjectionMarker<TMarker> gives a service a marker contract that can participate in nested forwarding, and TryGetDependencyInjectionMarker lets you detect that marker from a Type. The tests show this pattern being used to keep multiple generic implementations distinct while still forwarding the primary registration to marker-based contracts.

TryConfigure registers IConfigureOptions<TOptions> only when the service collection does not already contain one for the same options type. That makes repeated registration helpers idempotent for shared options objects instead of silently stacking multiple configuration delegates.

PostConfigureAllOf walks existing IConfigureOptions<TOptions> registrations and adds matching IPostConfigureOptions<TOptions> entries for option types that implement or derive from the requested base type. It is the package hook for synchronizing related option objects after their individual configuration steps have run.

GetServiceDescriptors reflects over the built-in Microsoft service provider to return the underlying ServiceDescriptor sequence, including the root provider behind a scope. It throws NotSupportedException when the provider implementation does not expose the expected internal descriptor path.

Basic usage

using Cuemon.Extensions.DependencyInjection;
using Codebelt.Extensions.Xunit;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Contoso.Orders.Tests;

public class OrderPipelineRegistrationTest : Test
{
    public OrderPipelineRegistrationTest(ITestOutputHelper output) : base(output)
    {
    }

    [Fact]
    public void Add_ShouldForwardImplementedContracts_ToThePrimaryRegistration()
    {
        var services = new ServiceCollection()
            .Add<OrderPipeline>(o => o.Lifetime = ServiceLifetime.Singleton);

        using var provider = services.BuildServiceProvider();
        var pipeline = provider.GetRequiredService<OrderPipeline>();
        var commandHandler = provider.GetRequiredService<IOrderCommandHandler>();
        var auditTrail = provider.GetRequiredService<IOrderAuditTrail>();

        TestOutput.WriteLine($"Registered contracts: {services.Count}");

        Assert.Equal(3, services.Count);
        Assert.Same(pipeline, commandHandler);
        Assert.Same(pipeline, auditTrail);
    }
}

public interface IOrderCommandHandler { }

public interface IOrderAuditTrail { }

public sealed class OrderPipeline : IOrderCommandHandler, IOrderAuditTrail { }

Use this pattern when one DI registration should back several related contracts without repeating AddSingleton or factory wiring for each interface. It matters because the package keeps lifetime selection and forwarded contracts anchored to one primary registration, which reduces drift between the service you register and the contracts consumers resolve.

Installation

dotnet add package Cuemon.Extensions.DependencyInjection

Usage guidance

Adopt Cuemon.Extensions.DependencyInjection when Microsoft.Extensions.DependencyInjection is already your container and you need forwarded interface registrations, marker-based service selection, or idempotent options configuration on top of the default registration API. Prefer the plain framework AddScoped, AddSingleton, or AddTransient methods when a single explicit registration is already clear, and reserve GetServiceDescriptors for the built-in Microsoft provider because unsupported provider implementations cause the extension to throw.

Family packages