Decorators
SourceGen.Ioc supports the decorator pattern for service registration.
Basic Decorator
csharp
public interface IHandler
{
void Handle();
}
// Decorator implementation
public class LoggingDecorator<T>(T inner, ILogger logger) : IHandler
where T : IHandler
{
public void Handle()
{
logger.Log("Before");
inner.Handle();
logger.Log("After");
}
}
// Register with decorator
[IocRegister<IHandler>(Decorators = [typeof(LoggingDecorator<>)])]
internal class MyHandler : IHandler
{
public void Handle() => Console.WriteLine("Handling");
}Generated Code
csharp
// <auto-generated/>
services.AddSingleton<global::MyNamespace.MyHandler, global::MyNamespace.MyHandler>();
services.AddSingleton<global::MyNamespace.IHandler>((global::System.IServiceProvider sp) =>
{
var s0 = sp.GetRequiredService<global::MyNamespace.MyHandler>();
var s0_p0 = sp.GetRequiredService<global::MyNamespace.ILogger>();
var s1 = new global::MyNamespace.LoggingDecorator<global::MyNamespace.MyHandler>(s0, s0_p0);
return s1;
});Multiple Decorators
Decorators are applied in order (first = outermost):
csharp
[IocRegister<IHandler>(
Decorators = [typeof(LoggingDecorator<>), typeof(CachingDecorator<>)])]
internal class MyHandler : IHandler;
// Execution order: Logging -> Caching -> MyHandlerGenerated Code
csharp
// <auto-generated/>
services.AddSingleton<global::MyNamespace.MyHandler, global::MyNamespace.MyHandler>();
services.AddSingleton<global::MyNamespace.IHandler>((global::System.IServiceProvider sp) =>
{
var s0 = sp.GetRequiredService<global::MyNamespace.MyHandler>();
var s1 = new global::MyNamespace.CachingDecorator<global::MyNamespace.MyHandler>(s0);
var s2 = new global::MyNamespace.LoggingDecorator<global::MyNamespace.CachingDecorator<global::MyNamespace.MyHandler>>(s1);
return s2;
});Default Decorators
Apply decorators to all implementations of an interface:
csharp
[IocRegisterDefaults<IHandler>(
ServiceLifetime.Transient,
Decorators = [typeof(LoggingDecorator<>), typeof(MetricsDecorator<>)])]
public interface IHandler;
// All handlers get logging and metrics decorators
[IocRegister]
internal class Handler1 : IHandler;
[IocRegister]
internal class Handler2 : IHandler;Generated Code
csharp
// <auto-generated/>
services.AddTransient<global::MyNamespace.Handler1, global::MyNamespace.Handler1>();
services.AddTransient<global::MyNamespace.IHandler>((global::System.IServiceProvider sp) =>
{
var s0 = sp.GetRequiredService<global::MyNamespace.Handler1>();
var s1 = new global::MyNamespace.MetricsDecorator<global::MyNamespace.Handler1>(s0);
var s2 = new global::MyNamespace.LoggingDecorator<global::MyNamespace.MetricsDecorator<global::MyNamespace.Handler1>>(s1);
return s2;
});
services.AddTransient<global::MyNamespace.Handler2, global::MyNamespace.Handler2>();
services.AddTransient<global::MyNamespace.IHandler>((global::System.IServiceProvider sp) =>
{
var s0 = sp.GetRequiredService<global::MyNamespace.Handler2>();
var s1 = new global::MyNamespace.MetricsDecorator<global::MyNamespace.Handler2>(s0);
var s2 = new global::MyNamespace.LoggingDecorator<global::MyNamespace.MetricsDecorator<global::MyNamespace.Handler2>>(s1);
return s2;
});Override Default Decorators
csharp
// Override decorators for specific handler
[IocRegister(Decorators = [typeof(CustomDecorator<>)])]
internal class SpecialHandler : IHandler;
// Or disable decorators
[IocRegister(Decorators = [])]
internal class NoDecoratorsHandler : IHandler;Generated Code
csharp
// <auto-generated/>
// SpecialHandler uses CustomDecorator instead of default decorators
services.AddTransient<global::MyNamespace.SpecialHandler, global::MyNamespace.SpecialHandler>();
services.AddTransient<global::MyNamespace.IHandler>((global::System.IServiceProvider sp) =>
{
var s0 = sp.GetRequiredService<global::MyNamespace.SpecialHandler>();
var s1 = new global::MyNamespace.CustomDecorator<global::MyNamespace.SpecialHandler>(s0);
return s1;
});
// NoDecoratorsHandler has no decorators
services.AddTransient<global::MyNamespace.NoDecoratorsHandler, global::MyNamespace.NoDecoratorsHandler>();
services.AddTransient<global::MyNamespace.IHandler>((global::System.IServiceProvider sp) => sp.GetRequiredService<global::MyNamespace.NoDecoratorsHandler>());Type Constraint Validation
The source generator automatically validates decorator type constraints at compile time. Decorators that don't satisfy the type constraints for a specific implementation will be skipped.
csharp
public interface IQuery;
public interface ICommand;
public interface IRequestHandler<TRequest, TResponse>;
// Base decorator - applies to all request handlers
public class LoggingDecorator<TRequest, TResponse>(IRequestHandler<TRequest, TResponse> inner)
: IRequestHandler<TRequest, TResponse>;
// Query-only decorator - only applies when TRequest implements IQuery
public class QueryCachingDecorator<TRequest, TResponse>(IRequestHandler<TRequest, TResponse> inner)
: IRequestHandler<TRequest, TResponse>
where TRequest : IQuery;
// Define defaults with both decorators
[IocRegisterDefaults(typeof(IRequestHandler<,>),
ServiceLifetime.Scoped,
Decorators = [typeof(LoggingDecorator<,>), typeof(QueryCachingDecorator<,>)])]
public interface IRequestHandler<TRequest, TResponse>;
// Command handler - only gets LoggingDecorator (QueryCachingDecorator constraint not satisfied)
public record MyCommand : ICommand;
[IocRegister]
internal class MyCommandHandler : IRequestHandler<MyCommand, bool>;
// Query handler - gets both LoggingDecorator and QueryCachingDecorator
public record MyQuery : IQuery;
[IocRegister]
internal class MyQueryHandler : IRequestHandler<MyQuery, string>;Generated Code
csharp
// <auto-generated/>
// MyCommandHandler: QueryCachingDecorator skipped (MyCommand doesn't implement IQuery)
services.AddScoped<global::MyNamespace.MyCommandHandler, global::MyNamespace.MyCommandHandler>();
services.AddScoped<global::MyNamespace.IRequestHandler<global::MyNamespace.MyCommand, bool>>((global::System.IServiceProvider sp) =>
{
var s0 = sp.GetRequiredService<global::MyNamespace.MyCommandHandler>();
var s1 = new global::MyNamespace.LoggingDecorator<global::MyNamespace.MyCommand, bool>(s0);
return s1;
});
// MyQueryHandler: Both decorators applied (MyQuery implements IQuery)
services.AddScoped<global::MyNamespace.MyQueryHandler, global::MyNamespace.MyQueryHandler>();
services.AddScoped<global::MyNamespace.IRequestHandler<global::MyNamespace.MyQuery, string>>((global::System.IServiceProvider sp) =>
{
var s0 = sp.GetRequiredService<global::MyNamespace.MyQueryHandler>();
var s1 = new global::MyNamespace.QueryCachingDecorator<global::MyNamespace.MyQuery, string>(s0);
var s2 = new global::MyNamespace.LoggingDecorator<global::MyNamespace.MyQuery, string>(s1);
return s2;
});