< Summary

Information
Class: NexusLabs.Needlr.ServiceCollectionExtensions
Assembly: NexusLabs.Needlr
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr/ServiceCollectionExtensions.cs
Line coverage
85%
Covered lines: 72
Uncovered lines: 12
Coverable lines: 84
Total lines: 277
Line coverage: 85.7%
Branch coverage
73%
Covered branches: 22
Total branches: 30
Branch coverage: 73.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddDecorator(...)100%88100%
CreateDecoratedDescriptor(...)75%4485.71%
AddDecorator(...)83.33%6686.95%
GetServiceRegistrations(...)100%11100%
GetServiceRegistrations(...)100%11100%
IsRegistered(...)100%11100%
IsRegistered(...)100%11100%
CreateOriginalService(...)83.33%6685.71%
CreateOriginalService(...)16.66%19628.57%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr/ServiceCollectionExtensions.cs

#LineLine coverage
 1using System.Diagnostics.CodeAnalysis;
 2
 3using Microsoft.Extensions.DependencyInjection;
 4
 5namespace NexusLabs.Needlr;
 6
 7/// <summary>
 8/// Extension methods for <see cref="IServiceCollection"/> to provide decorator functionality.
 9/// </summary>
 10public static class ServiceCollectionExtensions
 11{
 12    /// <summary>
 13    /// Decorates an existing service registration with a decorator type, preserving the original service's lifetime.
 14    /// The decorator must implement the service interface and take the service interface as a constructor parameter.
 15    /// Works with both interfaces and class types.
 16    /// </summary>
 17    /// <typeparam name="TService">The service type (interface or class) to decorate.</typeparam>
 18    /// <typeparam name="TDecorator">The decorator type that implements TService.</typeparam>
 19    /// <param name="services">The service collection to modify.</param>
 20    /// <returns>The service collection for method chaining.</returns>
 21    /// <exception cref="ArgumentNullException">Thrown when services is null.</exception>
 22    /// <exception cref="InvalidOperationException">Thrown when no service registration is found for TService.</exceptio
 23    /// <example>
 24    /// <code>
 25    /// // Register the original service
 26    /// services.AddScoped&lt;IMyService, MyService&gt;();
 27    ///
 28    /// // Decorate it while preserving the scoped lifetime
 29    /// services.AddDecorator&lt;IMyService, MyServiceDecorator&gt;();
 30    /// </code>
 31    /// </example>
 32    public static IServiceCollection AddDecorator<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.P
 33        where TDecorator : class, TService
 34    {
 244335        ArgumentNullException.ThrowIfNull(services);
 36
 37        // Find ALL existing service registrations for this type
 244238        var existingDescriptors = services
 58010139            .Where(d => d.ServiceType == typeof(TService))
 244240            .ToList();
 41
 244242        if (existingDescriptors.Count == 0)
 43        {
 144            throw new InvalidOperationException(
 145                $"No service registration found for type {typeof(TService).Name}. " +
 146                $"Please register the service before decorating it.");
 47        }
 48
 49        // Remove all existing registrations
 1124850        foreach (var descriptor in existingDescriptors)
 51        {
 318352            services.Remove(descriptor);
 53        }
 54
 55        // Create decorated registrations for each, preserving order and lifetime
 1124856        foreach (var existingDescriptor in existingDescriptors)
 57        {
 318358            var decoratedDescriptor = CreateDecoratedDescriptor<TService, TDecorator>(existingDescriptor);
 318359            services.Add(decoratedDescriptor);
 60        }
 61
 244162        return services;
 63    }
 64
 65    private static ServiceDescriptor CreateDecoratedDescriptor<TService, [DynamicallyAccessedMembers(DynamicallyAccessed
 66        ServiceDescriptor existingDescriptor)
 67        where TDecorator : class, TService
 68    {
 318369        return existingDescriptor.Lifetime switch
 318370        {
 317371            ServiceLifetime.Singleton => new ServiceDescriptor(typeof(TService), provider =>
 317372            {
 13873                var originalService = CreateOriginalService<TService>(provider, existingDescriptor);
 13874                return ActivatorUtilities.CreateInstance<TDecorator>(provider, originalService!);
 317375            }, ServiceLifetime.Singleton),
 776            ServiceLifetime.Scoped => new ServiceDescriptor(typeof(TService), provider =>
 777            {
 1378                var originalService = CreateOriginalService<TService>(provider, existingDescriptor);
 1379                return ActivatorUtilities.CreateInstance<TDecorator>(provider, originalService!);
 780            }, ServiceLifetime.Scoped),
 381            ServiceLifetime.Transient => new ServiceDescriptor(typeof(TService), provider =>
 382            {
 583                var originalService = CreateOriginalService<TService>(provider, existingDescriptor);
 584                return ActivatorUtilities.CreateInstance<TDecorator>(provider, originalService!);
 385            }, ServiceLifetime.Transient),
 086            _ => throw new InvalidOperationException(
 087                $"Unsupported service lifetime '{existingDescriptor.Lifetime}' " +
 088                $"for '{typeof(TService)}'.")
 318389        };
 90    }
 91
 92    /// <summary>
 93    /// Decorates an existing service registration with a decorator type, preserving the original service's lifetime.
 94    /// The decorator must implement the service interface and take the service interface as a constructor parameter.
 95    /// Works with both interfaces and class types.
 96    /// </summary>
 97    /// <param name="services">The service collection to modify.</param>
 98    /// <param name="serviceType">The service type (interface or class) to decorate.</param>
 99    /// <param name="decoratorType">The decorator type that implements the service type.</param>
 100    /// <returns>The service collection for method chaining.</returns>
 101    /// <exception cref="ArgumentNullException">Thrown when services, serviceType, or decoratorType is null.</exception>
 102    /// <exception cref="InvalidOperationException">Thrown when no service registration is found for the service type.</
 103    /// <example>
 104    /// <code>
 105    /// // Register the original service
 106    /// services.AddScoped&lt;IMyService, MyService&gt;();
 107    ///
 108    /// // Decorate it while preserving the scoped lifetime
 109    /// services.AddDecorator(typeof(IMyService), typeof(MyServiceDecorator));
 110    /// </code>
 111    /// </example>
 112    public static IServiceCollection AddDecorator(
 113        this IServiceCollection services,
 114        Type serviceType,
 115        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type decoratorType)
 116    {
 1230117        ArgumentNullException.ThrowIfNull(services);
 1230118        ArgumentNullException.ThrowIfNull(serviceType);
 1230119        ArgumentNullException.ThrowIfNull(decoratorType);
 120
 121        // Find ALL existing service registrations for this type
 1230122        var existingDescriptors = services
 327744123            .Where(d => d.ServiceType == serviceType)
 1230124            .ToList();
 125
 1230126        if (existingDescriptors.Count == 0)
 127        {
 0128            throw new InvalidOperationException(
 0129                $"No service registration found for type {serviceType.Name}. " +
 0130                $"Please register the service before decorating it.");
 131        }
 132
 133        // Remove all existing registrations
 6150134        foreach (var descriptor in existingDescriptors)
 135        {
 1845136            services.Remove(descriptor);
 137        }
 138
 139        // Create decorated registrations for each, preserving order and lifetime
 6150140        foreach (var existingDescriptor in existingDescriptors)
 141        {
 1845142            var decoratedDescriptor = new ServiceDescriptor(
 1845143                serviceType,
 1845144                provider =>
 1845145                {
 81146                    var originalService = CreateOriginalService(provider, existingDescriptor, serviceType);
 81147                    return ActivatorUtilities.CreateInstance(provider, decoratorType, originalService!);
 1845148                },
 1845149                existingDescriptor.Lifetime);
 1845150            services.Add(decoratedDescriptor);
 151        }
 152
 1230153        return services;
 154    }
 155
 156    /// <summary>
 157    /// Gets detailed information about all registered services.
 158    /// </summary>
 159    /// <param name="serviceCollection">The service provider to inspect.</param>
 160    /// <returns>A read-only list of service registration information.</returns>
 161    /// <exception cref="ArgumentNullException">Thrown when serviceCollection is null.</exception>
 162    /// <example>
 163    /// <code>
 164    /// // Get all singleton services
 165    /// var singletons = serviceCollection.GetServiceRegistrations(
 166    ///     descriptor => descriptor.Lifetime == ServiceLifetime.Singleton);
 167    ///
 168    /// // Get all services with a specific implementation type
 169    /// var specificImpls = serviceCollection.GetServiceRegistrations(
 170    ///     descriptor => descriptor.ImplementationType == typeof(MyService));
 171    /// </code>
 172    /// </example>
 173    public static IReadOnlyList<ServiceRegistrationInfo> GetServiceRegistrations(
 174        this IServiceCollection serviceCollection)
 175    {
 6176        ArgumentNullException.ThrowIfNull(serviceCollection);
 177
 11178        return serviceCollection.GetServiceRegistrations(_ => true);
 179    }
 180
 181    /// <summary>
 182    /// Gets detailed information about all registered services that match the specified predicate.
 183    /// </summary>
 184    /// <param name="serviceCollection">The service provider to inspect.</param>
 185    /// <param name="predicate">A function to filter the service descriptors.</param>
 186    /// <returns>A read-only list of service registration information.</returns>
 187    /// <exception cref="ArgumentNullException">Thrown when serviceCollection or predicate is null.</exception>
 188    /// <example>
 189    /// <code>
 190    /// // Get all singleton services
 191    /// var singletons = serviceCollection.GetServiceRegistrations(
 192    ///     descriptor => descriptor.Lifetime == ServiceLifetime.Singleton);
 193    ///
 194    /// // Get all services with a specific implementation type
 195    /// var specificImpls = serviceCollection.GetServiceRegistrations(
 196    ///     descriptor => descriptor.ImplementationType == typeof(MyService));
 197    /// </code>
 198    /// </example>
 199    public static IReadOnlyList<ServiceRegistrationInfo> GetServiceRegistrations(
 200        this IServiceCollection serviceCollection,
 201        Func<ServiceDescriptor, bool> predicate)
 202    {
 65203        ArgumentNullException.ThrowIfNull(serviceCollection);
 64204        ArgumentNullException.ThrowIfNull(predicate);
 205
 63206        return serviceCollection
 63207            .Where(predicate)
 2349208            .Select(descriptor => new ServiceRegistrationInfo(descriptor))
 63209            .ToArray();
 210    }
 211
 212    /// <summary>
 213    /// Determines whether a service of the specified type is registered in the service collection.
 214    /// </summary>
 215    /// <typeparam name="TService">The service type to check.</typeparam>
 216    /// <param name="services">The service collection to check.</param>
 217    /// <returns>True if the service is registered; otherwise, false.</returns>
 218    public static bool IsRegistered<TService>(this IServiceCollection services)
 219    {
 5220        ArgumentNullException.ThrowIfNull(services);
 4221        return services.IsRegistered(typeof(TService));
 222    }
 223
 224    /// <summary>
 225    /// Determines whether a service of the specified type is registered in the service collection.
 226    /// </summary>
 227    /// <param name="services">The service collection to check.</param>
 228    /// <param name="serviceType">The service type to check.</param>
 229    /// <returns>True if the service is registered; otherwise, false.</returns>
 230    public static bool IsRegistered(
 231        this IServiceCollection services,
 232        Type serviceType)
 233    {
 12234        ArgumentNullException.ThrowIfNull(services);
 23235        return services.Any(d => d.ServiceType == serviceType);
 236    }
 237
 238    private static TService CreateOriginalService<TService>(IServiceProvider provider, ServiceDescriptor originalDescrip
 239    {
 156240        if (originalDescriptor.ImplementationFactory is not null)
 241        {
 102242            return (TService)originalDescriptor.ImplementationFactory(provider);
 243        }
 244
 54245        if (originalDescriptor.ImplementationInstance is not null)
 246        {
 1247            return (TService)originalDescriptor.ImplementationInstance;
 248        }
 249
 53250        if (originalDescriptor.ImplementationType is not null)
 251        {
 53252            return (TService)ActivatorUtilities.CreateInstance(provider, originalDescriptor.ImplementationType);
 253        }
 254
 0255        throw new InvalidOperationException($"Unable to create instance of service {typeof(TService).Name} from the orig
 256    }
 257
 258    private static object CreateOriginalService(IServiceProvider provider, ServiceDescriptor originalDescriptor, Type se
 259    {
 81260        if (originalDescriptor.ImplementationFactory is not null)
 261        {
 81262            return originalDescriptor.ImplementationFactory(provider);
 263        }
 264
 0265        if (originalDescriptor.ImplementationInstance is not null)
 266        {
 0267            return originalDescriptor.ImplementationInstance;
 268        }
 269
 0270        if (originalDescriptor.ImplementationType is not null)
 271        {
 0272            return ActivatorUtilities.CreateInstance(provider, originalDescriptor.ImplementationType);
 273        }
 274
 0275        throw new InvalidOperationException($"Unable to create instance of service {serviceType.Name} from the original 
 276    }
 277}