AutoWire 1.2.0
See the version list below for details.
dotnet add package AutoWire --version 1.2.0
NuGet\Install-Package AutoWire -Version 1.2.0
<PackageReference Include="AutoWire" Version="1.2.0" />
<PackageVersion Include="AutoWire" Version="1.2.0" />
<PackageReference Include="AutoWire" />
paket add AutoWire --version 1.2.0
#r "nuget: AutoWire, 1.2.0"
#:package AutoWire@1.2.0
#addin nuget:?package=AutoWire&version=1.2.0
#tool nuget:?package=AutoWire&version=1.2.0
AutoWire
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:
- Finds all non-abstract, non-generic classes decorated with
[Scoped],[Singleton], or[Transient] - Resolves the correct service type(s) — explicit or auto-discovered
- 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.DependencyInjection8.0+. The[Keyed]property is available on all frameworks; the generatedAddKeyedScoped/Singleton/Transientcalls 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 | Versions 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. |
-
.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.