Skip to main content

Pipeline Behaviors

Pipeline composition is now static and based on GenDI decorators. Use DecoratorForAttribute with handler interfaces.

Request Decorator (logging + timing)

using System.Diagnostics;
using GenDI.Attributes;
using Microsoft.Extensions.Logging;
using NetMediate;

public record GetProductQuery(string ProductId);
public record ProductDto(string Id, string Name, decimal Price);

[DecoratorFor<IRequestHandler<GetProductQuery, ProductDto>>(Order = 1)]
public sealed class TimingLoggingDecorator(IRequestHandler<GetProductQuery, ProductDto> inner)
: IRequestHandler<GetProductQuery, ProductDto>
{
[Inject] public required ILogger<TimingLoggingDecorator> Logger { get; init; }

public async Task<ProductDto> Handle(
GetProductQuery message,
CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();
Logger.LogInformation("Handling {Query}", nameof(GetProductQuery));

var result = await inner.Handle(message, cancellationToken);

Logger.LogInformation("{Query} completed in {ElapsedMs} ms", nameof(GetProductQuery), sw.ElapsedMilliseconds);
return result;
}
}

Command Decorator (audit trail)

Records every command dispatch before forwarding to the next handler.

using GenDI.Attributes;
using NetMediate;

[ServiceInjection]
public interface IAuditWriter
{
Task WriteAsync(string commandName, object? key, CancellationToken ct);
}

[DecoratorFor<ICommandHandler<PlaceOrderCommand>>(Order = 1)]
public sealed class CommandAuditDecorator(ICommandHandler<PlaceOrderCommand> inner) : ICommandHandler<PlaceOrderCommand>
{
[Inject] public required IAuditWriter Audit { get; init; }

public async Task Handle(
PlaceOrderCommand message,
CancellationToken cancellationToken = default)
{
await Audit.WriteAsync(nameof(PlaceOrderCommand), null, cancellationToken);
await inner.Handle(message, cancellationToken);
}
}

Notification Decorator

Wraps notification handling with context enrichment.

using GenDI.Attributes;
using Microsoft.Extensions.Logging;
using NetMediate;

[DecoratorFor<INotificationHandler<UserRegistered>>(Order = 1)]
public sealed class NotificationLoggingDecorator(INotificationHandler<UserRegistered> inner)
: INotificationHandler<UserRegistered>
{
[Inject] public required ILogger<NotificationLoggingDecorator> Logger { get; init; }

public async Task Handle(
UserRegistered message,
CancellationToken cancellationToken = default)
{
using (Logger.BeginScope(new Dictionary<string, object>
{
["UserId"] = message.UserId,
["NotificationType"] = nameof(UserRegistered)
}))
{
await inner.Handle(message, cancellationToken);
}
}
}