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

Aldaviva / Unfucked / 19128584806

06 Nov 2025 07:47AM UTC coverage: 0.396% (-39.5%) from 39.923%
19128584806

push

github

Aldaviva
DateTime: added IsBefore and IsAfter for ZonedDateTime, not just OffsetDateTime. DI: Allow super registration for keyed services; fixed super registration of a hosted service causing an infinite recursion during injection. STUN: updated fallback server list, most of which have gone offline (including Google, confusingly); made HttpClient optional.

2 of 1605 branches covered (0.12%)

0 of 55 new or added lines in 2 files covered. (0.0%)

945 existing lines in 35 files now uncovered.

9 of 2272 relevant lines covered (0.4%)

0.04 hits per line

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

0.0
/DI/DependencyInjectionExtensions.cs
1
using Microsoft.Extensions.Configuration;
2
using Microsoft.Extensions.Configuration.Json;
3
using Microsoft.Extensions.DependencyInjection;
4
using Microsoft.Extensions.DependencyInjection.Extensions;
5
using Microsoft.Extensions.FileProviders;
6
using Microsoft.Extensions.Hosting;
7
using System.Runtime.Serialization;
8
using Unfucked.DI;
9
#if !NET6_0_OR_GREATER
10
using System.Reflection;
11
using System.Diagnostics;
12
#endif
13

14
namespace Unfucked;
15

16
/// <summary>
17
/// Methods that make it easier to work with the <c>Microsoft.Extensions.Hosting</c> dependency injection/inversion of control library, which is used in the Generic Host and ASP.NET Core.
18
/// </summary>
19
public static partial class DependencyInjectionExtensions {
20

21
    private static readonly IEnumerable<Type> InterfaceRegistrationBlacklist = [
×
22
        typeof(IDisposable),
×
23
        typeof(IAsyncDisposable),
×
24
        typeof(ICloneable),
×
25
        typeof(ISerializable),
×
26
        typeof(IHostedService) // if you want to register a class as a hosted service and also its own interfaces, call Services.AddHostedService<MyHostedService>(SuperRegistration.INTERFACES)
×
27
    ];
×
28

29
    /// <summary>
30
    /// <para>By default, the .NET host only looks for configuration files in the working directory, not the installation directory, which breaks when you run the program from any other directory.</para>
31
    /// <para>Fix this by also looking for JSON configuration files in the same directory as this executable.</para>
32
    /// </summary>
33
    /// <param name="builder">see <c>HostApplicationBuilder.Configuration</c></param>
34
    /// <returns>the same <see cref="IConfigurationBuilder"/> for chaining</returns>
35
    // ExceptionAdjustment: M:System.Collections.Generic.IList`1.Insert(System.Int32,`0) -T:System.NotSupportedException
36
    // ExceptionAdjustment: P:System.Diagnostics.Process.MainModule get -T:System.ComponentModel.Win32Exception
37
    public static IConfigurationBuilder AlsoSearchForJsonFilesInExecutableDirectory(this IConfigurationBuilder builder) {
38
        string? installationDir;
39
        try {
40
            string? processPath;
41
#if NET6_0_OR_GREATER
42
            processPath = Environment.ProcessPath;
43
#else
44
            processPath = Assembly.GetEntryAssembly()?.Location;
×
45
            if (processPath == null) {
×
46
                using Process currentProcess = Process.GetCurrentProcess();
47
                processPath = currentProcess.MainModule!.FileName;
48
            }
49
#endif
UNCOV
50
            installationDir = Path.GetDirectoryName(processPath);
×
UNCOV
51
        } catch (PathTooLongException) {
×
UNCOV
52
            return builder;
×
53
        }
54

UNCOV
55
        if (installationDir != null) {
×
UNCOV
56
            PhysicalFileProvider fileProvider = new(installationDir);
×
57

UNCOV
58
            IEnumerable<(int index, IConfigurationSource source)> sourcesToAdd = builder.Sources.SelectMany<IConfigurationSource, (int, IConfigurationSource)>((src, oldIndex) =>
×
UNCOV
59
                src is JsonConfigurationSource { Path: { } path } source
×
UNCOV
60
                    ? [
×
UNCOV
61
                        (oldIndex, new JsonConfigurationSource {
×
UNCOV
62
                            FileProvider   = fileProvider,
×
UNCOV
63
                            Path           = path,
×
UNCOV
64
                            Optional       = true,
×
UNCOV
65
                            ReloadOnChange = source.ReloadOnChange,
×
UNCOV
66
                            ReloadDelay    = source.ReloadDelay
×
UNCOV
67
                        })
×
UNCOV
68
                    ]
×
UNCOV
69
                    : []).ToList();
×
70

UNCOV
71
            int sourcesAdded = 0;
×
UNCOV
72
            foreach ((int index, IConfigurationSource source) in sourcesToAdd) {
×
UNCOV
73
                builder.Sources.Insert(index + sourcesAdded++, source);
×
74
            }
75
        }
76

UNCOV
77
        return builder;
×
UNCOV
78
    }
×
79

80
    /// <summary>
81
    /// <para>Declarative injection of dependencies with shorter lifetimes into dependents with longer lifetimes, like <c>javax.inject.Provider&lt;T&gt;</c>, without the complication of creating scopes, so you don't have a inject an <see cref="IServiceProvider"/> and imperatively request everything, which isn't very DI-like.</para>
82
    /// <para>Configure dependency injection context to allow you to inject <see cref="Provider{T}"/> instances into your dependent services.</para>
83
    /// <para>This allows you to inject not an instance of a dependency service into your consumer, but rather a factory method that lazily provides the dependency service when called in your dependent service.</para>
84
    /// <para>This is useful when the lifetime of the dependent is longer than the lifetime of the dependency, and you want the dependency to get cleaned up. For example, a singleton may depend on a prototype-scoped service that must be eagerly cleaned up after it's used to avoid leaking memory, or because the dependency cannot be reused.</para>
85
    /// <para>Register: <c>
86
    /// appBuilder.Services
87
    ///     .AddInjectableProviders()
88
    ///     .AddTransient&lt;MyDependency&gt;()
89
    ///     .AddSingleton&lt;MyDependent&gt;();
90
    /// </c></para>
91
    /// <para>Inject: <c>
92
    /// public class MyDependent(Provider&lt;MyDependency&gt; dependencyProvider) {
93
    ///     public void Start() {
94
    ///         using MyDependency dependency = dependencyProvider.Get();
95
    ///         dependency.Run();
96
    ///     }
97
    /// }
98
    /// </c></para>
99
    /// </summary>
100
    /// <param name="services">Application builder's <see cref="HostApplicationBuilder.Services"/>.</param>
101
    public static IServiceCollection AddInjectableProviders(this IServiceCollection services) {
102
        services.TryAddSingleton(typeof(Provider<>), typeof(MicrosoftDependencyInjectionServiceProvider<>));
×
103
        services.TryAddSingleton(typeof(OptionalProvider<>), typeof(MicrosoftDependencyInjectionServiceProvider<>));
×
104
        return services;
×
105
    }
106

107
    /// <summary>
108
    /// <para>By default in .NET 6 and later, an uncaught exception in a <see cref="BackgroundService"/> will log a critical error and cause the host application to exit with status code 0. This makes it very difficult to automatically determine if the application crashed, such as when it's run from a script or Task Scheduler.</para>
109
    /// <para>This extension allows you to change the exit code returned by this program when it exits due to a <see cref="BackgroundService"/> throwing an exception. By default, this will return 1 on exceptions, but you can customize the exit code too. The exit code is only changed if a <see cref="BackgroundService"/> threw an exception, so the program will still exit with 0 normally.</para>
110
    /// <para>Usage:</para>
111
    /// <para><code>builder.Services.SetExitCodeOnBackgroundServiceException(1);</code></para>
112
    /// </summary>
113
    /// <param name="services">From <see cref="HostApplicationBuilder.Services"/> or similar.</param>
114
    /// <param name="exitCode">The numeric status code you want the application to exit with when a <see cref="BackgroundService"/> throws an uncaught exception. To customize the exit code for different exceptions, use the overload that takes a function for this parameter.</param>
115
    public static IServiceCollection SetExitCodeOnBackgroundServiceException(this IServiceCollection services, int exitCode = 1) => SetExitCodeOnBackgroundServiceException(services, _ => exitCode);
×
116

117
    /// <summary>
118
    /// <para>By default in .NET 6 and later, an uncaught exception in a <see cref="BackgroundService"/> will log a critical error and cause the host application to exit with status code 0. This makes it very difficult to automatically determine if the application crashed, such as when it's run from a script or Task Scheduler.</para>
119
    /// <para>This extension allows you to change the exit code returned by this program when it exits due to a <see cref="BackgroundService"/> throwing an exception. By default, this will return 1 on exceptions, but you can customize the exit code too. The exit code is only changed if a <see cref="BackgroundService"/> threw an exception, so the program will still exit with 0 normally.</para>
120
    /// <para>Usage:</para>
121
    /// <para><code>builder.Services.SetExitCodeOnBackgroundServiceException(exception => exception is ApplicationException ? 1 : 2);</code></para>
122
    /// </summary>
123
    /// <param name="services">From <see cref="HostApplicationBuilder.Services"/> or similar.</param>
124
    /// <param name="exitCodeGenerator">A function that takes the <see cref="Exception"/> thrown by the <see cref="BackgroundService"/> and returns the status code to exit this process with.</param>
125
    public static IServiceCollection SetExitCodeOnBackgroundServiceException(this IServiceCollection services, Func<Exception, int> exitCodeGenerator) {
126
        services.AddHostedService(context => new BackgroundServiceExceptionListener(context, exitCodeGenerator));
×
127
        return services;
×
128
    }
129

130
    internal class BackgroundServiceExceptionListener(IServiceProvider services, Func<Exception, int> exitCodeGenerator): BackgroundService {
×
131

132
        protected override Task ExecuteAsync(CancellationToken stoppingToken) {
133
            IEnumerable<BackgroundService> backgroundServices = services.GetServices<IHostedService>().OfType<BackgroundService>().Where(service => service is not BackgroundServiceExceptionListener);
×
134

135
            services.GetRequiredService<IHostApplicationLifetime>().ApplicationStopped.Register(() => {
×
136
                if (backgroundServices.Select(service => service.ExecuteTask?.Exception?.InnerException).FirstOrDefault() is { } exception) {
×
137
                    Environment.ExitCode = exitCodeGenerator(exception);
×
138
                }
×
139
            });
×
140

141
            return Task.CompletedTask;
×
142
        }
143

144
    }
145

146
}
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