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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="X39.Util.DependencyInjection" Version="3.0.0.10" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="X39.Util.DependencyInjection" Version="3.0.0.10" />
                    
Directory.Packages.props
<PackageReference Include="X39.Util.DependencyInjection" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add X39.Util.DependencyInjection --version 3.0.0.10
                    
#r "nuget: X39.Util.DependencyInjection, 3.0.0.10"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package X39.Util.DependencyInjection@3.0.0.10
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=X39.Util.DependencyInjection&version=3.0.0.10
                    
Install as a Cake Addin
#tool nuget:?package=X39.Util.DependencyInjection&version=3.0.0.10
                    
Install as a Cake Tool

X39.Util.DependencyInjection

Attribute-based service registration for Microsoft.Extensions.DependencyInjection with compile-time source generation and NativeAOT support.

NuGet

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:

  1. Scans your project for classes decorated with [Singleton], [Transient], or [Scoped] attributes.
  2. Validates attribute usage and condition methods, reporting errors as compiler diagnostics.
  3. Emits a static extension method (default: Dependencies.AddDependencies()) containing all service registrations as direct IServiceCollection calls.

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 AddDependencies method 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 X39DI002 at compile time; the reflection-based path throws MultipleDependencyInjectionAttributesPresentException.
  • 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:

  1. 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.

  2. 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.

  3. 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.

  4. No Compensation. Your contribution is made voluntarily and without expectation of compensation, unless separately agreed in writing.

  5. Right to Remove. The Maintainer may remove, modify, or replace your contribution at any time, for any reason, without notice or obligation to you.

  6. 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.

  7. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
3.0.0.10 82 3/2/2026
3.0.0.9 85 3/2/2026
2.0.1.8 362 7/5/2024
2.0.1.7 268 2/28/2024
2.0.1.6 235 2/28/2024
2.0.1.5 238 2/28/2024
2.0.0.4 677 2/5/2023
2.0.0.3 434 1/21/2023
1.0.0.2 670 12/26/2022