< Summary

Information
Class: NexusLabs.Needlr.Injection.Bundle.ServiceProviderBuilder
Assembly: NexusLabs.Needlr.Injection.Bundle
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Injection.Bundle/ServiceProviderBuilder.cs
Line coverage
95%
Covered lines: 81
Uncovered lines: 4
Coverable lines: 85
Total lines: 170
Line coverage: 95.2%
Branch coverage
69%
Covered branches: 18
Total branches: 26
Branch coverage: 69.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)75%1616100%
GetCandidateAssemblies()100%11100%
Build(...)100%11100%
Build(...)100%11100%
Build(...)100%11100%
Build(...)75%4493.75%
ConfigurePostBuildServiceCollectionPlugins(...)50%6682.35%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Injection.Bundle/ServiceProviderBuilder.cs

#LineLine coverage
 1using Microsoft.Extensions.Configuration;
 2using Microsoft.Extensions.DependencyInjection;
 3
 4using NexusLabs.Needlr.Generators;
 5using NexusLabs.Needlr.Injection.Reflection.PluginFactories;
 6using NexusLabs.Needlr.Injection.SourceGen.PluginFactories;
 7
 8using System.Diagnostics.CodeAnalysis;
 9using System.Reflection;
 10
 11namespace NexusLabs.Needlr.Injection.Bundle;
 12
 13/// <summary>
 14/// Builds and configures an <see cref="IServiceCollection"/>, scanning assemblies for injectable types and plugins.
 15/// </summary>
 16[DoNotAutoRegister]
 17public sealed class ServiceProviderBuilder : IServiceProviderBuilder
 18{
 19    private readonly IServiceCollectionPopulator _serviceCollectionPopulator;
 20    private readonly Lazy<IReadOnlyList<Assembly>> _lazyCandidateAssemblies;
 21
 22    public ServiceProviderBuilder(
 23        IServiceCollectionPopulator serviceCollectionPopulator,
 24        IAssemblyProvider assemblyProvider) :
 1125        this(
 1126            serviceCollectionPopulator,
 1127            assemblyProvider,
 1128            additionalAssemblies: [])
 29    {
 930    }
 31
 1432    public ServiceProviderBuilder(
 1433        IServiceCollectionPopulator serviceCollectionPopulator,
 1434        IAssemblyProvider assemblyProvider,
 1435        IReadOnlyList<Assembly> additionalAssemblies)
 36    {
 1437        ArgumentNullException.ThrowIfNull(serviceCollectionPopulator);
 1338        ArgumentNullException.ThrowIfNull(assemblyProvider);
 1239        ArgumentNullException.ThrowIfNull(additionalAssemblies);
 40
 1141        _serviceCollectionPopulator = serviceCollectionPopulator;
 1142        _lazyCandidateAssemblies = new(() =>
 1143        {
 744            var staticAssemblies = assemblyProvider.GetCandidateAssemblies();
 1145            // Use assembly full name for deduplication instead of Location (Location is empty for single-file/AOT)
 746            HashSet<string> uniqueAssemblyNames = new(StringComparer.OrdinalIgnoreCase);
 747            List<Assembly> allCandidateAssemblies = new(additionalAssemblies.Count + staticAssemblies.Count);
 1148
 1149            // load the static referenced assemblies
 2050            foreach (var assembly in staticAssemblies)
 1151            {
 352                var name = assembly.FullName ?? assembly.GetName().Name ?? string.Empty;
 353                if (uniqueAssemblyNames.Add(name))
 1154                {
 355                    allCandidateAssemblies.Add(assembly);
 1156                }
 1157            }
 1158
 1159            // load any additional
 1860            foreach (var assembly in additionalAssemblies)
 1161            {
 262                var name = assembly.FullName ?? assembly.GetName().Name ?? string.Empty;
 263                if (uniqueAssemblyNames.Add(name))
 1164                {
 165                    allCandidateAssemblies.Add(assembly);
 1166                }
 1167            }
 1168
 769            return allCandidateAssemblies.Distinct().ToArray();
 1170        });
 1171    }
 72
 73    /// <inheritdoc />
 1274    public IReadOnlyList<Assembly> GetCandidateAssemblies() => _lazyCandidateAssemblies.Value;
 75
 76    /// <inheritdoc />
 77    public IServiceProvider Build(
 78        IConfiguration config) =>
 279        Build(
 280            services: new ServiceCollection(),
 281            config: config);
 82
 83    /// <inheritdoc />
 84    public IServiceProvider Build(
 85        IServiceCollection services,
 86        IConfiguration config) =>
 687        Build(
 688            services: services,
 689            config: config,
 690            postPluginRegistrationCallbacks: []);
 91
 92    /// <inheritdoc/>
 93    public IServiceProvider Build(
 94        IServiceCollection services,
 95        IConfiguration config,
 96        IReadOnlyList<Action<IServiceCollection>> postPluginRegistrationCallbacks) =>
 797        Build(services, config, [], postPluginRegistrationCallbacks);
 98
 99    /// <inheritdoc/>
 100    public IServiceProvider Build(
 101        IServiceCollection services,
 102        IConfiguration config,
 103        IReadOnlyList<Action<IServiceCollection>> preRegistrationCallbacks,
 104        IReadOnlyList<Action<IServiceCollection>> postPluginRegistrationCallbacks)
 105    {
 7106        ArgumentNullException.ThrowIfNull(services);
 6107        ArgumentNullException.ThrowIfNull(config);
 5108        ArgumentNullException.ThrowIfNull(preRegistrationCallbacks);
 5109        ArgumentNullException.ThrowIfNull(postPluginRegistrationCallbacks);
 110
 5111        services.AddSingleton(config);
 112
 10113        foreach (var callback in preRegistrationCallbacks)
 114        {
 0115            callback.Invoke(services);
 116        }
 117
 5118        _serviceCollectionPopulator.RegisterToServiceCollection(
 5119            services,
 5120            config,
 5121            GetCandidateAssemblies());
 122
 12123        foreach (var callback in postPluginRegistrationCallbacks)
 124        {
 1125            callback.Invoke(services);
 126        }
 127
 5128        var provider = services.BuildServiceProvider();
 129
 5130        ConfigurePostBuildServiceCollectionPlugins(provider, config);
 131
 5132        return provider;
 133    }
 134
 135    /// <inheritdoc />
 136    [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
 137        Justification = "PluginFactory is only used as fallback when source-gen bootstrap is not present. AOT apps use s
 138    public void ConfigurePostBuildServiceCollectionPlugins(
 139        IServiceProvider provider,
 140        IConfiguration config)
 141    {
 7142        ArgumentNullException.ThrowIfNull(provider);
 6143        ArgumentNullException.ThrowIfNull(config);
 144
 5145        var candidateAssemblies = GetCandidateAssemblies();
 146
 5147        IPluginFactory pluginFactory = NeedlrSourceGenBootstrap.TryGetProviders(out _, out var pluginTypeProvider)
 5148            ? new GeneratedPluginFactory(pluginTypeProvider, allowAllWhenAssembliesEmpty: true)
 5149            : new ReflectionPluginFactory();
 150
 5151        PostBuildServiceCollectionPluginOptions options = new(
 5152            provider,
 5153            config,
 5154            candidateAssemblies,
 5155            pluginFactory);
 156
 5157        var executedPluginTypes = new HashSet<Type>();
 10158        foreach (var plugin in pluginFactory.CreatePluginsFromAssemblies<IPostBuildServiceCollectionPlugin>(candidateAss
 159        {
 0160            var pluginType = plugin.GetType();
 0161            if (!executedPluginTypes.Add(pluginType))
 162            {
 163                // Skip duplicate plugin instances of the same type
 164                continue;
 165            }
 166
 0167            plugin.Configure(options);
 168        }
 5169    }
 170}