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

Aldaviva / Unfucked / 27924609497

20 Jun 2026 06:09AM UTC coverage: 44.453% (-1.0%) from 45.469%
27924609497

push

github

Aldaviva
DI: added UnignoreHiddenFiles and AddJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange, bool includeHidden = false). Unfucked: added StringComparison.FilesystemPaths and Paths.MatchesExtensions(string, SearchValues<string>).

664 of 1877 branches covered (35.38%)

4 of 40 new or added lines in 3 files covered. (10.0%)

4 existing lines in 1 file now uncovered.

1154 of 2596 relevant lines covered (44.45%)

180.1 hits per line

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

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

47
        if (installationDir != null && !installationDir.Equals(Environment.CurrentDirectory, StringComparison.FilesystemPaths)) {
1!
NEW
48
            PhysicalFileProvider fileProvider = new(installationDir, ExclusionFilters.None);
×
49

UNCOV
50
            IEnumerable<(int index, IConfigurationSource source)> sourcesToAdd = builder.Sources.SelectMany<IConfigurationSource, (int, IConfigurationSource)>((src, oldIndex) =>
×
NEW
51
                src is JsonConfigurationSource { Path: {} path } source ? [
×
NEW
52
                    (oldIndex, new JsonConfigurationSource {
×
NEW
53
                        FileProvider   = fileProvider,
×
NEW
54
                        Path           = path,
×
NEW
55
                        Optional       = true,
×
NEW
56
                        ReloadOnChange = source.ReloadOnChange,
×
NEW
57
                        ReloadDelay    = source.ReloadDelay
×
NEW
58
                    })
×
NEW
59
                ] : []).ToList();
×
60

UNCOV
61
            int sourcesAdded = 0;
×
UNCOV
62
            foreach ((int index, IConfigurationSource source) in sourcesToAdd) {
×
UNCOV
63
                builder.Sources.Insert(index + sourcesAdded++, source);
×
64
            }
65
        }
66

67
        return builder;
1✔
68
    }
×
69

70
    /// <summary>
71
    /// <para>Mutate all existing physical file configuration sources to allow them to load hidden, system, and dot files without ignoring them or crashing. Does not affect subsequently added configuration sources, so call this last.</para>
72
    /// <para>This won't help if it is called after a required hidden file has already been added, because that will have crashed before calling this method. To fix this, either call <see cref="AddJsonFile"/> and pass <c>true</c> as the <c>includeHidden</c> argument, or pass a custom <see cref="PhysicalFileProvider"/> to <see cref="JsonConfigurationExtensions.AddJsonFile(IConfigurationBuilder,IFileProvider,string,bool,bool)"/> with its <c>filters</c> constructor argument set to <see cref="ExclusionFilters.None"/>.</para>
73
    /// </summary>
74
    /// <param name="builder">The <c>Configuration</c> property value of the <see cref="HostApplicationBuilder"/> or other application builder.</param>
75
    /// <seealso cref="AddJsonFile"/>
76
    public static IConfigurationBuilder UnignoreHiddenFiles(this IConfigurationBuilder builder) {
NEW
77
        IDictionary<PhysicalFileProvider, PhysicalFileProvider> replacementProviders = new Dictionary<PhysicalFileProvider, PhysicalFileProvider>();
×
NEW
78
        foreach (FileConfigurationSource source in builder.Sources.OfType<FileConfigurationSource>()) {
×
NEW
79
            if (source.FileProvider is PhysicalFileProvider fileProvider && !replacementProviders.Values.Contains(fileProvider)) {
×
NEW
80
                source.FileProvider = replacementProviders.GetOrAdd(fileProvider, static replaced => new PhysicalFileProvider(replaced.Root, ExclusionFilters.None), out bool _);
×
81
            }
82
        }
NEW
83
        foreach (PhysicalFileProvider replaced in replacementProviders.Keys) {
×
NEW
84
            replaced.Dispose();
×
85
        }
NEW
86
        (builder as IConfigurationRoot)?.Reload();
×
NEW
87
        return builder;
×
88
    }
89

90
    /// <inheritdoc cref="JsonConfigurationExtensions.AddJsonFile(IConfigurationBuilder,string,bool,bool)" />
91
    /// <param name="includeHidden">If <c>false</c> (default), hidden, system, and dot files will be ignored and will not be loaded, causing a crash if <paramref name="optional"/> is <c>true</c>. Otherwise, if <c>true</c>, then hidden, system, and dot files will be treated normally and loaded properly.</param>
92
    /// <seealso cref="UnignoreHiddenFiles"/>
93
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange, bool includeHidden = false) {
NEW
94
        if (!includeHidden) {
×
NEW
95
            return JsonConfigurationExtensions.AddJsonFile(builder, path, optional, reloadOnChange);
×
96
        } else {
NEW
97
            return builder.AddJsonFile(source => {
×
NEW
98
                source.Path           = path;
×
NEW
99
                source.Optional       = optional;
×
NEW
100
                source.ReloadOnChange = reloadOnChange;
×
NEW
101
                resolveFileProvider(source);
×
NEW
102
            });
×
103
        }
104

105
        // Copied from Microsoft.Extensions.Configuration.FileConfigurationSource.ResolveFileProvider(), which stupidly hardcodes always excluding hidden files
106
        static void resolveFileProvider(JsonConfigurationSource source) {
NEW
107
            if (source.FileProvider == null && !string.IsNullOrEmpty(source.Path) && Path.IsPathRooted(source.Path)) {
×
NEW
108
                string? directory  = Path.GetDirectoryName(source.Path);
×
NEW
109
                string  pathToFile = Path.GetFileName(source.Path);
×
NEW
110
                while (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) {
×
NEW
111
                    pathToFile = Path.Combine(Path.GetFileName(directory), pathToFile);
×
NEW
112
                    directory  = Path.GetDirectoryName(directory);
×
113
                }
NEW
114
                if (Directory.Exists(directory)) {
×
NEW
115
                    source.FileProvider = new PhysicalFileProvider(directory, ExclusionFilters.None);
×
NEW
116
                    source.Path         = pathToFile;
×
117
                }
118
            }
NEW
119
        }
×
120
    }
121

122
    /// <summary>
123
    /// <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>
124
    /// <para>Configure dependency injection context to allow you to inject <see cref="Provider{T}"/> instances into your dependent services.</para>
125
    /// <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>
126
    /// <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>
127
    /// <para>Register: <c>
128
    /// appBuilder.Services
129
    ///     .AddInjectableProviders()
130
    ///     .AddTransient&lt;MyDependency&gt;()
131
    ///     .AddSingleton&lt;MyDependent&gt;();
132
    /// </c></para>
133
    /// <para>Inject: <c>
134
    /// public class MyDependent(Provider&lt;MyDependency&gt; dependencyProvider) {
135
    ///     public void Start() {
136
    ///         using MyDependency dependency = dependencyProvider.Get();
137
    ///         dependency.Run();
138
    ///     }
139
    /// }
140
    /// </c></para>
141
    /// </summary>
142
    /// <param name="services">Application builder's <see cref="HostApplicationBuilder.Services"/>.</param>
143
    public static IServiceCollection AddInjectableProviders(this IServiceCollection services) {
144
        services.TryAddSingleton(typeof(Provider<>), typeof(MicrosoftDependencyInjectionServiceProvider<>));
1✔
145
        services.TryAddSingleton(typeof(OptionalProvider<>), typeof(MicrosoftDependencyInjectionServiceProvider<>));
1✔
146
        return services;
1✔
147
    }
148

149
    /// <summary>
150
    /// <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>
151
    /// <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>
152
    /// <para>Usage:</para>
153
    /// <para><code>builder.Services.SetExitCodeOnBackgroundServiceException(1);</code></para>
154
    /// </summary>
155
    /// <param name="services">From <see cref="HostApplicationBuilder.Services"/> or similar.</param>
156
    /// <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>
157
    public static IServiceCollection SetExitCodeOnBackgroundServiceException(this IServiceCollection services, int exitCode = 1) => services.SetExitCodeOnBackgroundServiceException(_ => exitCode);
2✔
158

159
    /// <summary>
160
    /// <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>
161
    /// <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>
162
    /// <para>Usage:</para>
163
    /// <para><code>builder.Services.SetExitCodeOnBackgroundServiceException(exception => exception is ApplicationException ? 1 : 2);</code></para>
164
    /// </summary>
165
    /// <param name="services">From <see cref="HostApplicationBuilder.Services"/> or similar.</param>
166
    /// <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>
167
    public static IServiceCollection SetExitCodeOnBackgroundServiceException(this IServiceCollection services, Func<Exception, int> exitCodeGenerator) {
168
        services.AddHostedService(context => new BackgroundServiceExceptionListener(context, exitCodeGenerator));
2✔
169
        return services;
1✔
170
    }
171

172
    internal sealed class BackgroundServiceExceptionListener(IServiceProvider services, Func<Exception, int> exitCodeGenerator): BackgroundService {
1✔
173

174
        protected override Task ExecuteAsync(CancellationToken stoppingToken) {
175
            IEnumerable<BackgroundService> backgroundServices =
1✔
176
                services.GetServices<IHostedService>().OfType<BackgroundService>().Where(static service => service is not BackgroundServiceExceptionListener);
2✔
177

178
            services.GetRequiredService<IHostApplicationLifetime>().ApplicationStopped.Register(() => {
1✔
179
                if (backgroundServices.Select(static service => service.ExecuteTask?.Exception?.InnerException).FirstOrDefault() is {} exception) {
2!
180
                    Environment.ExitCode = exitCodeGenerator(exception);
1✔
181
                }
1✔
182
            });
2✔
183

184
            return Task.CompletedTask;
1✔
185
        }
186

187
    }
188

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