Skip to content

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 -> MyHandler
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 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;
});

← Back to Overview

Released under the MIT License.