AutoWire 1.5.0
See the version list below for details.
dotnet add package AutoWire --version 1.5.0
NuGet\Install-Package AutoWire -Version 1.5.0
<PackageReference Include="AutoWire" Version="1.5.0" />
<PackageVersion Include="AutoWire" Version="1.5.0" />
<PackageReference Include="AutoWire" />
paket add AutoWire --version 1.5.0
#r "nuget: AutoWire, 1.5.0"
#:package AutoWire@1.5.0
#addin nuget:?package=AutoWire&version=1.5.0
#tool nuget:?package=AutoWire&version=1.5.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>() |
[HostedService] |
Singleton | services.AddHostedService<T>() |
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)].
Hosted services (Worker Services)
[HostedService] registers a background service with a single attribute — no manual AddHostedService<T>() required.
using AutoWire;
using Microsoft.Extensions.Hosting;
[HostedService]
public class DataSyncWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await SyncDataAsync();
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
// Program.cs — one call registers everything, including hosted services
builder.Services.AddAutoWireServices();
AutoWire generates:
services.AddHostedService<global::DataSyncWorker>();
AddHostedService uses TryAddEnumerable internally — calling AddAutoWireServices() multiple times is safe and idempotent.
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] / [HostedService] etc. applied to an abstract class — will never be registered |
| AW002 | ℹ Info | Multiple non-keyed Add-strategy registrations for the same service type |
| AW003 | ❌ Error | Explicit ServiceType in [Scoped(typeof(IFoo))] is not implemented by the decorated class |
AW001 example
// ⚠ AW001: Abstract class 'BaseHandler' decorated with [Scoped] will not be registered.
[Scoped]
public abstract class BaseHandler : IHandler { }
AW003 example
// ❌ AW003 Error: 'ReportService' does not implement 'IOrderService'.
[Scoped(typeof(IOrderService))]
public class ReportService : IReportService { } // wrong type!
// ✅ Fix:
[Scoped(typeof(IReportService))]
public class ReportService : IReportService { }
AW003 is an Error (not a warning) — the registration would always throw at runtime, so it blocks the build.
[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.
```csharp
// 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: Does it work with Worker Services and background jobs?
Yes — use [HostedService] on any class implementing IHostedService or extending BackgroundService. AutoWire generates services.AddHostedService<T>() and handles idempotency automatically.
Q: What if I accidentally specify the wrong service type?
AW003 catches it at compile time with a build error. [Scoped(typeof(IFoo))] on a class that doesn't implement IFoo will fail the build with a clear message rather than exploding at runtime.
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, DuplicateStrategy.Skip to keep the first, 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: Does it support the decorator pattern?
Yes — use [DecorateScoped(typeof(IService))], [DecorateSingleton], or [DecorateTransient] on a class to wrap an existing registration. AutoWire generates compile-time code that self-registers the inner type and wires the decorator. No Scrutor required.
Q: I'm writing a NuGet library and don't want to override my consumer's registrations. What should I use?
Use [TryScoped], [TrySingleton], or [TryTransient]. These generate TryAddScoped/Singleton/Transient calls — the registration is silently skipped if the service type is already registered by the consumer.
Q: Can I register one class against multiple interfaces?
Yes — since AllowMultiple = true, you can stack attributes:
[Scoped(typeof(IOrderReader))]
[Scoped(typeof(IOrderWriter))]
public class OrderService : IOrderReader, IOrderWriter { }
Without stacking, AutoWire auto-discovers ALL non-system interfaces, which achieves the same result when you want all of them.
Q: My test project also uses AutoWire and I'm getting an ambiguous method error.
Add [assembly: AutoWire.AutoWireOptions(MethodName = "AddTestServices")] to your test project. This renames the generated method so each project has a unique one.
Q: Does it work with SpecFlow / xUnit / NUnit test fixtures?
Yes. Call AddAutoWireServices() first, then register stubs/fakes after — last registration wins. See the Testing & SpecFlow section for a full example.
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.
Q: What frameworks are supported?
net6.0 · net7.0 · net8.0 · net9.0 · netstandard2.0 · netstandard2.1 — any project using Microsoft.Extensions.DependencyInjection. Keyed services require .NET 8+.
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 | 43 | 6/25/2026 |
| 1.17.0 | 50 | 6/25/2026 |
| 1.16.0 | 48 | 6/25/2026 |
| 1.15.0 | 49 | 6/25/2026 |
| 1.14.0 | 52 | 6/25/2026 |
| 1.13.0 | 46 | 6/25/2026 |
| 1.12.0 | 45 | 6/24/2026 |
| 1.11.0 | 43 | 6/24/2026 |
| 1.10.1 | 40 | 6/24/2026 |
| 1.10.0 | 45 | 6/24/2026 |
| 1.9.0 | 47 | 6/24/2026 |
| 1.8.0 | 48 | 6/24/2026 |
| 1.7.0 | 46 | 6/24/2026 |
| 1.6.0 | 44 | 6/24/2026 |
| 1.5.0 | 40 | 6/24/2026 |
| 1.4.0 | 41 | 6/24/2026 |
| 1.3.0 | 45 | 6/24/2026 |
| 1.2.0 | 42 | 6/24/2026 |
| 1.0.1 | 41 | 6/24/2026 |
| 1.0.0 | 46 | 6/24/2026 |
1.5.0: [HostedService] attribute generates AddHostedService<T>() — zero-boilerplate Worker Service registration. AW003 Error diagnostic: compile-time error when explicit ServiceType isn't implemented by the decorated class. 1.4.0: Decorator support — [DecorateScoped/Singleton/Transient]. 1.3.0: AllowMultiple, DuplicateStrategy, [TryScoped/TrySingleton/TryTransient], AW001+AW002. 1.2.0: Open generics + AutoWireOptions. 1.0.0: Initial release.