Attribute-driven DI registration with source-generated wiring and optional interface generation.
$ dotnet add package AttributedDIKeep DI registration close to your services.
AttributedDI lets you mark services with simple attributes, generates interfaces from your
implementations, and wires everything with the equivalent
services.AddTransient/Scoped/Singleton(...) calls at compile time. Registrations live
next to the types they describe, and the generator writes the wiring for you (no runtime
reflection, AOT friendly).
If you've ever shipped a bug because you forgot to register a service in Program.cs,
or spent time debating lifetimes while staring at a giant registration list, this is for you.
I built AttributedDI after repeatedly:
Program.cs,Program.cs as the list grew past a dozen registrations.Manual DI registration scales poorly as projects grow, and runtime scanning is slow and unfriendly to trimming/AOT. AttributedDI keeps registration colocated with the type, and its interface generation helps you introduce abstractions without the usual manual churn. It produces normal, readable registration code during build.
AddAttributedDi() to register across references.Add the package to any project that defines services:
dotnet add package AttributedDI
The source generator is included automatically with the package reference.
Annotate services and call the generated extension method.
using AttributedDI;
using Microsoft.Extensions.DependencyInjection;
namespace MyApp;
public interface IClock
{
DateTime UtcNow { get; }
}
[Singleton]
[RegisterAs<IClock>]
public sealed class SystemClock : IClock
{
public DateTime UtcNow => DateTime.UtcNow;
}
[Scoped]
[RegisterAsSelf]
public sealed class Session
{
}
AttributedDI can also generate an interface for you and register against it in one step:
[RegisterAsGeneratedInterface]
public sealed partial class MetricsSink
{
public void Write(string name, double value) { }
[ExcludeInterfaceMember]
public string DebugOnly => "local";
}
At build time, AttributedDI generates an extension method named Add{AssemblyName} on
{AssemblyName}ServiceCollectionExtensions (names are sanitized into valid identifiers).
Use it in startup:
var services = new ServiceCollection();
services.AddMyApp();
RegisterAsSelf registers the type as itself.RegisterAsImplementedInterfaces registers the type against all implemented interfaces.RegisterAs<TService> registers the type against a specific service type.Transient, Scoped, or Singleton to pick a lifetime (default is transient).Example with a keyed registration:
[Singleton]
[RegisterAs<IGateway>("primary")]
public sealed class PrimaryGateway : IGateway
{
}
Generate an interface from a concrete type:
[GenerateInterface]
public sealed partial class WeatherClient
{
public Task<string> GetAsync(string city, CancellationToken ct) => Task.FromResult("ok");
}
Generate an interface and register against it in one step:
[RegisterAsGeneratedInterface]
public sealed partial class MetricsSink
{
public void Write(string name, double value) { }
[ExcludeInterfaceMember]
public string DebugOnly => "local";
}
Both [GenerateInterface] and [RegisterAsGeneratedInterface] require a non-nested partial class or struct.
For edge cases and exclusions, see docs/interface-generation-exceptions.md.
If you want stable, explicit names, apply the assembly-level attribute:
using AttributedDI;
[assembly: ServiceCollectionExtension(
extensionClassName: "DependencyInjectionExtensions",
methodName: "AddMyServices",
extensionNamespace: "MyApp")]
Then call:
services.AddMyServices();
If you have multiple projects that each define services, you can generate a single
AddAttributedDi() method in the top-level project that calls all of their generated
extension methods.
Typical setup:
AttributedDI in each downstream project that defines services.Enable it in the upstream project file:
<PropertyGroup>
<GenerateAttributedDIExtensions>true</GenerateAttributedDIExtensions>
</PropertyGroup>
Then call:
services.AddAttributedDi();
MIT. See LICENSE.