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

Aldaviva / Unfucked / 27961381910

22 Jun 2026 02:46PM UTC coverage: 44.453%. Remained the same
27961381910

push

github

Aldaviva
Document FilterContext

664 of 1877 branches covered (35.38%)

0 of 5 new or added lines in 1 file covered. (0.0%)

1 existing line 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!
48
            PhysicalFileProvider fileProvider = new(installationDir, ExclusionFilters.None);
×
49

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

61
            int sourcesAdded = 0;
×
62
            foreach ((int index, IConfigurationSource source) in sourcesToAdd) {
×
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) {
77
        IDictionary<PhysicalFileProvider, PhysicalFileProvider> replacementProviders = new Dictionary<PhysicalFileProvider, PhysicalFileProvider>();
×
78
        foreach (FileConfigurationSource source in builder.Sources.OfType<FileConfigurationSource>()) {
×
79
            if (source.FileProvider is PhysicalFileProvider fileProvider && !replacementProviders.Values.Contains(fileProvider)) {
×
80
                source.FileProvider = replacementProviders.GetOrAdd(fileProvider, static replaced => new PhysicalFileProvider(replaced.Root, ExclusionFilters.None), out bool _);
×
81
            }
82
        }
83
        foreach (PhysicalFileProvider replaced in replacementProviders.Keys) {
×
84
            replaced.Dispose();
×
85
        }
86
        (builder as IConfigurationRoot)?.Reload();
×
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) {
94

NEW
95
        return includeHidden
×
NEW
96
            ? builder.AddJsonFile(source => {
×
97
                source.Path           = path;
×
98
                source.Optional       = optional;
×
99
                source.ReloadOnChange = reloadOnChange;
×
100
                resolveFileProvider(source);
×
NEW
101
            })
×
NEW
102
            : JsonConfigurationExtensions.AddJsonFile(builder, path, optional, reloadOnChange);
×
103

104
        // Copied from Microsoft.Extensions.Configuration.FileConfigurationSource.ResolveFileProvider(), which stupidly hardcodes always excluding hidden/sensitive files
105
        static void resolveFileProvider(JsonConfigurationSource source) {
106
            const ExclusionFilters filter = ExclusionFilters.None;
107

108
            if (source.FileProvider == null && !string.IsNullOrEmpty(source.Path) && Path.IsPathRooted(source.Path)) {
×
109
                string? directory  = Path.GetDirectoryName(source.Path);
×
110
                string  pathToFile = Path.GetFileName(source.Path);
×
111
                while (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) {
×
112
                    pathToFile = Path.Combine(Path.GetFileName(directory), pathToFile);
×
113
                    directory  = Path.GetDirectoryName(directory);
×
114
                }
115
                if (Directory.Exists(directory)) {
×
NEW
116
                    source.FileProvider = new PhysicalFileProvider(directory, filter);
×
117
                    source.Path         = pathToFile;
×
118
                }
119
            }
120
        }
×
121
    }
122

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

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

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

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

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

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

185
            return Task.CompletedTask;
1✔
186
        }
187

188
    }
189

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