DependencyModules.SourceGenerator 1.0.0-rc9340

This is a prerelease version of DependencyModules.SourceGenerator.
There is a newer version of this package available.
See the version list below for details.
dotnet add package DependencyModules.SourceGenerator --version 1.0.0-rc9340
                    
NuGet\Install-Package DependencyModules.SourceGenerator -Version 1.0.0-rc9340
                    
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="DependencyModules.SourceGenerator" Version="1.0.0-rc9340">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DependencyModules.SourceGenerator" Version="1.0.0-rc9340" />
                    
Directory.Packages.props
<PackageReference Include="DependencyModules.SourceGenerator">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
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 DependencyModules.SourceGenerator --version 1.0.0-rc9340
                    
#r "nuget: DependencyModules.SourceGenerator, 1.0.0-rc9340"
                    
#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 DependencyModules.SourceGenerator@1.0.0-rc9340
                    
#: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=DependencyModules.SourceGenerator&version=1.0.0-rc9340&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=DependencyModules.SourceGenerator&version=1.0.0-rc9340&prerelease
                    
Install as a Cake Tool

DependencyModules

NuGet build coverage License: MIT

Your DI registrations, written as attributes and compiled into your assembly. No reflection, no assembly scanning, no startup cost โ€” and Native AOT works, because there is nothing left to trim away.

๐Ÿ“– Documentation ยท Getting started ยท Conventions ยท Decorators ยท Testing ยท AOT

The whole trick

You mark a class:

[SingletonService]
public class SmtpEmailSender : IEmailSender;

At build time the generator writes the registration you would have written yourself:

// ApplicationModule.Dependencies.g.cs
services.AddSingleton(
    typeof(global::MyApp.IEmailSender),
    typeof(global::MyApp.SmtpEmailSender)
);

That is the entire mechanism. The output is ordinary C# that you can read, grep, set a breakpoint in, and check into a review. Nothing inspects your assembly at run time, so there is no startup scan to pay for and nothing for the trimmer to guess about.

Why not a runtime scanner?

If you have used Scrutor, Autofac modules, or hand-written AddScoped lists, this is what changes:

Runtime scanning DependencyModules
When registration is decided First request to the container dotnet build
A convention that matches nothing Silent DM0005 at build
A service that cannot be constructed InvalidOperationException, eventually DM0002 at build
Trimming / Native AOT Types disappear; scanner finds nothing Literal typeof(), so the trimmer keeps them
Startup cost Proportional to assembly size None
What actually got registered Debugger, at run time A file you can open

The interesting half is the third row. A trimmer removes CreateOrderHandler because nothing statically references it โ€” a scanner that would have found it by reflection does not count. Emitting typeof(CreateOrderHandler) into your assembly is a static reference, which is why this approach and Native AOT get along.

Install

dotnet add package DependencyModules.Runtime
dotnet add package DependencyModules.SourceGenerator

Requires .NET 8.0 or later. The packages ship net8.0 and net10.0 assemblies, so a project on either LTS gets one built against its own framework. Console applications also want Microsoft.Extensions.DependencyInjection.

Quick start

Mark the services, declare a module, load it once:

// Services.cs
using DependencyModules.Runtime.Attributes;

namespace MyApp;

[SingletonService]
public class SmtpEmailSender : IEmailSender;

[ScopedService]
public class OrderRepository : IOrderRepository;
// Program.cs
using MyApp;                       // the generated module lives in your root namespace
using DependencyModules.Runtime;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
services.AddModule<ApplicationModule>();

var provider = services.BuildServiceProvider();

ApplicationModule is generated for you in a project whose entry point is a top-level Program.cs. Anywhere else โ€” a class library, or a project that wants more than one module โ€” declare your own:

[DependencyModule]
public partial class ApplicationModule;

A module must be partial, and must be declared directly in a namespace rather than nested inside another type. Services marked with [SingletonService] and friends may be nested freely.

Coming from top-level statements? The generated module takes your project's RootNamespace, and top-level statements sit in the global namespace โ€” so Program.cs needs using YourRootNamespace; before it can name ApplicationModule.

Registering forty things without writing forty attributes

Declare the rule once. It is matched by the compiler, against the types that exist at build time:

[DependencyModule]
public partial class HandlerModule : IConventionModule {
    void IConventionModule.Conventions(IConventionDefinitions conventions) {
        conventions.RegisterAll(typeof(IRequestHandler<,>)).AsScoped();

        conventions.RegisterAll(typeof(IValidator<>))
            .IncludeBaseClasses()
            .AlsoAsSelf()
            .AsScoped();
    }
}

Every handler in the project is registered against the closed interface it implements. Add a handler tomorrow and it joins; delete one and the registration goes with it. A convention that stops matching anything is a build warning rather than a runtime surprise.

The body of Conventions is never executed โ€” it is read from source at compile time, which is why only the documented calls can appear in it. See the conventions guide.

Composing modules

A module generates an attribute of the same name, so modules compose by attribute:

[DependencyModule]
[DomainModule]
[InfrastructureModule(useInMemory: true, ConnectionName = "primary")]
public partial class ApiModule;

Constructor parameters and settable properties on a module are mirrored onto its generated attribute, so a module can be configured by whoever composes it. For anything the attributes cannot express, implement IServiceCollectionConfiguration and write the registrations by hand.

Decorators and interception

Wrap a service without touching it or its callers. The first constructor parameter is the wrapped instance; the rest resolve normally:

[Decorator(Order = 2000)]
public class CachingRepository(IRepository inner, IMemoryCache cache) : IRepository;

[Decorator(Order = 1000)]
public class TracingRepository(IRepository inner, ILogger<TracingRepository> log) : IRepository;

// resolves as CachingRepository(TracingRepository(SqlRepository))

Lower orders sit closer to the implementation. Ordering is global across every module in an AddModule(s) call, so an application's decorators can wrap those a library contributed โ€” by convention framework code uses 0โ€“999 and application code starts at 1000.

For cross-cutting behaviour across every member of a service, [Intercept] generates a typed wrapper rather than a dynamic proxy. See decorators and interception.

Testing

Tests receive their dependencies as method parameters, against the real registration graph:

[assembly: ApplicationModule]
[assembly: NSubstituteSupport]

public class OrderTests {
    [ModuleTest]
    public async Task PlaceOrder_PricesThroughTheChannel(
        IRequestHandler<PlaceOrder, Order> handler,
        [Mock] IBookRepository books) {

        books.Find("isbn-1", Arg.Any<CancellationToken>())
            .Returns(new Book("isbn-1", 20m));

        var order = await handler.Handle(new PlaceOrder("isbn-1", 10), default);

        Assert.Equal(140m, order.Total);
    }
}
dotnet add package DependencyModules.xUnit        # or DependencyModules.NUnit
dotnet add package DependencyModules.NSubstitute  # or .Moq, or .FakeItEasy

Each test gets its own provider, so singletons cannot leak between them. See the testing guide.

Native AOT

Verified end to end: a console application using conventions, keyed registrations, decorators, a static factory and an intercepted open generic publishes to a 2.2 MB self-contained binary with zero IL trim or AOT warnings, behaving identically to the JIT build.

The one limitation is not this library's to fix: the container cannot close an open generic over a value type without dynamic code, so IRepository<Order> resolves and IRepository<int> throws. Setting PublishAot makes that fail in an ordinary dotnet run rather than only after publishing. See the AOT guide.

Feature reference

[SingletonService] [ScopedService] [TransientService] Register with the matching lifetime
[CrossWireService] One instance shared across the implementation and its interfaces
As = typeof(IFoo) Choose the service type explicitly
Key = "primary" Keyed registration
Using = RegistrationType.Try Add, Try, TryEnumerable or Replace
Realm = typeof(SomeModule) Restrict a registration to one module
[IfEnvironment("Development")] Register only in named environments
[Decorator] [Decorate] [Intercept] Wrap a service, or one you do not own
A static method carrying a service attribute Factory, for types the container cannot build

Full details for each, with the rules and the edge cases, are in the documentation.

Samples

The integ-tests/ directory is a working sample gallery, built and tested on every commit:

Sample Shows
SutProject Every registration shape, in one project
SutProject.Tests Conventions, realms, keyed services, cross-wiring, factories, features, and all three mocking libraries
ConsoleTestProject Top-level statements and the generated ApplicationModule
web/WebApiApp An ASP.NET Core host, with its own test project

Reporting a problem

If services are not registered as you expect, these three steps produce almost everything needed to diagnose it:

  1. Read the generated code. Set <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> and look under obj/. The registrations the generator produced are the ground truth. (Point CompilerGeneratedFilesOutputPath inside obj/ โ€” a folder in the project directory gets compiled as ordinary source on the next build.)
  2. Turn on the generator log, which records the configuration in effect, every module and service discovered, and anything skipped along with the reason:
    <PropertyGroup>
      <DependencyModules_LogOutputDirectory>$(MSBuildProjectDirectory)/dmlogs</DependencyModules_LogOutputDirectory>
    </PropertyGroup>
    
  3. Check for DM#### warnings in the build output. The generator reports these for mistakes it can detect โ€” see the diagnostics reference.

Please include the log and the generated file in any issue.

License

MIT. See LICENSE.txt and CHANGELOG.md.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages (8)

Showing the top 5 NuGet packages that depend on DependencyModules.SourceGenerator:

Package Downloads
SimpleRequest.Aws.Host.Runtime

Package Description

SimpleRequest.Aws.Host.DdbStream

Package Description

SimpleRequest.Aws.Host.Sqs

Package Description

SimpleRequest.Aws.Lambda.CwDashboard

Package Description

SimpleRequest.Aws.Lambda.Runtime

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 0 8/16/2026
1.0.0-rc9340 0 8/16/2026
1.0.0-rc9230 132 8/12/2026
1.0.0-rc9220 71 8/11/2026
1.0.0-rc9210 48 8/9/2026
1.0.0-rc9200 92 8/7/2026
1.0.0-RC9177 711 3/12/2026
1.0.0-RC9166 158 3/7/2026
1.0.0-RC9159 191 3/5/2026
1.0.0-RC9153 1,273 2/3/2026
1.0.0-RC9148 172 5/10/2025
1.0.0-RC9145 97 5/10/2025
1.0.0-RC9141 187 4/24/2025
1.0.0-RC9138 225 4/22/2025
1.0.0-RC9137 247 4/17/2025
1.0.0-RC9136 203 4/13/2025
1.0.0-RC9135 191 4/13/2025
1.0.0-RC9133 142 4/12/2025
1.0.0-RC9131 123 4/11/2025
1.0.0-RC9130 186 3/31/2025
Loading failed