• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Aldaviva / Unfucked / 19430876270

17 Nov 2025 01:13PM UTC coverage: 47.301% (-0.5%) from 47.79%
19430876270

push

github

Aldaviva
DI: fix super registration with hosted services where another hosted service also depends on this hosted service, which would cause an infinite loop due to a three-node cyclic dependency at injection time. Fixed by using the concrete class instance instead of the IHostedService instance, or make a new keyed service if no concrete class service was requested.

632 of 1673 branches covered (37.78%)

31 of 51 new or added lines in 2 files covered. (60.78%)

8 existing lines in 2 files now uncovered.

1139 of 2408 relevant lines covered (47.3%)

50.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.71
/DI/DependencyInjectionExtensions.SuperRegistration.cs
1
using Microsoft.Extensions.DependencyInjection;
2
using Microsoft.Extensions.Hosting;
3
using System.Reflection;
4
using System.Runtime.Serialization;
5
using Unfucked.DI;
6

7
namespace Unfucked;
8

9
public static partial class DependencyInjectionExtensions {
10

11
    private static readonly IEnumerable<Type> INTERFACE_REGISTRATION_BLACKLIST = [
1✔
12
        typeof(IDisposable),
1✔
13
        typeof(IAsyncDisposable),
1✔
14
        typeof(ICloneable),
1✔
15
        typeof(ISerializable),
1✔
16
        typeof(IHostedService) // if you want to register a class as a hosted service and also its own interfaces, call Services.AddHostedService<MyHostedService>(SuperRegistration.INTERFACES)
1✔
17
    ];
1✔
18

19
    #region Normal registrations
20

21
    /// <summary>
22
    /// <para>Register a class in the dependency injection context, and also register it so it can be injected as any of its interfaces or superclasses, like Spring does by default and Autofac optionally allows.</para>
23
    /// <para>For example, if <c>MyClass</c> implements <c>IMyInterface</c> and extends <c>MySuperClass</c>, this allows you to easily register <c>MyClass</c> in DI and inject either <c>IMyInterface</c> or <c>MySuperClass</c> into a service's constructor, without injection casting or duplicative unrefactorable registration clutter.</para>
24
    /// </summary>
25
    /// <typeparam name="TImpl">Type of the class to register</typeparam>
26
    /// <param name="services"><see cref="IHostApplicationBuilder.Services"/> or similar</param>
27
    /// <param name="alsoRegister">Also register the class as its own concrete class, all of its extended superclasses, or all of its implemented interfaces, or <see cref="SuperRegistration.NONE"/> to only register it as its own type (default <c>Microsoft.Extensions.DependencyInjection</c> behavior). A union of multiple values can be passed with logical OR (<see cref="SuperRegistration.SUPERCLASSES"/><c> | </c><see cref="SuperRegistration.INTERFACES"/>).</param>
28
    /// <returns>The same collection of service registrations, for chained calls</returns>
29
    public static IServiceCollection AddSingleton<TImpl>(this IServiceCollection services, SuperRegistration alsoRegister) where TImpl: class =>
30
        Add<TImpl>(services, ServiceLifetime.Singleton, alsoRegister);
1✔
31

32
    /// <inheritdoc cref="AddSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,SuperRegistration)" />
33
    /// <param name="instance">One concrete singleton instance that will always be returned when these types are injected</param>
34
    public static IServiceCollection AddSingleton<TImpl>(this IServiceCollection services, TImpl instance, SuperRegistration alsoRegister) where TImpl: class =>
35
        Add(services, alsoRegister, instance);
×
36

37
    /// <inheritdoc cref="AddSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,SuperRegistration)" />
38
    /// <param name="factory">Function that creates instances of <typeparamref name="TImpl"/></param>
39
    public static IServiceCollection AddSingleton<TImpl>(this IServiceCollection services, Func<IServiceProvider, TImpl> factory, SuperRegistration alsoRegister) where TImpl: class =>
40
        Add(services, ServiceLifetime.Singleton, alsoRegister, factory);
×
41

42
    /// <inheritdoc cref="AddSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,SuperRegistration)" />
43
    public static IServiceCollection AddTransient<TImpl>(this IServiceCollection services, SuperRegistration alsoRegister) where TImpl: class =>
44
        Add<TImpl>(services, ServiceLifetime.Transient, alsoRegister);
×
45

46
    /// <inheritdoc cref="AddSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,SuperRegistration)" />
47
    /// <param name="factory">Function that creates instances of <typeparamref name="TImpl"/></param>
48
    public static IServiceCollection AddTransient<TImpl>(this IServiceCollection services, Func<IServiceProvider, TImpl> factory, SuperRegistration alsoRegister) where TImpl: class =>
49
        Add(services, ServiceLifetime.Transient, alsoRegister, factory);
×
50

51
    /// <inheritdoc cref="AddSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,SuperRegistration)" />
52
    public static IServiceCollection AddScoped<TImpl>(this IServiceCollection services, SuperRegistration alsoRegister) where TImpl: class =>
53
        Add<TImpl>(services, ServiceLifetime.Scoped, alsoRegister);
×
54

55
    /// <inheritdoc cref="AddSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,SuperRegistration)" />
56
    /// <param name="factory">Function that creates instances of <typeparamref name="TImpl"/></param>
57
    public static IServiceCollection AddScoped<TImpl>(this IServiceCollection services, Func<IServiceProvider, TImpl> factory, SuperRegistration alsoRegister) where TImpl: class =>
58
        Add(services, ServiceLifetime.Scoped, alsoRegister, factory);
×
59

60
    #endregion
61

62
    #region Hosted services
63

64
    /// <inheritdoc cref="AddSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,SuperRegistration)" />
65
    public static IServiceCollection AddHostedService<TImpl>(this IServiceCollection services, SuperRegistration alsoRegister) where TImpl: class, IHostedService {
66
        if ((alsoRegister & SuperRegistration.CONCRETE_CLASS) != 0) {
3✔
67
            return Add<TImpl>(services, alsoRegister & ~SuperRegistration.CONCRETE_CLASS, () => [
4✔
68
                new ServiceDescriptor(typeof(TImpl), typeof(TImpl), ServiceLifetime.Singleton),
4✔
69
                new ServiceDescriptor(typeof(IHostedService), ServiceProvider<TImpl>, ServiceLifetime.Singleton)
4✔
70
            ], extra => new ServiceDescriptor(extra, ServiceProvider<TImpl>, ServiceLifetime.Singleton));
5!
71
        } else {
72
            Guid concreteClassKey = Guid.NewGuid();
1✔
73
            return Add<TImpl>(services, alsoRegister, () => [
2✔
74
                new ServiceDescriptor(typeof(TImpl), concreteClassKey, typeof(TImpl), ServiceLifetime.Singleton),
2✔
75
                new ServiceDescriptor(typeof(IHostedService), provider => KeyedServiceProvider<TImpl>(provider, concreteClassKey), ServiceLifetime.Singleton)
1✔
76
            ], extra => new ServiceDescriptor(extra, provider => KeyedServiceProvider<TImpl>(provider, concreteClassKey), ServiceLifetime.Singleton));
4✔
77
        }
78
    }
79

80
    /// <inheritdoc cref="AddSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,SuperRegistration)" />
81
    /// <param name="factory">Function that creates instances of <typeparamref name="TImpl"/></param>
82
    public static IServiceCollection AddHostedService<TImpl>(this IServiceCollection services, Func<IServiceProvider, TImpl> factory, SuperRegistration alsoRegister)
83
        where TImpl: class, IHostedService {
NEW
84
        if ((alsoRegister & SuperRegistration.CONCRETE_CLASS) != 0) {
×
NEW
85
            return Add<TImpl>(services, alsoRegister & ~SuperRegistration.CONCRETE_CLASS, () => [
×
NEW
86
                new ServiceDescriptor(typeof(TImpl), factory, ServiceLifetime.Singleton),
×
NEW
87
                new ServiceDescriptor(typeof(IHostedService), ServiceProvider<TImpl>, ServiceLifetime.Singleton)
×
NEW
88
            ], extra => new ServiceDescriptor(extra, ServiceProvider<TImpl>, ServiceLifetime.Singleton));
×
89
        } else {
NEW
90
            Guid concreteClassKey = Guid.NewGuid();
×
NEW
91
            return Add<TImpl>(services, alsoRegister, () => [
×
NEW
92
                new ServiceDescriptor(typeof(TImpl), concreteClassKey, (provider, _) => factory(provider), ServiceLifetime.Singleton),
×
NEW
93
                new ServiceDescriptor(typeof(IHostedService), provider => KeyedServiceProvider<TImpl>(provider, concreteClassKey), ServiceLifetime.Singleton)
×
NEW
94
            ], extra => new ServiceDescriptor(extra, provider => KeyedServiceProvider<TImpl>(provider, concreteClassKey), ServiceLifetime.Singleton));
×
95
        }
96
    }
97

98
    #endregion
99

100
    #region Keyed services
101

102
    /// <inheritdoc cref="AddSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,SuperRegistration)" />
103
    /// <param name="serviceKey">Key that identifies this registration, to be used when injecting using <see cref="ServiceKeyAttribute"/>.</param>
104
    public static IServiceCollection AddKeyedSingleton<TImpl>(this IServiceCollection services, object? serviceKey, SuperRegistration alsoRegister) where TImpl: class =>
105
        Add<TImpl>(services, ServiceLifetime.Singleton, alsoRegister, serviceKey);
×
106

107
    /// <inheritdoc cref="AddKeyedSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,object?,Unfucked.DI.SuperRegistration)" />
108
    /// <param name="instance">One concrete singleton instance that will always be returned when these types are injected</param>
109
    public static IServiceCollection AddKeyedSingleton<TImpl>(this IServiceCollection services, object? serviceKey, TImpl instance, SuperRegistration alsoRegister) where TImpl: class =>
110
        Add(services, alsoRegister, instance, serviceKey);
×
111

112
    /// <inheritdoc cref="AddKeyedSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,object?,Unfucked.DI.SuperRegistration)" />
113
    /// <param name="factory">Function that creates instances of <typeparamref name="TImpl"/></param>
114
    public static IServiceCollection AddKeyedSingleton<TImpl>(this IServiceCollection services, object? serviceKey, Func<IServiceProvider, object?, TImpl> factory, SuperRegistration alsoRegister)
115
        where TImpl: class => Add(services, ServiceLifetime.Singleton, alsoRegister, factory, serviceKey);
×
116

117
    /// <inheritdoc cref="AddKeyedSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,object?,Unfucked.DI.SuperRegistration)" />
118
    public static IServiceCollection AddKeyedTransient<TImpl>(this IServiceCollection services, object? serviceKey, SuperRegistration alsoRegister) where TImpl: class =>
119
        Add<TImpl>(services, ServiceLifetime.Transient, alsoRegister, serviceKey);
1✔
120

121
    /// <inheritdoc cref="AddKeyedSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,object?,Unfucked.DI.SuperRegistration)" />
122
    /// <param name="factory">Function that creates instances of <typeparamref name="TImpl"/></param>
123
    public static IServiceCollection AddKeyedTransient<TImpl>(this IServiceCollection services, object? serviceKey, Func<IServiceProvider, object?, TImpl> factory, SuperRegistration alsoRegister)
124
        where TImpl: class => Add(services, ServiceLifetime.Transient, alsoRegister, factory, serviceKey);
×
125

126
    /// <inheritdoc cref="AddKeyedSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,object?,Unfucked.DI.SuperRegistration)" />
127
    public static IServiceCollection AddKeyedScoped<TImpl>(this IServiceCollection services, object? serviceKey, SuperRegistration alsoRegister) where TImpl: class =>
128
        Add<TImpl>(services, ServiceLifetime.Scoped, alsoRegister, serviceKey);
×
129

130
    /// <inheritdoc cref="AddKeyedSingleton{TImpl}(Microsoft.Extensions.DependencyInjection.IServiceCollection,object?,Unfucked.DI.SuperRegistration)" />
131
    /// <param name="factory">Function that creates instances of <typeparamref name="TImpl"/></param>
132
    public static IServiceCollection AddKeyedScoped<TImpl>(this IServiceCollection services, object? serviceKey, Func<IServiceProvider, object?, TImpl> factory, SuperRegistration alsoRegister)
NEW
133
        where TImpl: class => Add(services, ServiceLifetime.Scoped, alsoRegister, factory, serviceKey);
×
134

135
    #endregion
136

137
    private static IServiceCollection Add<TImpl>(IServiceCollection services, ServiceLifetime scope, SuperRegistration alsoRegister) where TImpl: class =>
138
        Add<TImpl>(services, alsoRegister, () => [new ServiceDescriptor(typeof(TImpl), typeof(TImpl), scope)],
2✔
139
            extra => scope == ServiceLifetime.Singleton ? new ServiceDescriptor(extra, ServiceProvider<TImpl>, scope) : new ServiceDescriptor(extra, typeof(TImpl), scope));
3!
140

141
    private static IServiceCollection Add<TImpl>(IServiceCollection services, SuperRegistration alsoRegister, TImpl instance) where TImpl: class =>
NEW
142
        Add<TImpl>(services, alsoRegister, () => [new ServiceDescriptor(typeof(TImpl), instance, ServiceLifetime.Singleton)],
×
NEW
143
            extra => new ServiceDescriptor(extra, _ => instance, ServiceLifetime.Singleton));
×
144

145
    private static IServiceCollection Add<TImpl>(IServiceCollection services, ServiceLifetime scope, SuperRegistration alsoRegister, Func<IServiceProvider, TImpl> factory) where TImpl: class =>
NEW
146
        Add<TImpl>(services, alsoRegister, () => [new ServiceDescriptor(typeof(TImpl), factory, scope)], extra => new ServiceDescriptor(extra, ServiceProvider<TImpl>, scope));
×
147

148
    private static IServiceCollection Add<TImpl>(IServiceCollection services, ServiceLifetime scope, SuperRegistration alsoRegister, object? serviceKey) where TImpl: class =>
149
        Add<TImpl>(services, alsoRegister, () => [new ServiceDescriptor(typeof(TImpl), serviceKey, typeof(TImpl), scope)],
2✔
150
            extra => scope == ServiceLifetime.Singleton ? new ServiceDescriptor(extra, serviceKey, KeyedServiceProvider<TImpl>, scope)
3!
151
                : new ServiceDescriptor(extra, serviceKey, typeof(TImpl), scope));
3✔
152

153
    private static IServiceCollection Add<TImpl>(IServiceCollection services, SuperRegistration alsoRegister, TImpl instance, object? serviceKey) where TImpl: class =>
NEW
154
        Add<TImpl>(services, alsoRegister, () => [new ServiceDescriptor(typeof(TImpl), serviceKey, instance)],
×
NEW
155
            extra => new ServiceDescriptor(extra, serviceKey, (_, _) => instance, ServiceLifetime.Singleton));
×
156

157
    private static IServiceCollection Add<TImpl>(IServiceCollection services, ServiceLifetime scope, SuperRegistration alsoRegister, Func<IServiceProvider, object?, TImpl> factory, object? serviceKey)
NEW
158
        where TImpl: class => Add<TImpl>(services, alsoRegister, () => [new ServiceDescriptor(typeof(TImpl), serviceKey, factory, scope)],
×
NEW
159
        extra => new ServiceDescriptor(extra, serviceKey, KeyedServiceProvider<TImpl>, scope));
×
160

161
    private static IServiceCollection Add<TImpl>(IServiceCollection services, SuperRegistration alsoRegister, Func<IEnumerable<ServiceDescriptor>> defaultRegistrations,
162
                                                 Func<Type, ServiceDescriptor> extraRegistration) where TImpl: class {
163
        List<ServiceDescriptor> registrations = [..defaultRegistrations()];
5✔
164

165
        try {
166
            if ((alsoRegister & SuperRegistration.CONCRETE_CLASS) != 0) {
5!
NEW
167
                registrations.Add(extraRegistration(typeof(TImpl)));
×
168
            }
169

170
            if ((alsoRegister & SuperRegistration.INTERFACES) != 0) {
5✔
171
                registrations.AddRange(typeof(TImpl).GetInterfaces().Except(INTERFACE_REGISTRATION_BLACKLIST).Select(extraRegistration));
4✔
172
            }
173

174
            if ((alsoRegister & SuperRegistration.SUPERCLASSES) != 0) {
5✔
175
                Type @class = typeof(TImpl);
2✔
176
                while (@class.BaseType is {} superclass && superclass != typeof(object) && superclass != typeof(ValueType)) {
4✔
177
                    registrations.Add(extraRegistration(superclass));
2✔
178
                    @class = superclass;
2✔
179
                }
2✔
180
            }
181
        } catch (TargetInvocationException) {
5✔
182
            // if an interface's static initializer is throwing an exception, it will become obvious anyway
183
        }
×
184

185
        services.AddAll(registrations); // TryAddEnumerable fails when trying to register a class as itself, such as .AddSingleton<HttpClient>()
5✔
186
        return services;
5✔
187
    }
188

189
    private static object ServiceProvider<TImpl>(IServiceProvider services) => services.GetService<TImpl>()!;
5✔
190

191
    private static object KeyedServiceProvider<TImpl>(IServiceProvider services, object? serviceKey) => services.GetKeyedService<TImpl>(serviceKey)!;
2✔
192

193
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc