Keep 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:
- forgetting to add new services in
Program.cs, - second-guessing the correct lifetime for each service, and
- refactoring
Program.csas 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.
- Interface generation from concrete types (with optional auto-registration).
- Attribute-driven registration for self, implemented interfaces, or a specific service type.
- Compile-time source generation (no runtime reflection scan).
- Keyed registrations when you pass a key to registration attributes.
- Optional interface generation from concrete types.
- Customizable extension class/method names.
- Optional aggregate
AddAttributedDi()to register across references.
Add the package to any project that defines services:
dotnet add package AttributedDIThe 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();RegisterAsSelfregisters the type as itself.RegisterAsImplementedInterfacesregisters the type against all implemented interfaces.RegisterAs<TService>registers the type against a specific service type.- Apply
Transient,Scoped, orSingletonto pick a lifetime (default is transient). - Pass a key to any registration attribute to generate keyed registrations.
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:
- Install
AttributedDIin each downstream project that defines services. - Mark services with registration attributes in those downstream projects.
- Reference those downstream projects from your upstream (composition root) project.
- Enable aggregate generation in the upstream project.
Enable it in the upstream project file:
<PropertyGroup>
<GenerateAttributedDIExtensions>true</GenerateAttributedDIExtensions>
</PropertyGroup>Then call:
services.AddAttributedDi();MIT. See LICENSE.