-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Description
Description
Add support for the Decorator pattern, allowing services to be wrapped with decorators automatically during registration. This enables cross-cutting concerns like logging, caching, validation, and performance monitoring without modifying the original service implementation.
Motivation
The Decorator pattern is a powerful way to add behavior to services dynamically. Currently, decorators must be manually configured in DI setup code. With AttributedDI, decorators should be discoverable and registered automatically via attributes.
Usage Scenario
Basic Decorator Registration
public interface IUserService
{
User GetUser(int id);
void UpdateUser(User user);
}
[RegisterAs<IUserService>]
[Singleton]
public class UserService : IUserService
{
public User GetUser(int id) => new User { Id = id, Name = "John" };
public void UpdateUser(User user) { }
}
// Register a logging decorator
[RegisterAsDecorator<IUserService>]
[Singleton]
public class LoggingUserServiceDecorator : IUserService
{
private readonly IUserService _inner;
public LoggingUserServiceDecorator(IUserService inner)
{
_inner = inner;
}
public User GetUser(int id)
{
Console.WriteLine($"Getting user {id}");
return _inner.GetUser(id);
}
public void UpdateUser(User user)
{
Console.WriteLine($"Updating user {user.Id}");
_inner.UpdateUser(user);
}
}
// Register a caching decorator
[RegisterAsDecorator<IUserService>]
[Scoped]
public class CachingUserServiceDecorator : IUserService
{
private readonly IUserService _inner;
private readonly Dictionary<int, User> _cache = new();
public CachingUserServiceDecorator(IUserService inner)
{
_inner = inner;
}
public User GetUser(int id)
{
if (_cache.TryGetValue(id, out var user))
return user;
user = _inner.GetUser(id);
_cache[id] = user;
return user;
}
public void UpdateUser(User user)
{
_inner.UpdateUser(user);
_cache[user.Id] = user;
}
}When AddServices() is called:
- The core
UserServiceis registered asIUserService - A decorator composition is created:
CachingUserServiceDecoratorwrappingLoggingUserServiceDecoratorwrappingUserService - When
IUserServiceis injected, the outermost decorator is provided
Decorator Ordering
The first decorator registered becomes the outermost decorator:
CachingUserServiceDecorator
-> LoggingUserServiceDecorator
-> UserService
This means caching wraps logging wraps the actual service.
Implementation Notes
- Add
RegisterAsDecoratorAttribute<TService>attribute - Decorators must have a constructor accepting
TService(orLazy<TService>for deferred initialization) - Support all lifetime attributes with decorators
- Handle keyed services: decorators can decorate keyed services with the same key
- Validate that decorator types implement/inherit from the service type
- Decorators are applied in the order they are declared (first = outermost)
- Add comprehensive unit tests for decorator chain composition
- Add integration tests covering multiple decorators, mixed lifetimes, and keyed services
- Update documentation with decorator examples and best practices
Validation Rules
- A decorator must have exactly one constructor
- That constructor must accept a parameter of type
TService(the interface being decorated) - Decorator classes should not be registered as regular services (validate in source generator)
- Decorators and the core service should have compatible lifetime assignments
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels