DecoWeaver¶
DecoWeaver is a compile-time decorator registration library for .NET dependency injection. It uses C# 11+ interceptors to automatically apply the decorator pattern at build time, eliminating runtime reflection and assembly scanning.
Key Features¶
- ⚡ Zero Runtime Overhead: Decorators applied at compile time using C# interceptors
- 🎯 Type-Safe: Full compile-time validation with IntelliSense support
- 🔧 Simple API: Apply decorators with
[DecoratedBy<T>]or[assembly: DecorateService(...)] - 🌐 Assembly-Level Decorators: Apply decorators to all implementations from one place
- 🚀 Generic Type Decoration: Decorate generic types like
IRepository<T>with open generic decorators - 🏭 Factory Delegate Support: Works with factory registrations like
AddScoped<T, Impl>(sp => new Impl(...)) - 🚫 Opt-Out Support: Exclude specific decorators with
[DoNotDecorate] - 📦 No Runtime Dependencies: Only build-time source generator dependency
- 🔗 Order Control: Explicit decorator ordering via
Orderproperty - ✨ Clean Generated Code: Readable, debuggable interceptor code
Installation¶
Quick Start¶
1. Define Your Service and Implementation¶
public interface IUserRepository
{
Task<User> GetByIdAsync(int id);
}
public class UserRepository : IUserRepository
{
public async Task<User> GetByIdAsync(int id)
{
// Your implementation
}
}
2. Create a Decorator¶
public class LoggingUserRepository : IUserRepository
{
private readonly IUserRepository _inner;
private readonly ILogger<LoggingUserRepository> _logger;
public LoggingUserRepository(
IUserRepository inner,
ILogger<LoggingUserRepository> logger)
{
_inner = inner;
_logger = logger;
}
public async Task<User> GetByIdAsync(int id)
{
_logger.LogInformation("Getting user {UserId}", id);
var user = await _inner.GetByIdAsync(id);
_logger.LogInformation("Retrieved user {UserId}: {UserName}", id, user.Name);
return user;
}
}
3. Apply the Decorator Attribute¶
using DecoWeaver.Attributes;
[DecoratedBy<LoggingUserRepository>]
public class UserRepository : IUserRepository
{
// Your implementation
}
4. Register in DI Container¶
var services = new ServiceCollection();
// DecoWeaver automatically wraps UserRepository with LoggingUserRepository
services.AddScoped<IUserRepository, UserRepository>();
var provider = services.BuildServiceProvider();
var repo = provider.GetRequiredService<IUserRepository>();
// Returns: LoggingUserRepository wrapping UserRepository
That's it! DecoWeaver handles the decoration automatically at compile time.
How It Works¶
DecoWeaver uses a source generator to:
- Discover
[DecoratedBy]attributes on your classes at compile time - Find DI registration calls like
AddScoped<IService, Implementation>() - Generate C# interceptor code that rewrites those calls
- Wrap implementations with decorators using keyed services
The result is zero runtime overhead and fully type-safe decorator application.
Multiple Decorators with Ordering¶
You can stack multiple decorators and control their order:
[DecoratedBy<LoggingRepository>(Order = 1)] // Applied first (innermost)
[DecoratedBy<CachingRepository>(Order = 2)] // Applied second
[DecoratedBy<MetricsRepository>(Order = 3)] // Applied third (outermost)
public class UserRepository : IUserRepository
{
// Your implementation
}
// Result: MetricsRepository → CachingRepository → LoggingRepository → UserRepository
Generic Type Decoration¶
DecoWeaver supports decorating generic types with open generic decorators:
public interface IRepository<T> where T : class
{
Task<T> GetByIdAsync(int id);
}
[DecoratedBy<CachingRepository<>>] // Open generic decorator
public class Repository<T> : IRepository<T> where T : class
{
// Your implementation
}
// Register each closed generic type - required for DecoWeaver
services.AddScoped<IRepository<User>, Repository<User>>();
services.AddScoped<IRepository<Product>, Repository<Product>>();
// Each gets its own decorated instance
var userRepo = provider.GetRequiredService<IRepository<User>>();
// Returns: CachingRepository<User> wrapping Repository<User>
var productRepo = provider.GetRequiredService<IRepository<Product>>();
// Returns: CachingRepository<Product> wrapping Repository<Product>
Documentation¶
- Getting Started - Installation and setup
- Core Concepts - Understanding how DecoWeaver works
- Usage Guide - Detailed attribute usage
- Examples - Real-world usage patterns
- Advanced Topics - Deep dive into internals
Requirements¶
- .NET SDK with C# 11+ support (Visual Studio 2022 17.4+, Rider 2022.3+)
- .NET 8.0+ runtime (for keyed services support)
- Microsoft.Extensions.DependencyInjection 8.0+
Why DecoWeaver?¶
Traditional Decorator Registration¶
// Manual, error-prone, runtime overhead
services.AddScoped<IUserRepository>(sp =>
{
var inner = new UserRepository(/* dependencies */);
var logged = new LoggingRepository(inner, sp.GetRequiredService<ILogger>());
var cached = new CachingRepository(logged, sp.GetRequiredService<ICache>());
return cached;
});
With DecoWeaver¶
// Clean, compile-time, zero overhead
[DecoratedBy<CachingRepository>(Order = 2)]
[DecoratedBy<LoggingRepository>(Order = 1)]
public class UserRepository : IUserRepository { }
services.AddScoped<IUserRepository, UserRepository>(); // Done!
Contributing¶
See the Contributing Guide for information on how to contribute to DecoWeaver.
License¶
This project is licensed under the MIT License.