| | | 1 | | using Microsoft.Extensions.DependencyInjection; |
| | | 2 | | |
| | | 3 | | using NexusLabs.Needlr.Generators; |
| | | 4 | | |
| | | 5 | | using System.Reflection; |
| | | 6 | | |
| | | 7 | | namespace NexusLabs.Needlr.Injection.SourceGen.TypeRegistrars; |
| | | 8 | | |
| | | 9 | | /// <summary> |
| | | 10 | | /// A type registrar that uses compile-time generated type registry |
| | | 11 | | /// instead of runtime reflection for type discovery. |
| | | 12 | | /// </summary> |
| | | 13 | | /// <remarks> |
| | | 14 | | /// <para> |
| | | 15 | | /// This registrar is designed to work with the source generator from |
| | | 16 | | /// <c>NexusLabs.Needlr.Generators</c>. To use it: |
| | | 17 | | /// </para> |
| | | 18 | | /// <list type="number"> |
| | | 19 | | /// <item>Add the <c>NexusLabs.Needlr.Generators</c> package to your project</item> |
| | | 20 | | /// <item>Add <c>[assembly: GenerateTypeRegistry(...)]</c> attribute</item> |
| | | 21 | | /// <item>Use <c>.UsingGeneratedTypeRegistrar()</c> when building the service provider</item> |
| | | 22 | | /// </list> |
| | | 23 | | /// <para> |
| | | 24 | | /// The assemblies parameter is ignored when a type provider is supplied, |
| | | 25 | | /// as all type discovery happens at compile time. |
| | | 26 | | /// </para> |
| | | 27 | | /// </remarks> |
| | | 28 | | /// <example> |
| | | 29 | | /// <code> |
| | | 30 | | /// // In your assembly (typically Program.cs or a dedicated file): |
| | | 31 | | /// [assembly: GenerateTypeRegistry(IncludeNamespacePrefixes = new[] { "MyCompany", "NexusLabs" })] |
| | | 32 | | /// |
| | | 33 | | /// // When building the service provider: |
| | | 34 | | /// var serviceProvider = new Syringe() |
| | | 35 | | /// .UsingGeneratedTypeRegistrar() |
| | | 36 | | /// .BuildServiceProvider(config); |
| | | 37 | | /// </code> |
| | | 38 | | /// </example> |
| | | 39 | | public sealed class GeneratedTypeRegistrar : ITypeRegistrar |
| | | 40 | | { |
| | | 41 | | private readonly Func<IReadOnlyList<InjectableTypeInfo>>? _typeProvider; |
| | | 42 | | |
| | | 43 | | /// <summary> |
| | | 44 | | /// Initializes a new instance using the default generated TypeRegistry. |
| | | 45 | | /// </summary> |
| | | 46 | | /// <remarks> |
| | | 47 | | /// This constructor uses reflection to locate the generated TypeRegistry class. |
| | | 48 | | /// The generated class must be present in the calling assembly. |
| | | 49 | | /// </remarks> |
| | | 50 | | public GeneratedTypeRegistrar() |
| | 0 | 51 | | : this(null) |
| | | 52 | | { |
| | 0 | 53 | | } |
| | | 54 | | |
| | | 55 | | /// <summary> |
| | | 56 | | /// Initializes a new instance with a custom type provider. |
| | | 57 | | /// </summary> |
| | | 58 | | /// <param name="typeProvider"> |
| | | 59 | | /// A function that returns the injectable types. |
| | | 60 | | /// If null, the registrar will attempt to locate the generated TypeRegistry at runtime. |
| | | 61 | | /// </param> |
| | 275 | 62 | | public GeneratedTypeRegistrar(Func<IReadOnlyList<InjectableTypeInfo>>? typeProvider) |
| | | 63 | | { |
| | 275 | 64 | | _typeProvider = typeProvider; |
| | 275 | 65 | | } |
| | | 66 | | |
| | | 67 | | /// <inheritdoc /> |
| | | 68 | | public void RegisterTypesFromAssemblies( |
| | | 69 | | IServiceCollection services, |
| | | 70 | | ITypeFilterer typeFilterer, |
| | | 71 | | IReadOnlyList<Assembly> assemblies) |
| | | 72 | | { |
| | 259 | 73 | | var types = _typeProvider?.Invoke() ?? []; |
| | | 74 | | |
| | 72947 | 75 | | foreach (var typeInfo in types) |
| | | 76 | | { |
| | | 77 | | // Use pre-computed lifetime - no reflection needed |
| | | 78 | | // The source generator only emits types with valid lifetimes |
| | 36215 | 79 | | if (!typeInfo.Lifetime.HasValue) |
| | | 80 | | { |
| | | 81 | | continue; |
| | | 82 | | } |
| | | 83 | | |
| | 36214 | 84 | | var type = typeInfo.Type; |
| | | 85 | | |
| | | 86 | | // Check if type is excluded via Except<T>() or Except(predicate) |
| | 36214 | 87 | | if (typeFilterer.IsTypeExcluded(type)) |
| | | 88 | | { |
| | | 89 | | continue; |
| | | 90 | | } |
| | | 91 | | |
| | | 92 | | // Get effective lifetime, allowing type filterer to override pre-computed lifetime |
| | | 93 | | // This enables UsingOnlyAsTransient<T>(), UsingOnlyAsSingleton<T>(), etc. |
| | 36189 | 94 | | var defaultLifetime = ConvertToFiltererLifetime(typeInfo.Lifetime.Value); |
| | 36189 | 95 | | var effectiveLifetime = typeFilterer.GetEffectiveLifetime(type, defaultLifetime); |
| | 36189 | 96 | | var serviceLifetime = ConvertToServiceLifetime(effectiveLifetime); |
| | | 97 | | |
| | 36189 | 98 | | RegisterTypeAsSelfWithInterfaces(services, typeInfo, serviceLifetime); |
| | | 99 | | } |
| | | 100 | | |
| | | 101 | | // Apply decorators from the source generator |
| | 258 | 102 | | if (NeedlrSourceGenBootstrap.TryGetDecoratorApplier(out var decoratorApplier) && decoratorApplier is not null) |
| | | 103 | | { |
| | 252 | 104 | | decoratorApplier(services); |
| | | 105 | | } |
| | 258 | 106 | | } |
| | | 107 | | |
| | | 108 | | private static TypeFiltererLifetime ConvertToFiltererLifetime(InjectableLifetime lifetime) |
| | | 109 | | { |
| | 36189 | 110 | | return lifetime switch |
| | 36189 | 111 | | { |
| | 34747 | 112 | | InjectableLifetime.Singleton => TypeFiltererLifetime.Singleton, |
| | 721 | 113 | | InjectableLifetime.Scoped => TypeFiltererLifetime.Scoped, |
| | 721 | 114 | | InjectableLifetime.Transient => TypeFiltererLifetime.Transient, |
| | 0 | 115 | | _ => TypeFiltererLifetime.Singleton |
| | 36189 | 116 | | }; |
| | | 117 | | } |
| | | 118 | | |
| | | 119 | | private static ServiceLifetime ConvertToServiceLifetime(TypeFiltererLifetime lifetime) |
| | | 120 | | { |
| | 36189 | 121 | | return lifetime switch |
| | 36189 | 122 | | { |
| | 34737 | 123 | | TypeFiltererLifetime.Singleton => ServiceLifetime.Singleton, |
| | 721 | 124 | | TypeFiltererLifetime.Scoped => ServiceLifetime.Scoped, |
| | 731 | 125 | | TypeFiltererLifetime.Transient => ServiceLifetime.Transient, |
| | 0 | 126 | | _ => ServiceLifetime.Singleton |
| | 36189 | 127 | | }; |
| | | 128 | | } |
| | | 129 | | |
| | | 130 | | private static void RegisterTypeAsSelfWithInterfaces( |
| | | 131 | | IServiceCollection services, |
| | | 132 | | InjectableTypeInfo typeInfo, |
| | | 133 | | ServiceLifetime lifetime) |
| | | 134 | | { |
| | 36189 | 135 | | var type = typeInfo.Type; |
| | 36189 | 136 | | var factory = typeInfo.Factory; |
| | | 137 | | |
| | | 138 | | // Factory is always provided by source generator - required for AOT compatibility |
| | 36189 | 139 | | if (factory is null) |
| | | 140 | | { |
| | 1 | 141 | | throw new InvalidOperationException( |
| | 1 | 142 | | $"No factory delegate provided for type '{type.FullName}'. " + |
| | 1 | 143 | | "This indicates the type was not processed by the Needlr source generator. " + |
| | 1 | 144 | | "Ensure the type is included in the GenerateTypeRegistry namespace prefixes."); |
| | | 145 | | } |
| | | 146 | | |
| | | 147 | | // Register as self using factory (AOT-compatible, no reflection needed) |
| | 36188 | 148 | | services.Add(new ServiceDescriptor(type, factory, lifetime)); |
| | | 149 | | |
| | | 150 | | // Register as interfaces |
| | 105016 | 151 | | foreach (var interfaceType in typeInfo.Interfaces) |
| | | 152 | | { |
| | | 153 | | // For singletons, register interfaces as factory delegates to ensure same instance |
| | 16320 | 154 | | if (lifetime == ServiceLifetime.Singleton) |
| | | 155 | | { |
| | 14870 | 156 | | services.Add(new ServiceDescriptor( |
| | 14870 | 157 | | interfaceType, |
| | 135 | 158 | | serviceProvider => serviceProvider.GetRequiredService(type), |
| | 14870 | 159 | | lifetime)); |
| | | 160 | | } |
| | | 161 | | else |
| | | 162 | | { |
| | | 163 | | // For transient/scoped services, use the factory |
| | 1450 | 164 | | services.Add(new ServiceDescriptor(interfaceType, factory, lifetime)); |
| | | 165 | | } |
| | | 166 | | } |
| | | 167 | | |
| | | 168 | | // Register keyed services from [Keyed] attributes |
| | 73336 | 169 | | foreach (var serviceKey in typeInfo.ServiceKeys) |
| | | 170 | | { |
| | | 171 | | // Register keyed self |
| | 480 | 172 | | services.Add(new ServiceDescriptor(type, serviceKey, (sp, _) => factory(sp), lifetime)); |
| | | 173 | | |
| | | 174 | | // Register keyed interfaces |
| | 1920 | 175 | | foreach (var interfaceType in typeInfo.Interfaces) |
| | | 176 | | { |
| | 480 | 177 | | if (lifetime == ServiceLifetime.Singleton) |
| | | 178 | | { |
| | 480 | 179 | | services.Add(new ServiceDescriptor( |
| | 480 | 180 | | interfaceType, |
| | 480 | 181 | | serviceKey, |
| | 2 | 182 | | (sp, _) => sp.GetRequiredService(type), |
| | 480 | 183 | | lifetime)); |
| | | 184 | | } |
| | | 185 | | else |
| | | 186 | | { |
| | 0 | 187 | | services.Add(new ServiceDescriptor(interfaceType, serviceKey, (sp, _) => factory(sp), lifetime)); |
| | | 188 | | } |
| | | 189 | | } |
| | | 190 | | } |
| | 36188 | 191 | | } |
| | | 192 | | } |