Skip to content

DecoWeaver

Build Status NuGet Downloads

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 Order property
  • ✨ Clean Generated Code: Readable, debuggable interceptor code

Installation

dotnet add package DecoWeaver --prerelease

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:

  1. Discover [DecoratedBy] attributes on your classes at compile time
  2. Find DI registration calls like AddScoped<IService, Implementation>()
  3. Generate C# interceptor code that rewrites those calls
  4. 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

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.