AutoWire 1.2.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package AutoWire --version 1.2.0
                    
NuGet\Install-Package AutoWire -Version 1.2.0
                    
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="AutoWire" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AutoWire" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="AutoWire" />
                    
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 AutoWire --version 1.2.0
                    
#r "nuget: AutoWire, 1.2.0"
                    
#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 AutoWire@1.2.0
                    
#: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=AutoWire&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=AutoWire&version=1.2.0
                    
Install as a Cake Tool

AutoWire

NuGet NuGet Downloads CI License: MIT

Compile-time dependency injection auto-registration for .NET — add [Scoped], [Singleton], or [Transient] to your services and AutoWire generates the IServiceCollection registration code at build time.

Zero runtime overhead. No reflection. No startup cost.


The problem

Every .NET project accumulates this:

// Program.cs — grows forever, breaks when you forget to update it
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IInventoryService, InventoryService>();
builder.Services.AddSingleton<IEmailSender, SmtpEmailSender>();
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
builder.Services.AddTransient<IReportGenerator, PdfReportGenerator>();
// ... 40 more lines

The AutoWire solution

// Put the registration intent next to the class — where it belongs.

[Scoped]
public class OrderService : IOrderService { ... }

[Scoped]
public class ProductService : IProductService { ... }

[Singleton]
public class RedisCacheService : ICacheService { ... }

[Transient]
public class PdfReportGenerator : IReportGenerator { ... }
// Program.cs — one line, forever.
builder.Services.AddAutoWireServices();

AutoWire generates the full registration code at compile time — the code in obj/ is exactly what you'd have written by hand.


Installation

dotnet add package AutoWire

That's it. No other packages required.


Quick start

1. Decorate your services

using AutoWire;

// Register against all implemented non-system interfaces (auto-discovery)
[Scoped]
public class OrderService : IOrderService, IAuditableService { }
// → generates: services.AddScoped<IOrderService, OrderService>();
//              services.AddScoped<IAuditableService, OrderService>();

// Register against a specific interface only
[Singleton(typeof(ICache))]
public class RedisCache : ICache, IDisposable { }
// → generates: services.AddSingleton<ICache, RedisCache>();

// Register as concrete type (no interface)
[Transient]
public class PdfExporter { }
// → generates: services.AddTransient<PdfExporter>();

// Keyed service (.NET 8+)
[Scoped(Key = "primary")]
public class SqlOrderRepository : IOrderRepository { }
// → generates: services.AddKeyedScoped<IOrderRepository, SqlOrderRepository>("primary");

2. Register once

// ASP.NET Core
builder.Services.AddAutoWireServices();

// Generic Host / Worker Service
services.AddAutoWireServices();

3. Inject normally

public class CheckoutController(IOrderService orders, ICache cache)
{
    // resolved from AutoWire-generated registrations
}

Attribute reference

Attribute DI lifetime Equivalent
[Scoped] Scoped services.AddScoped<TService, TImpl>()
[Singleton] Singleton services.AddSingleton<TService, TImpl>()
[Transient] Transient services.AddTransient<TService, TImpl>()

All three attributes share the same constructor overloads:

// Auto-discover all non-system interfaces
[Scoped]

// Register against one specific interface
[Scoped(typeof(IMyService))]

// Keyed service (.NET 8+)
[Scoped(Key = "keyName")]

// Keyed + explicit interface (.NET 8+)
[Scoped(typeof(IMyService), Key = "keyName")]

Open generic types

AutoWire fully supports open generic registrations. The correct typeof() overload is generated automatically — no reflection needed.

// Auto-discovers compatible generic interfaces
[Scoped]
public class Repository<T> : IRepository<T> { }
// → services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

// Explicit service type
[Singleton(typeof(IReadOnlyRepository<>))]
public class CachedRepository<T> : IRepository<T>, IReadOnlyRepository<T> { }
// → services.AddSingleton(typeof(IReadOnlyRepository<>), typeof(CachedRepository<>));

// No interface — registers as concrete open generic
[Transient]
public class EventProcessor<T> { }
// → services.AddTransient(typeof(EventProcessor<>));

Resolving closed generics from DI works automatically:

// All of these work after a single AddAutoWireServices() call:
provider.GetRequiredService<IRepository<Order>>();
provider.GetRequiredService<IRepository<Product>>();
provider.GetRequiredService<EventProcessor<EmailMessage>>();

How it works

AutoWire is a Roslyn incremental source generator. At build time it:

  1. Finds all non-abstract, non-generic classes decorated with [Scoped], [Singleton], or [Transient]
  2. Resolves the correct service type(s) — explicit or auto-discovered
  3. Emits ServiceCollectionExtensions.AddAutoWireServices() into your compilation

The generated file lives in obj/ and looks exactly like hand-written code:

// <auto-generated by AutoWire/>
public static partial class ServiceCollectionExtensions
{
    public static IServiceCollection AddAutoWireServices(this IServiceCollection services)
    {
        services.AddScoped<IOrderService, OrderService>();
        services.AddScoped<IAuditableService, OrderService>();
        services.AddSingleton<ICache, RedisCache>();
        services.AddTransient<PdfExporter>();
        return services;
    }
}

No reflection. No assembly scanning. No startup cost.


vs. the alternatives

Approach Registration Runtime overhead Refactor-safe
Manual Write by hand None ❌ Easy to forget
Scrutor Assembly scanning Reflection at startup
Injectio Source generator None
AutoWire Source generator None

AutoWire differs from Scrutor in that registration happens at compile time — there is no assembly scanning, no reflection, and no startup cost. It also differs from Scrutor's convention-based scanning in that intent is expressed directly on the class, making it easy to understand what is registered without reading Startup.cs.


Interface auto-discovery rules

When no ServiceType is specified, AutoWire registers the class against every interface it implements, excluding System.* interfaces (e.g. IDisposable, IComparable). If the class has no non-system interfaces, it is registered as its own concrete type.

[Scoped]
public class OrderService : IOrderService, IDisposable
{
    public void Dispose() { }
}
// → services.AddScoped<IOrderService, OrderService>();
// IDisposable is excluded — "System.IDisposable" is a System.* interface

Keyed services (.NET 8+)

public interface IPaymentGateway { }

[Scoped(Key = "stripe")]
public class StripeGateway : IPaymentGateway { }

[Scoped(Key = "paypal")]
public class PayPalGateway : IPaymentGateway { }
// Resolve by key
var stripe = serviceProvider.GetRequiredKeyedService<IPaymentGateway>("stripe");

// Or inject via [FromKeyedServices]
public class CheckoutService([FromKeyedServices("stripe")] IPaymentGateway gateway) { }

Note: Keyed services require Microsoft.Extensions.DependencyInjection 8.0+. The [Keyed] property is available on all frameworks; the generated AddKeyedScoped/Singleton/Transient calls only compile on .NET 8+.


Supported frameworks

net6.0 · net7.0 · net8.0 · net9.0 · netstandard2.0 · netstandard2.1

Works with ASP.NET Core, Worker Services, MAUI, Blazor, console apps — any project using Microsoft.Extensions.DependencyInjection.


FAQ

Q: What if I have two services implementing the same interface? AutoWire registers each independently. The last registration wins for non-keyed services (standard .NET DI behaviour). Use keyed services to disambiguate.

Q: Does it work with open generic types? Not in v1 — generic classes are skipped. Register generic services manually.

Q: Can I see the generated code? Yes — look in obj/Debug/net9.0/generated/AutoWire/AutoWire.AutoWireGenerator/AutoWireServiceCollectionExtensions.g.cs.

Q: Does it slow down my build? Incremental source generators only re-run when a decorated class changes. The overhead on a clean build is negligible — far less than assembly scanning at runtime.



Testing & SpecFlow

Replacing implementations in tests

Microsoft DI uses last-registration-wins, so you can always override production services after calling AddAutoWireServices():

// SpecFlow [BeforeScenario] hook or xUnit fixture
services.AddAutoWireServices();          // production registrations

services.AddScoped<IOrderService, FakeOrderService>();   // overrides — last wins
services.AddScoped<IEmailSender, NullEmailSender>();

Fixing ambiguous method errors in test projects

If your test project also references AutoWire and decorates test doubles with [Scoped] etc., both assemblies generate AddAutoWireServices(), causing an ambiguous extension method error.

Fix it by adding one line to your test project:

// Any .cs file in the test project, e.g. GlobalUsings.cs
[assembly: AutoWire.AutoWireOptions(MethodName = "AddTestServices")]

Now each project has a distinct method:

services.AddAutoWireServices();  // production project
services.AddTestServices();      // test project — no ambiguity

Full SpecFlow example

// SpecFlow startup class
public class TestDependencies : IDependencyInjectionContainerBuilder
{
    public IServiceCollection CreateServiceCollection()
    {
        var services = new ServiceCollection();

        // Register all production services
        services.AddAutoWireServices();

        // Swap specific services with test doubles
        services.AddScoped<IPaymentGateway, StubPaymentGateway>();
        services.AddScoped<IEmailSender, SpyEmailSender>();

        return services;
    }
}

💼 Need .NET consulting?

I'm the author of AutoWire and a suite of 28+ Polly v8 resilience packages. I'm available for consulting on Polly v8 resilience, Azure cloud architecture, and clean .NET design.

→ solidqualitysolutions.com · LinkedIn

License

MIT © Justin Bannister

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  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 was computed.  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 was computed.  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 was computed.  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 was computed.  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 was computed. 
.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.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.18.0 0 6/25/2026
1.17.0 0 6/25/2026
1.16.0 5 6/25/2026
1.15.0 8 6/25/2026
1.14.0 8 6/25/2026
1.13.0 19 6/25/2026
1.12.0 43 6/24/2026
1.11.0 39 6/24/2026
1.10.1 36 6/24/2026
1.10.0 42 6/24/2026
1.9.0 43 6/24/2026
1.8.0 45 6/24/2026
1.7.0 43 6/24/2026
1.6.0 43 6/24/2026
1.5.0 37 6/24/2026
1.4.0 39 6/24/2026
1.3.0 42 6/24/2026
1.2.0 40 6/24/2026
1.0.1 39 6/24/2026
1.0.0 42 6/24/2026

1.0.0: Initial release. Add [Scoped], [Singleton], or [Transient] attributes to any class — AutoWire generates AddAutoWireServices() at compile time. Supports ServiceType override, keyed services (.NET 8+), and multi-interface registration. Zero runtime overhead.