X39.Util.DependencyInjection
3.0.0.10
dotnet add package X39.Util.DependencyInjection --version 3.0.0.10
NuGet\Install-Package X39.Util.DependencyInjection -Version 3.0.0.10
<PackageReference Include="X39.Util.DependencyInjection" Version="3.0.0.10" />
<PackageVersion Include="X39.Util.DependencyInjection" Version="3.0.0.10" />
<PackageReference Include="X39.Util.DependencyInjection" />
paket add X39.Util.DependencyInjection --version 3.0.0.10
#r "nuget: X39.Util.DependencyInjection, 3.0.0.10"
#:package X39.Util.DependencyInjection@3.0.0.10
#addin nuget:?package=X39.Util.DependencyInjection&version=3.0.0.10
#tool nuget:?package=X39.Util.DependencyInjection&version=3.0.0.10
X39.Util.DependencyInjection
Attribute-based service registration for Microsoft.Extensions.DependencyInjection with
compile-time source generation and NativeAOT support.
- X39.Util.DependencyInjection
Installation
dotnet add package X39.Util.DependencyInjection
Or add directly to your .csproj:
<PackageReference Include="X39.Util.DependencyInjection" Version="*" />
The package includes a Roslyn source generator that runs automatically at compile time. No additional packages are required.
Quick Start
Decorate your service class with a lifetime attribute and call the generated AddDependencies
method during startup:
using X39.Util.DependencyInjection;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.AddDependencies(context.Configuration);
})
.Build();
host.Run();
using X39.Util.DependencyInjection.Attributes;
public interface IMyService
{
void DoWork();
}
[Singleton<MyService, IMyService>]
public class MyService : IMyService
{
public void DoWork() { /* ... */ }
}
The source generator discovers all attributed classes at compile time and generates the
AddDependencies extension method with explicit registration calls — no runtime reflection needed.
Source Generator
How It Works
At compile time, the included Roslyn incremental source generator:
- Scans your project for classes decorated with
[Singleton],[Transient], or[Scoped]attributes. - Validates attribute usage and condition methods, reporting errors as compiler diagnostics.
- Emits a static extension method (default:
Dependencies.AddDependencies()) containing all service registrations as directIServiceCollectioncalls.
The generated code is fully AOT-safe — all types are statically known and no reflection is used at runtime.
Generated Code Example
For a class like:
[Singleton<MyService, IMyService>]
public class MyService : IMyService
{
[DependencyInjectionCondition]
internal static bool IsEnabled(IConfiguration configuration)
=> configuration.GetValue<bool>("Features:MyService");
}
The generator produces:
// <auto-generated/>
public static class Dependencies
{
public static IServiceCollection AddDependencies(
this IServiceCollection services,
IConfiguration configuration)
{
RuntimeHelpers.RunClassConstructor(typeof(MyApp.MyService).TypeHandle);
if (MyApp.MyService.IsEnabled(configuration))
{
services.AddSingleton<MyApp.IMyService, MyApp.MyService>();
}
return services;
}
}
NativeAOT Support
Because the source generator resolves all registrations at compile time, the generated code is fully compatible with NativeAOT publishing. There is no runtime reflection, no dynamic assembly scanning, and all types are explicitly referenced in the generated output.
Customizing the Generated Class
By default, the generator creates a class named Dependencies in the
X39.Util.DependencyInjection namespace with an AddDependencies extension method. You can
customize both via MSBuild properties in your .csproj:
<PropertyGroup>
<X39_DependencyInjection_ClassName>MyServices</X39_DependencyInjection_ClassName>
<X39_DependencyInjection_Namespace>MyApp.DI</X39_DependencyInjection_Namespace>
</PropertyGroup>
This produces MyApp.DI.MyServices.AddMyServices(...) instead. The method name is always
Add + the class name.
Compile-Time Diagnostics
The source generator reports errors at build time rather than at runtime:
| ID | Description |
|---|---|
X39DI001 |
Condition method is private. Change to internal or public for source-generated registration. |
X39DI002 |
Multiple dependency injection attributes on the same class. |
X39DI003 |
Invalid condition method signature — must be static, return bool, and accept zero parameters or a single IConfiguration parameter. |
Lifetime Attributes
Each attribute corresponds to a standard DI lifetime. The generic forms require .NET 7+.
| Attribute | Lifetime | Equivalent call |
|---|---|---|
[Singleton<TService>] |
Singleton | AddSingleton<TService>() |
[Singleton<TService, TAbstraction>] |
Singleton | AddSingleton<TAbstraction, TService>() |
[Transient<TService>] |
Transient | AddTransient<TService>() |
[Transient<TService, TAbstraction>] |
Transient | AddTransient<TAbstraction, TService>() |
[Scoped<TService>] |
Scoped | AddScoped<TService>() |
[Scoped<TService, TAbstraction>] |
Scoped | AddScoped<TAbstraction, TService>() |
In the two-type-parameter form, TService is the implementation class and TAbstraction is the
interface or base class (TService : TAbstraction).
Pre-.NET 7: Non-generic versions are available using typeof(...). These are marked
[Obsolete] on .NET 7+ in favor of the generic forms.
// Without abstraction (registers as itself)
[Singleton(typeof(MyService))]
// With abstraction (note: parameter order is serviceType, actualType)
[Singleton(typeof(IMyService), typeof(MyService))]
Conditional Registration
Use [DependencyInjectionCondition] on a static method to control whether a service is registered.
The method must be static, return bool, and accept either no parameters or a single
IConfiguration parameter.
Important: Condition methods must be internal or public when using the source generator.
Private condition methods produce a compile-time error (X39DI001).
public interface IMyService
{
bool SomeFunc();
}
[Singleton<DebugService, IMyService>]
public class DebugService : IMyService
{
[DependencyInjectionCondition]
internal static bool Condition()
{
#if DEBUG
return true;
#else
return false;
#endif
}
public bool SomeFunc() => true;
}
[Singleton<ReleaseService, IMyService>]
public class ReleaseService : IMyService
{
[DependencyInjectionCondition]
internal static bool Condition()
{
#if DEBUG
return false;
#else
return true;
#endif
}
public bool SomeFunc() => true;
}
A condition method can also accept IConfiguration to make decisions based on app configuration:
[DependencyInjectionCondition]
internal static bool IsEnabled(IConfiguration configuration)
{
return configuration.GetValue<bool>("Features:MyService");
}
If a class has multiple condition methods, all must return true for the service to be
registered (AND logic).
Reflection-Based Registration (Legacy)
The library also includes reflection-based registration methods that scan assemblies at runtime. These are useful when source generation is not available or when scanning assemblies outside your project.
All methods are extension methods on IServiceCollection.
AddAttributedServicesOf(IConfiguration, Assembly)
Scans the given assembly for classes decorated with lifetime attributes and registers them.
services.AddAttributedServicesOf(configuration, typeof(Program).Assembly);
AddAttributedServicesFromAssemblyOf<T>(IConfiguration)
Convenience overload that scans the assembly containing type T.
services.AddAttributedServicesFromAssemblyOf<Program>(configuration);
AddAttributedServicesOf(IConfiguration, AppDomain)
Scans all assemblies loaded in the given AppDomain.
services.AddAttributedServicesOf(configuration, AppDomain.CurrentDomain);
Note: The reflection-based methods use runtime type scanning and are not compatible with NativeAOT. Prefer the source-generated
AddDependenciesmethod for new projects.
Behavior Notes
- Static constructors are executed during registration (before condition methods are evaluated).
- Only one lifetime attribute is allowed per class. The source generator reports
X39DI002at compile time; the reflection-based path throwsMultipleDependencyInjectionAttributesPresentException. - Lifetime attributes are not inherited (
Inherited = false).
Exceptions
Exceptions are thrown by the reflection-based registration path. The source generator reports equivalent issues as compile-time diagnostics instead.
All exceptions derive from DependencyInjectionException.
| Exception | Thrown when |
|---|---|
ActualTypeIsNotMatchingDecoratedTypeException |
The TService type parameter does not match the class the attribute is applied to. |
ConditionMethodHasInvalidSignatureException |
A [DependencyInjectionCondition] method is not static, does not return bool, or has unsupported parameters. |
MultipleDependencyInjectionAttributesPresentException |
A class has more than one lifetime attribute ([Singleton], [Transient], [Scoped]). |
ServiceTypeIsNotImplementingDecoratedTypeException |
The decorated class does not implement the TAbstraction type. |
Semantic Versioning
This library follows the principles of Semantic Versioning.
Contributing
Contributions are welcome! Please submit a pull request or create a discussion to discuss any changes you wish to make.
Code of Conduct
Be excellent to each other.
Contributor License Agreement
By submitting a contribution (pull request, patch, or any other form) to this project, you agree to the following terms:
License Grant. You grant the project maintainer ("Maintainer") and all recipients of the software a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license to use, reproduce, modify, distribute, sublicense, and otherwise exploit your contribution under the terms of the GNU Lesser General Public License v3.0 (LGPL-3.0-only). You additionally grant the Maintainer the right to relicense your contribution under any other open-source or proprietary license at the Maintainer's sole discretion.
Originality. You represent that your contribution is your original work, or that you have sufficient rights to grant the licenses above. If your contribution includes third-party material, you represent that its license is compatible with the LGPL-3.0-only and permits the grants made herein.
No Conflicting Obligations. You represent that your contribution is not subject to any agreement, obligation, or encumbrance (including but not limited to employment agreements or prior license grants) that would conflict with or restrict the rights granted under this agreement.
No Compensation. Your contribution is made voluntarily and without expectation of compensation, unless separately agreed in writing.
Right to Remove. The Maintainer may remove, modify, or replace your contribution at any time, for any reason, without notice or obligation to you.
Liability. To the maximum extent permitted by applicable law, your contribution is provided "as is", without warranty of any kind. You shall be solely liable for any damage arising from the inclusion of your contribution to the extent such damage is caused by a defect, rights violation, or other issue originating in your contribution.
Governing Law. This agreement is governed by the laws of the Federal Republic of Germany (Bundesrepublik Deutschland), in particular the German Civil Code (BGB), without regard to its conflict-of-laws provisions. For contributors outside Germany, this choice of law applies to the extent permitted by the contributor's local jurisdiction.
Please add yourself to the CONTRIBUTORS file when submitting your first pull request, and include the following statement in your pull request description:
I have read and agree to the Contributor License Agreement in this project's README.
License
This project is licensed under the GNU Lesser General Public License v3.0. See the LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- JetBrains.Annotations (>= 2022.1.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 7.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
- X39.Util (>= 1.0.0.33)
-
.NETStandard 2.1
- JetBrains.Annotations (>= 2022.1.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 7.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
- X39.Util (>= 1.0.0.33)
-
net10.0
- JetBrains.Annotations (>= 2022.1.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 7.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
- X39.Util (>= 1.0.0.33)
-
net6.0
- JetBrains.Annotations (>= 2022.1.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 7.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
- X39.Util (>= 1.0.0.33)
-
net7.0
- JetBrains.Annotations (>= 2022.1.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 7.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
- X39.Util (>= 1.0.0.33)
-
net8.0
- JetBrains.Annotations (>= 2022.1.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 7.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
- X39.Util (>= 1.0.0.33)
-
net9.0
- JetBrains.Annotations (>= 2022.1.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 7.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
- X39.Util (>= 1.0.0.33)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on X39.Util.DependencyInjection:
| Package | Downloads |
|---|---|
|
X39.Util.Blazor.WebAssembly
Utility services and components useful for working with blazor. |
GitHub repositories
This package is not used by any popular GitHub repositories.