AutoWire 1.4.0
See the version list below for details.
dotnet add package AutoWire --version 1.4.0
NuGet\Install-Package AutoWire -Version 1.4.0
<PackageReference Include="AutoWire" Version="1.4.0" />
<PackageVersion Include="AutoWire" Version="1.4.0" />
<PackageReference Include="AutoWire" />
paket add AutoWire --version 1.4.0
#r "nuget: AutoWire, 1.4.0"
#:package AutoWire@1.4.0
#addin nuget:?package=AutoWire&version=1.4.0
#tool nuget:?package=AutoWire&version=1.4.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 | Generated call |
|---|---|---|
[Scoped] |
Scoped | services.AddScoped<TService, TImpl>() |
[Singleton] |
Singleton | services.AddSingleton<TService, TImpl>() |
[Transient] |
Transient | services.AddTransient<TService, TImpl>() |
[TryScoped] |
Scoped | services.TryAddScoped<TService, TImpl>() |
[TrySingleton] |
Singleton | services.TryAddSingleton<TService, TImpl>() |
[TryTransient] |
Transient | services.TryAddTransient<TService, TImpl>() |
All 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")]
Multiple attributes on one class
All attributes support AllowMultiple = true. Use this to register a single class against several explicitly-specified interfaces:
// Without AllowMultiple you'd need a single attribute and auto-discovery.
// With AllowMultiple you can pick exactly which interfaces win:
[Scoped(typeof(IOrderReader))]
[Scoped(typeof(IOrderWriter))]
public class OrderService : IOrderReader, IOrderWriter, IDisposable { }
// → services.AddScoped<IOrderReader, OrderService>();
// → services.AddScoped<IOrderWriter, OrderService>();
// IDisposable is excluded from both registrations.
DuplicateStrategy
Control what happens when the same service type is registered more than once using the Duplicate property:
public enum DuplicateStrategy
{
Add, // (default) AddScoped — last registration wins
Skip, // TryAddScoped — skipped if service type already registered
Replace // RemoveAll + AddScoped — removes all prior registrations, then adds
}
Replace — make the winner explicit
[Scoped]
public class DefaultOrderService : IOrderService { } // registered first
[Scoped(Duplicate = DuplicateStrategy.Replace)]
public class PremiumOrderService : IOrderService { } // removes prior, registers itself
// → services.AddScoped<IOrderService, DefaultOrderService>(); // then...
// → services.RemoveAll<IOrderService>();
// → services.AddScoped<IOrderService, PremiumOrderService>();
// Result: only PremiumOrderService is registered for IOrderService.
Skip — provide a default, respect consumer overrides
[Scoped]
public class ProductionMessageBus : IMessageBus { } // always registered
[Scoped(Duplicate = DuplicateStrategy.Skip)]
public class FallbackMessageBus : IMessageBus { } // skipped — IMessageBus already taken
TryScoped / TrySingleton / TryTransient
Convenience attributes that always use TryAdd semantics — ideal for NuGet library authors who want to provide sensible defaults without overriding the consumer's registrations:
// In your library:
[TryScoped]
public class DefaultRetryPolicy : IRetryPolicy { }
// → services.TryAddScoped<IRetryPolicy, DefaultRetryPolicy>();
// Consumer's host:
services.AddAutoWireServices(); // DefaultRetryPolicy registered
services.AddScoped<IRetryPolicy, AggressiveRetryPolicy>(); // consumer overrides — fine
// Or consumer pre-registers before your library:
services.AddScoped<IRetryPolicy, AggressiveRetryPolicy>(); // registered first
services.AddAutoWireServices(); // TryAdd is skipped — no override
[TryScoped] / [TrySingleton] / [TryTransient] are equivalent to [Scoped(Duplicate = DuplicateStrategy.Skip)].
Roslyn diagnostics
AutoWire ships two built-in diagnostics that surface problems as squiggles in the IDE — no runtime surprises.
| ID | Severity | Condition |
|---|---|---|
| AW001 | ⚠ Warning | [Scoped] / [TryScoped] etc. applied to an abstract class — the class will never be registered |
| AW002 | ℹ Info | Multiple non-keyed Add-strategy registrations for the same service type — last wins, which may be unintentional |
AW001 example
// ⚠ AW001: Abstract class 'BaseHandler' decorated with [Scoped] will not be registered.
[Scoped]
public abstract class BaseHandler : IHandler { }
AW002 example
// ℹ AW002: Multiple non-keyed implementations are registered for 'IOrderService'.
[Scoped] public class OrderServiceV1 : IOrderService { }
[Scoped] public class OrderServiceV2 : IOrderService { }
// Suppress by being explicit:
[Scoped] public class OrderServiceV1 : IOrderService { }
[Scoped(Duplicate = DuplicateStrategy.Replace)] public class OrderServiceV2 : IOrderService { }
// AW002 suppressed — intent is clear.
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>>();
Decorator pattern
[DecorateScoped] / [DecorateSingleton] / [DecorateTransient] wraps an existing service registration with a decorator class at compile time — no Scrutor dependency required.
// Inner service — registered normally
[Scoped]
public class OrderService : IOrderService
{
public string GetStatus() => "pending";
}
// Decorator — wraps IOrderService
[DecorateScoped(typeof(IOrderService))]
public class LoggingOrderService : IOrderService
{
private readonly IOrderService _inner;
// Constructor takes the SERVICE TYPE (IOrderService) — AutoWire injects the concrete inner
public LoggingOrderService(IOrderService inner) { _inner = inner; }
public string GetStatus()
{
Console.WriteLine("GetStatus called");
return _inner.GetStatus();
}
}
// Program.cs — unchanged
builder.Services.AddAutoWireServices();
AutoWire generates:
// 1. Normal registration for inner service
services.AddScoped<IOrderService, OrderService>();
// 2. Decorator wiring (generated at the end, after all normal registrations)
services.RemoveAll<IOrderService>();
services.AddScoped<OrderService>(); // inner concrete self-registered — injectable directly
services.AddScoped<IOrderService>(sp =>
(IOrderService)ActivatorUtilities.CreateInstance(
sp, typeof(LoggingOrderService), sp.GetRequiredService<OrderService>()));
What this gives you
provider.GetRequiredService<IOrderService>()→LoggingOrderServicewrappingOrderService✓provider.GetRequiredService<OrderService>()→OrderServicedirectly (useful in tests) ✓- The decorator is the same lifetime as the
[DecorateScoped/Singleton/Transient]attribute - No reflection — the inner type is resolved at compile time from AutoWire's own registration map
Multiple decorators
Apply [Decorate*] to a class that decorates multiple service types using AllowMultiple:
[DecorateScoped(typeof(IOrderService))]
[DecorateScoped(typeof(IReadOnlyOrderService))]
public class CachingOrderService : IOrderService, IReadOnlyOrderService { ... }
Decorating manually-registered services
If the inner service wasn't registered via AutoWire (e.g. registered manually in Program.cs), AutoWire generates a runtime fallback that scans the IServiceCollection to find and wrap the existing registration automatically.
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 and emits an AW002 info diagnostic. Use DuplicateStrategy.Replace to make the winner explicit, or keyed services to disambiguate.
Q: Does it work with open generic types?
Yes — AutoWire auto-discovers compatible open generic interfaces and emits services.AddScoped(typeof(IRepo<>), typeof(Repo<>)). See the Open generic types section.
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 | 39 | 6/25/2026 |
| 1.17.0 | 44 | 6/25/2026 |
| 1.16.0 | 45 | 6/25/2026 |
| 1.15.0 | 45 | 6/25/2026 |
| 1.14.0 | 48 | 6/25/2026 |
| 1.13.0 | 42 | 6/25/2026 |
| 1.12.0 | 45 | 6/24/2026 |
| 1.11.0 | 43 | 6/24/2026 |
| 1.10.1 | 38 | 6/24/2026 |
| 1.10.0 | 44 | 6/24/2026 |
| 1.9.0 | 45 | 6/24/2026 |
| 1.8.0 | 48 | 6/24/2026 |
| 1.7.0 | 45 | 6/24/2026 |
| 1.6.0 | 44 | 6/24/2026 |
| 1.5.0 | 39 | 6/24/2026 |
| 1.4.0 | 41 | 6/24/2026 |
| 1.3.0 | 43 | 6/24/2026 |
| 1.2.0 | 41 | 6/24/2026 |
| 1.0.1 | 40 | 6/24/2026 |
| 1.0.0 | 43 | 6/24/2026 |
1.4.0: Decorator support — [DecorateScoped(typeof(ISvc))]/[DecorateSingleton]/[DecorateTransient] wraps existing registrations at compile time. Inner service is self-registered for direct injection. Runtime fallback for services not registered via AutoWire. 1.3.0: AllowMultiple, DuplicateStrategy (Add/Skip/Replace), [TryScoped]/[TrySingleton]/[TryTransient], AW001+AW002 diagnostics. 1.2.0: Open generic support + AutoWireOptions(MethodName). 1.0.0: Initial release.