pvNugsMediatorNc9Abstractions 9.0.7

dotnet add package pvNugsMediatorNc9Abstractions --version 9.0.7
                    
NuGet\Install-Package pvNugsMediatorNc9Abstractions -Version 9.0.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="pvNugsMediatorNc9Abstractions" Version="9.0.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="pvNugsMediatorNc9Abstractions" Version="9.0.7" />
                    
Directory.Packages.props
<PackageReference Include="pvNugsMediatorNc9Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add pvNugsMediatorNc9Abstractions --version 9.0.7
                    
#r "nuget: pvNugsMediatorNc9Abstractions, 9.0.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package pvNugsMediatorNc9Abstractions@9.0.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=pvNugsMediatorNc9Abstractions&version=9.0.7
                    
Install as a Cake Addin
#tool nuget:?package=pvNugsMediatorNc9Abstractions&version=9.0.7
                    
Install as a Cake Tool

pvNugsMediatorNc9Abstractions

๐ŸŽฏ A lightweight, flexible mediator pattern implementation for .NET 9 that decouples application components through request/response and publish/subscribe messaging patterns.

Features

โœจ Request/Response Pattern: Send commands and queries to single handlers with typed responses
๐Ÿ“ข Publish/Subscribe Pattern: Broadcast notifications to multiple handlers concurrently
๐Ÿ”„ Pipeline Behaviors: Add cross-cutting concerns like logging, validation, and caching
๐ŸŽญ Cleaner Void Handlers: Single-parameter handlers return Task instead of Task<Unit> - no more Unit.Value!
๐Ÿ”Œ Dependency Injection Ready: Seamlessly integrates with Microsoft.Extensions.DependencyInjection
๐Ÿ“ Fully Documented: Comprehensive XML documentation with IntelliSense support
๐Ÿงช Testable: Design promotes clean architecture and testability
โšก Async First: Built for modern asynchronous programming patterns
๐Ÿ” Simplified API: PvNugs handlers use only HandleAsync - no dual method implementation needed
๐Ÿ” Handler Introspection: Discover and validate registered handlers at runtime (PvNugs exclusive)
โš™๏ธ Flexible Discovery Modes: Manual, Decorated, or FullScan handler registration strategies

Architecture & Backward Compatibility

This package provides two layers of interfaces for maximum flexibility:

Base Interfaces (Mediator namespace)

Framework-agnostic interfaces that define the core mediator pattern (MediatR-compatible):

  • IMediator, IRequest<TResponse>, IRequestHandler<TRequest, TResponse> (uses Handle method)
  • INotification, INotificationHandler<TNotification> (uses Handle method)
  • IPipelineBehavior<TRequest, TResponse> (uses Handle method)

PvNugs Interfaces (pvNugs namespace)

Branded interfaces with explicit async naming - standalone, not inheriting from base:

  • IPvNugsMediator : IMediator (extends base mediator for compatibility)
  • IPvNugsMediatorRequest<TResponse> : IRequest<TResponse> (constraint only, not inheritance)
  • IPvNugsMediatorRequestHandler<TRequest, TResponse> (uses HandleAsync method)
  • IPvNugsMediatorNotificationHandler<TNotification> (uses HandleAsync method)
  • IPvNugsMediatorPipelineRequestHandler<TRequest, TResponse> (uses HandleAsync method)

Why This Dual Design?

Backward Compatible โœ…

// v9.0.6: New code is simpler - just HandleAsync!
public class NewHandler : IPvNugsMediatorRequestHandler<GetUserRequest, User>
{
    public async Task<User> HandleAsync(GetUserRequest request, CancellationToken ct)
    {
        return await _repository.GetByIdAsync(request.UserId, ct);
    }
}

// Existing code from v9.0.5 with BOTH methods still works!
public class ExistingHandler : IPvNugsMediatorRequestHandler<GetProductRequest, Product>
{
    public async Task<Product> HandleAsync(GetProductRequest request, CancellationToken ct)
    {
        return await _repository.GetByIdAsync(request.ProductId, ct);
    }
    
    // This extra method from v9.0.5 doesn't break anything - just not required anymore
    public Task<Product> Handle(GetProductRequest request, CancellationToken ct)
        => HandleAsync(request, ct);
}

Simplified Development โœจ

// PvNugs handlers are simple - just implement HandleAsync!
public class GetUserHandler : IPvNugsMediatorRequestHandler<GetUserRequest, User>
{
    public async Task<User> HandleAsync(GetUserRequest request, CancellationToken ct)
    {
        // Your logic here - that's it!
        return await _repository.GetByIdAsync(request.UserId, ct);
    }
}

// MediatR handlers use Handle method
public class MediatRHandler : IRequestHandler<GetUserRequest, User>
{
    public async Task<User> Handle(GetUserRequest request, CancellationToken ct)
    {
        return await _repository.GetByIdAsync(request.UserId, ct);
    }
}

Backward Compatibility ๐Ÿ”

// The MEDIATOR supports both interface types seamlessly
services.AddScoped<IPvNugsMediator, Mediator>();

// PvNugs handlers
services.AddTransient<IPvNugsMediatorRequestHandler<GetUserRequest, User>, PvNugsHandler>();

// MediatR handlers - work side by side!
services.AddTransient<IRequestHandler<GetProductRequest, Product>, MediatRHandler>();

// Both work through the same mediator
public class MyService(IPvNugsMediator mediator)
{
    public async Task DoWork()
    {
        var user = await mediator.SendAsync(new GetUserRequest());    // PvNugs handler
        var product = await mediator.SendAsync(new GetProductRequest()); // MediatR handler
    }
}

PvNugs Exclusive Features ๐ŸŒŸ

// IPvNugsMediator adds value beyond IMediator
var pvNugsMediator = app.Services.GetRequiredService<IPvNugsMediator>();

// Handler introspection (not available in base IMediator)
var handlers = pvNugsMediator.GetRegisteredHandlers();
foreach (var handler in handlers)
{
    Console.WriteLine($"{handler.RegistrationType}: {handler.ImplementationType.Name}");
}

Installation

Install via NuGet Package Manager:

Install-Package pvNugsMediatorNc9Abstractions

Or via .NET CLI:

dotnet add package pvNugsMediatorNc9Abstractions

Core Components

๐ŸŽฏ Mediator Interfaces

  • IMediator: Base mediator interface for routing requests and publishing notifications
  • IPvNugsMediator: PvNugs-branded mediator that extends IMediator for backward compatibility

๐Ÿ“จ Request/Response

Base Interfaces:

  • IRequest<TResponse>: Base marker interface for requests expecting a response
  • IRequest: Base convenience interface for requests returning Unit (void-like)
  • IRequestHandler<TRequest, TResponse>: Base handler for processing requests (returns Task<TResponse>)
  • IRequestHandler<TRequest>: Base handler for void-like requests (returns Task, not Task<Unit>)

PvNugs Interfaces:

  • IPvNugsMediatorRequest<TResponse>: PvNugs-branded request interface
  • IPvNugsMediatorRequest: PvNugs-branded void-like request interface
  • IPvNugsMediatorRequestHandler<TRequest, TResponse>: PvNugs-branded request handler (returns Task<TResponse>)
  • IPvNugsMediatorRequestHandler<TRequest>: PvNugs-branded void-like request handler (returns Task, not Task<Unit>)

๐Ÿ“ข Publish/Subscribe

Base Interfaces:

  • INotification: Base marker interface for notifications
  • INotificationHandler<TNotification>: Base handler for processing notifications

PvNugs Interfaces:

  • IPvNugsMediatorNotification: PvNugs-branded notification interface
  • IPvNugsMediatorNotificationHandler<TNotification>: PvNugs-branded notification handler

๐Ÿ”„ Pipeline Behaviors

Base Interfaces:

  • IPipelineBehavior<TRequest, TResponse>: Base interface for pipeline behaviors
  • RequestHandlerDelegate<TResponse>: Delegate for invoking the next handler in the pipeline

PvNugs Interfaces:

  • IPvNugsMediatorPipelineRequestHandler<TRequest, TResponse>: PvNugs-branded pipeline behavior

๐ŸŽญ Unit Type

  • Unit: Represents a void-like return type for requests that don't return meaningful data

โš™๏ธ Discovery & Configuration

  • DiscoveryMode: Enum specifying handler discovery strategy (Manual, Decorated, FullScan)
  • MediatorHandlerAttribute: Decorator attribute for Decorated mode handler discovery
  • ServiceLifetime: Enum for specifying DI lifetime (Transient, Scoped, Singleton)
  • MediatorRegistrationInfo: Information about registered handlers (for introspection)

๐Ÿ“ Key Difference: HandleAsync vs Handle

PvNugs handler interfaces use HandleAsync - simple, explicit async naming:

  • IPvNugsMediatorRequestHandler<TRequest, TResponse> โ†’ HandleAsync method
  • IPvNugsMediatorNotificationHandler<TNotification> โ†’ HandleAsync method
  • IPvNugsMediatorPipelineRequestHandler<TRequest, TResponse> โ†’ HandleAsync method

Base MediatR interfaces use Handle - traditional naming:

  • IRequestHandler<TRequest, TResponse> โ†’ Handle method
  • INotificationHandler<TNotification> โ†’ Handle method
  • IPipelineBehavior<TRequest, TResponse> โ†’ Handle method

No delegation needed! Each interface has only ONE method to implement:

// PvNugs handler - just HandleAsync
public class PvNugsHandler : IPvNugsMediatorRequestHandler<GetUserRequest, User>
{
    public async Task<User> HandleAsync(GetUserRequest request, CancellationToken ct)
    {
        // Your implementation - that's it!
        return await _repository.GetByIdAsync(request.UserId, ct);
    }
}

// MediatR handler - just Handle  
public class MediatRHandler : IRequestHandler<GetUserRequest, User>
{
    public async Task<User> Handle(GetUserRequest request, CancellationToken ct)
    {
        return await _repository.GetByIdAsync(request.UserId, ct);
    }
}

Quick Start

1๏ธโƒฃ Request/Response Pattern

Define a Query Request

using pvNugsMediatorNc9Abstractions;

public class GetUserByIdRequest : IPvNugsMediatorRequest<User>
{
    public int UserId { get; init; }
}

public class GetUserByIdHandler : IPvNugsMediatorRequestHandler<GetUserByIdRequest, User>
{
    private readonly IUserRepository _userRepository;
    
    public GetUserByIdHandler(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }
    
    public async Task<User> HandleAsync(
        GetUserByIdRequest request, 
        CancellationToken cancellationToken)
    {
        return await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
    }
}

Define a Command Request (void-like)

public class DeleteUserRequest : IPvNugsMediatorRequest
{
    public int UserId { get; init; }
}

public class DeleteUserHandler : IPvNugsMediatorRequestHandler<DeleteUserRequest>
{
    private readonly IUserRepository _userRepository;
    
    public DeleteUserHandler(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }
    
    public async Task HandleAsync(
        DeleteUserRequest request, 
        CancellationToken cancellationToken)
    {
        await _userRepository.DeleteAsync(request.UserId, cancellationToken);
        // No need to return Unit.Value! Just returns Task
    }
}

2๏ธโƒฃ Publish/Subscribe Pattern

Define a Notification

public class UserCreatedNotification : IPvNugsMediatorNotification
{
    public int UserId { get; init; }
    public string Email { get; init; }
}

// First Handler - Send Welcome Email
public class SendWelcomeEmailHandler : IPvNugsMediatorNotificationHandler<UserCreatedNotification>
{
    private readonly IEmailService _emailService;
    
    public SendWelcomeEmailHandler(IEmailService emailService)
    {
        _emailService = emailService;
    }
    
    public async Task HandleAsync(
        UserCreatedNotification notification, 
        CancellationToken cancellationToken)
    {
        await _emailService.SendWelcomeEmailAsync(notification.Email, cancellationToken);
    }
}

// Second Handler - Log Event
public class LogUserCreationHandler : IPvNugsMediatorNotificationHandler<UserCreatedNotification>
{
    private readonly ILogger _logger;
    
    public LogUserCreationHandler(ILogger logger)
    {
        _logger = logger;
    }
    
    public async Task HandleAsync(
        UserCreatedNotification notification, 
        CancellationToken cancellationToken)
    {
        _logger.LogInformation("User {UserId} created", notification.UserId);
        await Task.CompletedTask;
    }
}

3๏ธโƒฃ Pipeline Behaviors

Logging Pipeline

public class LoggingPipeline<TRequest, TResponse> 
    : IPvNugsMediatorPipelineRequestHandler<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly ILogger _logger;
    
    public LoggingPipeline(ILogger logger)
    {
        _logger = logger;
    }
    
    public async Task<TResponse> HandleAsync(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        var requestName = typeof(TRequest).Name;
        _logger.LogInformation("Handling {RequestName}", requestName);
        
        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            var response = await next();
            stopwatch.Stop();
            
            _logger.LogInformation(
                "Handled {RequestName} in {ElapsedMs}ms", 
                requestName, 
                stopwatch.ElapsedMilliseconds);
            
            return response;
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            _logger.LogError(ex, 
                "Error handling {RequestName} after {ElapsedMs}ms", 
                requestName, 
                stopwatch.ElapsedMilliseconds);
            throw;
        }
    }
}

Validation Pipeline

public class ValidationPipeline<TRequest, TResponse> 
    : IPvNugsMediatorPipelineRequestHandler<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    public async Task<TResponse> HandleAsync(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        if (request is IValidatable validatable)
        {
            var errors = validatable.Validate();
            if (errors.Any())
            {
                throw new ValidationException(errors);
            }
        }
        
        return await next();
    }
}

4๏ธโƒฃ Dependency Injection Setup

using Microsoft.Extensions.DependencyInjection;
using pvNugsMediatorNc9Abstractions;

var services = new ServiceCollection();

// Register handlers
services.AddTransient<IPvNugsMediatorRequestHandler<GetUserByIdRequest, User>, GetUserByIdHandler>();
services.AddTransient<IPvNugsMediatorRequestHandler<DeleteUserRequest>, DeleteUserHandler>();

// Register notification handlers (can have multiple for same notification)
services.AddTransient<IPvNugsMediatorNotificationHandler<UserCreatedNotification>, SendWelcomeEmailHandler>();
services.AddTransient<IPvNugsMediatorNotificationHandler<UserCreatedNotification>, LogUserCreationHandler>();

// Register pipeline behaviors (executed in order)
services.AddTransient<IPvNugsMediatorPipelineRequestHandler<GetUserByIdRequest, User>, LoggingPipeline<GetUserByIdRequest, User>>();
services.AddTransient<IPvNugsMediatorPipelineRequestHandler<GetUserByIdRequest, User>, ValidationPipeline<GetUserByIdRequest, User>>();

// Register the mediator implementation (from pvNugsMediatorNc9 package)
services.TryAddPvNugsMediator(); // Requires pvNugsMediatorNc9 package

5๏ธโƒฃ Using the Mediator

public class UserService
{
    private readonly IPvNugsMediator _mediator;
    
    public UserService(IPvNugsMediator mediator)
    {
        _mediator = mediator;
    }
    
    public async Task<User> GetUserAsync(int userId)
    {
        // Send query request and get response
        var user = await _mediator.Send(new GetUserByIdRequest { UserId = userId });
        return user;
    }
    
    public async Task DeleteUserAsync(int userId)
    {
        // Send command request (returns Unit)
        await _mediator.Send(new DeleteUserRequest { UserId = userId });
    }
    
    public async Task CreateUserAsync(User user)
    {
        // Save user...
        
        // Publish notification to all subscribers
        await _mediator.Publish(new UserCreatedNotification 
        { 
            UserId = user.Id, 
            Email = user.Email 
        });
    }
}

6๏ธโƒฃ Using Base Interfaces (MediatR Compatibility)

You can also use base MediatR interfaces for framework-agnostic code:

using pvNugsMediatorNc9Abstractions.Mediator; // Base interfaces

// Define using base interfaces
public class GetProductRequest : IRequest<Product>
{
    public int ProductId { get; init; }
}

public class GetProductHandler : IRequestHandler<GetProductRequest, Product>
{
    // Base handlers use Handle method (MediatR convention)
    public async Task<Product> Handle(
        GetProductRequest request, 
        CancellationToken cancellationToken)
    {
        // Implementation
        return new Product();
    }
}

// Inject using base interface
public class ProductService
{
    private readonly IMediator _mediator; // Base interface
    
    public ProductService(IMediator mediator)
    {
        _mediator = mediator;
    }
    
    public async Task<Product> GetProductAsync(int id)
    {
        return await _mediator.Send(new GetProductRequest { ProductId = id });
    }
}

Note: Both approaches work with the same mediator implementation:

  • Use PvNugs interfaces (HandleAsync) for explicit async naming
  • Use Base interfaces (Handle) for MediatR compatibility
  • The mediator automatically detects and invokes the correct method

Design Patterns

๐ŸŽฏ CQRS (Command Query Responsibility Segregation)

The mediator pattern naturally supports CQRS by separating:

  • Commands: Requests that modify state (often return Unit)
  • Queries: Requests that read data (return typed results)
// Command
public class CreateUserCommand : IPvNugsMediatorRequest<Guid>
{
    public string Username { get; init; }
    public string Email { get; init; }
}

// Query
public class GetUserQuery : IPvNugsMediatorRequest<User>
{
    public int UserId { get; init; }
}

๐Ÿ”„ Chain of Responsibility

Pipeline behaviors implement the chain of responsibility pattern:

Request โ†’ ValidationPipeline โ†’ LoggingPipeline โ†’ CachingPipeline โ†’ Handler โ†’ Response

๐Ÿ“ข Observer Pattern

Notifications implement the observer pattern, allowing multiple handlers to react to events without tight coupling.

Advanced Scenarios

Polymorphic Notifications

object notification = new UserCreatedNotification { UserId = 123, Email = "user@example.com" };
await _mediator.PublishAsync(notification); // Uses runtime type resolution

Cancellation Support

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var user = await _mediator.Send(
    new GetUserByIdRequest { UserId = 123 }, 
    cts.Token);

Generic Pipeline for All Requests

// This pipeline will be registered for ALL request types
services.AddTransient(typeof(IPvNugsMediatorPipelineRequestHandler<,>), typeof(LoggingPipeline<,>));

Handler Introspection (PvNugs Exclusive) ๐Ÿ”

The IPvNugsMediator interface provides a powerful introspection feature to discover all registered handlers in the DI container:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        var mediator = app.ApplicationServices.GetRequiredService<IPvNugsMediator>();
        
        // Get all registered handlers
        var registrations = mediator.GetRegisteredHandlers();
        
        Console.WriteLine($"\n๐Ÿ“‹ Registered Mediator Components ({registrations.Count()}):\n");
        
        // Group by type for better organization
        var grouped = registrations.GroupBy(r => r.RegistrationType);
        
        foreach (var group in grouped)
        {
            Console.WriteLine($"  {group.Key}: {group.Count()}");
            foreach (var reg in group.OrderBy(r => r.ImplementationType.Name))
            {
                Console.WriteLine($"    โ€ข {reg.ImplementationType.Name}");
                if (reg.MessageType != null)
                {
                    Console.WriteLine($"      Handles: {reg.MessageType.Name}");
                }
                if (reg.ResponseType != null)
                {
                    Console.WriteLine($"      Returns: {reg.ResponseType.Name}");
                }
                Console.WriteLine($"      Lifetime: {reg.Lifetime}");
            }
            Console.WriteLine();
        }
    }
}

Use Cases:

1. Validation During Startup

// Ensure all expected handlers are registered
var mediator = services.BuildServiceProvider().GetRequiredService<IPvNugsMediator>();
var handlers = mediator.GetRegisteredHandlers();

var requiredHandlers = new[] { "GetUserByIdRequest", "CreateUserRequest", "DeleteUserRequest" };
var registeredRequests = handlers
    .Where(h => h.MessageType != null)
    .Select(h => h.MessageType!.Name)
    .ToHashSet();

foreach (var required in requiredHandlers)
{
    if (!registeredRequests.Contains(required))
    {
        throw new InvalidOperationException($"Required handler for {required} is not registered!");
    }
}

2. Health Check Endpoint

app.MapGet("/health/mediator", (IPvNugsMediator mediator) =>
{
    var handlers = mediator.GetRegisteredHandlers();
    
    return Results.Ok(new
    {
        Status = "Healthy",
        TotalHandlers = handlers.Count(),
        RequestHandlers = handlers.Count(r => r.RegistrationType.Contains("Request")),
        NotificationHandlers = handlers.Count(r => r.RegistrationType.Contains("Notification")),
        PipelineBehaviors = handlers.Count(r => r.RegistrationType.Contains("Pipeline")),
        Details = handlers.Select(h => new
        {
            h.RegistrationType,
            Implementation = h.ImplementationType.Name,
            Message = h.MessageType?.Name,
            Response = h.ResponseType?.Name,
            h.Lifetime
        })
    });
});

3. Generate Documentation

// Auto-generate markdown documentation of all handlers
var mediator = services.BuildServiceProvider().GetRequiredService<IPvNugsMediator>();
var handlers = mediator.GetRegisteredHandlers();

var markdown = new StringBuilder();
markdown.AppendLine("# Available Handlers\n");

var requestHandlers = handlers.Where(h => h.RegistrationType.Contains("Request"));
markdown.AppendLine("## Request Handlers\n");
foreach (var handler in requestHandlers.OrderBy(h => h.MessageType?.Name))
{
    markdown.AppendLine($"- **{handler.MessageType?.Name}**");
    markdown.AppendLine($"  - Handler: `{handler.ImplementationType.Name}`");
    markdown.AppendLine($"  - Returns: `{handler.ResponseType?.Name ?? "Unit"}`");
    markdown.AppendLine();
}

File.WriteAllText("handlers.md", markdown.ToString());

4. Development Dashboard

#if DEBUG
// Show registered handlers in console during development
app.Lifetime.ApplicationStarted.Register(() =>
{
    var mediator = app.Services.GetRequiredService<IPvNugsMediator>();
    var handlers = mediator.GetRegisteredHandlers();
    
    Console.WriteLine("\n" + new string('=', 60));
    Console.WriteLine("  MEDIATOR CONFIGURATION");
    Console.WriteLine(new string('=', 60));
    
    foreach (var handler in handlers.OrderBy(h => h.RegistrationType).ThenBy(h => h.ImplementationType.Name))
    {
        Console.WriteLine(handler); // Uses ToString() for formatted output
    }
    
    Console.WriteLine(new string('=', 60) + "\n");
});
#endif

๐Ÿ’ก Performance Note: GetRegisteredHandlers() uses reflection and should be called during startup/diagnostics only, not in request hot paths.

Handler Discovery Modes โš™๏ธ

The mediator implementation supports three discovery strategies, allowing you to choose the right balance between performance, convenience, and control:

Discovery Mode Comparison

Mode Registration Startup Time Runtime Performance Best For
Manual Explicit DI registration โšก Fastest โšกโšกโšก Best Production
Decorated [MediatorHandler] attribute โšก Fast โšกโšก Good Medium apps
FullScan Automatic via reflection โฑ๏ธ Slower โšกโšก Good Development

Explicit control with best performance

// Configuration
services.AddPvNugsMediator(options => 
{
    options.DiscoveryMode = DiscoveryMode.Manual;
});

// Manually register each handler
services.AddTransient<IPvNugsMediatorRequestHandler<GetUserRequest, User>, GetUserHandler>();
services.AddTransient<IPvNugsMediatorRequestHandler<CreateUserRequest>, CreateUserHandler>();
services.AddTransient<IPvNugsNotificationHandler<UserCreatedNotification>, SendEmailHandler>();
services.AddTransient<IPvNugsNotificationHandler<UserCreatedNotification>, LogEventHandler>();

Pros:

  • โšก Best performance: No reflection, no scanning
  • ๐ŸŽฏ Explicit control: You know exactly what's registered
  • ๐Ÿ”’ Type safety: Compile-time validation
  • ๐Ÿ“ฆ Smallest footprint: No extra metadata

Cons:

  • ๐Ÿ”ง More code: Each handler needs explicit registration
  • ๐Ÿ“ Maintenance: Must update registrations when adding handlers

Use When:

  • Production environments where performance matters
  • Large applications with many handlers
  • Explicit configuration is preferred
  • Compile-time safety is important

2๏ธโƒฃ Decorated Mode (Balanced Approach)

Convention-based with selective discovery

// Configuration
services.AddPvNugsMediator(options => 
{
    options.DiscoveryMode = DiscoveryMode.Decorated;
});

// Handlers are automatically discovered if decorated
[MediatorHandler]
public class GetUserHandler : IPvNugsMediatorRequestHandler<GetUserRequest, User>
{
    public async Task<User> HandleAsync(GetUserRequest request, CancellationToken ct)
    {
        return await _repository.GetByIdAsync(request.UserId, ct);
    }
}

// Specify lifetime if needed (default is Transient)
[MediatorHandler(Lifetime = ServiceLifetime.Scoped)]
public class CreateUserHandler : IPvNugsMediatorRequestHandler<CreateUserRequest>
{
    private readonly UserDbContext _context;
    
    public CreateUserHandler(UserDbContext context)
    {
        _context = context;
    }
    
    public async Task HandleAsync(CreateUserRequest request, CancellationToken ct)
    {
        // Implementation - no need to return Unit.Value!
        await _context.Users.AddAsync(request.User, ct);
        await _context.SaveChangesAsync(ct);
    }
}

// This handler is NOT discovered (no attribute)
public class InternalHelper : IPvNugsMediatorRequestHandler<HelperRequest, Result>
{
    // Not registered because it lacks [MediatorHandler]
}

Pros:

  • ๐ŸŽฏ Selective discovery: Only decorated handlers are registered
  • โšก Good performance: Faster than FullScan
  • โœจ Convention-based: Simple attribute marks handlers
  • ๐Ÿ” Self-documenting: Clear which classes are handlers
  • โฑ๏ธ Lifetime control: Specify Transient/Scoped/Singleton per handler

Cons:

  • ๐Ÿ“ Requires attributes: Must decorate every handler
  • ๐Ÿ” Some reflection: Scans for decorated types
  • ๐ŸŽ“ Learning curve: Team needs to understand convention

Use When:

  • Medium to large applications
  • You want selective handler registration
  • Convention over configuration is preferred
  • You need per-handler lifetime control

3๏ธโƒฃ FullScan Mode (Development Convenience)

Zero configuration, maximum convenience

// Configuration
services.AddPvNugsMediator(options => 
{
    options.DiscoveryMode = DiscoveryMode.FullScan;
    
    // Optionally limit which assemblies to scan for better performance
    options.AssembliesToScan = new[] 
    { 
        typeof(GetUserHandler).Assembly,    // Your handlers
        typeof(OrderHandlers).Assembly      // Another assembly
    };
});

// Handlers are automatically discovered - no registration or attributes needed!
public class GetUserHandler : IPvNugsMediatorRequestHandler<GetUserRequest, User>
{
    public async Task<User> HandleAsync(GetUserRequest request, CancellationToken ct)
    {
        return await _repository.GetByIdAsync(request.UserId, ct);
    }
}

public class SendEmailHandler : IPvNugsMediatorNotificationHandler<UserCreatedNotification>
{
    public async Task HandleAsync(UserCreatedNotification notification, CancellationToken ct)
    {
        await _emailService.SendWelcomeEmailAsync(notification.Email, ct);
    }
}
// Both handlers above are automatically registered!

Pros:

  • ๐Ÿš€ Zero configuration: Just implement the interface
  • โœจ Maximum convenience: Perfect for rapid development
  • ๐Ÿ”„ Automatic: New handlers are instantly available
  • ๐ŸŽฏ Simple: No attributes, no registrations

Cons:

  • โฑ๏ธ Startup overhead: Reflection scanning takes time
  • ๐Ÿ” Scans all types: May discover unintended handlers
  • ๐Ÿ’พ More memory: Keeps handler metadata
  • ๐ŸŽญ Less explicit: Harder to see what's registered

Use When:

  • Development and prototyping
  • Small to medium applications
  • Convenience > performance
  • Rapid iteration is important
  • Applications restart infrequently

Choosing the Right Mode

public void ConfigureServices(IServiceCollection services)
{
    services.AddPvNugsMediator(options =>
    {
        // Production: Use Manual for best performance
        if (Environment.IsProduction())
        {
            options.DiscoveryMode = DiscoveryMode.Manual;
        }
        // Development: Use FullScan for convenience
        else if (Environment.IsDevelopment())
        {
            options.DiscoveryMode = DiscoveryMode.FullScan;
            options.AssembliesToScan = new[] { typeof(Startup).Assembly };
        }
        // Staging: Use Decorated for balance
        else
        {
            options.DiscoveryMode = DiscoveryMode.Decorated;
        }
    });
    
    // Manual mode: Add explicit registrations here
    if (options.DiscoveryMode == DiscoveryMode.Manual)
    {
        services.AddTransient<IPvNugsMediatorRequestHandler<GetUserRequest, User>, GetUserHandler>();
        // ... more registrations
    }
}

Decision Guide:

  • ๐Ÿ“Š Small app (<20 handlers) โ†’ FullScan for convenience
  • ๐Ÿ“ˆ Medium app (20-100 handlers) โ†’ Decorated for balance
  • ๐Ÿข Large app (100+ handlers) โ†’ Manual for control
  • โšก Performance critical โ†’ Manual
  • ๐Ÿš€ Rapid prototyping โ†’ FullScan
  • ๐Ÿ”’ Explicit control needed โ†’ Manual
  • โœจ Convention preferred โ†’ Decorated

Available Implementations

This abstractions package provides the foundation for the mediator pattern. For a ready-to-use concrete implementation, use:

  • pvNugsMediatorNc9: Full mediator implementation with pipeline support and DI integration

Benefits

โœ… Reduced Coupling: Components communicate through the mediator, not directly
โœ… Single Responsibility: Handlers focus on one specific task
โœ… Open/Closed Principle: Add new handlers without modifying existing code
โœ… Testability: Easy to mock the mediator for unit testing
โœ… Cross-Cutting Concerns: Pipeline behaviors handle logging, validation, etc.
โœ… Clean Architecture: Supports domain-driven design and clean architecture principles

Best Practices

  1. Keep Handlers Focused: Each handler should do one thing well
  2. Use Pipelines for Cross-Cutting Concerns: Don't repeat logging/validation in every handler
  3. Prefer Unit over bool for Commands: More explicit than returning success flags
  4. Name Requests Clearly: Use suffixes like Request, Command, Query, or Notification
  5. Validate Early: Use validation pipelines to fail fast
  6. Handle Exceptions Gracefully: Use error handling pipelines for consistent error responses

Thread Safety

  • The mediator is designed to be thread-safe when used with proper DI container scoping
  • Notification handlers typically execute concurrently - ensure handlers are thread-safe if needed
  • Pipeline behaviors execute sequentially in the order they are registered

Performance Considerations

  • Request handlers are resolved from DI on each request
  • Notification handlers are resolved and invoked concurrently for better performance
  • Pipeline overhead is minimal (typically < 1ms per pipeline)

Compatibility

  • .NET Version: .NET 9.0+
  • DI Container: Microsoft.Extensions.DependencyInjection
  • C# Version: C# 12.0+

License

MIT License - see LICENSE file for details

Author

Pierre Van Wallendael - pvWay Ltd

Repository

GitHub Repository


๐Ÿ’ก Tip: Combine with pvNugsLoggerNc9Abstractions for comprehensive logging in your handlers and pipelines!

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net9.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on pvNugsMediatorNc9Abstractions:

Package Downloads
pvNugsMediatorNc9

Production-ready mediator pattern implementation for .NET 9 with built-in logging, dynamic handler resolution via reflection and DI, full pipeline behavior support, and comprehensive error handling. NEW v9.0.4: Task-returning void handlers eliminate Unit.Value boilerplate for cleaner code. Registered as scoped service for proper WebAPI integration with scoped dependencies (DbContext, HttpContext). Requires pvNugsLoggerNc9Abstractions for automatic operation logging.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
9.0.7 189 4/1/2026
9.0.6 182 1/21/2026
9.0.5 140 1/21/2026
9.0.4 135 1/21/2026
9.0.3 154 1/19/2026
9.0.2 139 1/19/2026
9.0.1 139 1/19/2026
9.0.0 160 1/12/2026

v9.0.7: Cleaner void-like handlers! IRequestHandler<TRequest> and IPvNugsMediatorRequestHandler<TRequest> now return Task instead of Task<Unit> - no need to return Unit.Value anymore! Single-parameter interfaces no longer inherit from two-parameter versions, providing cleaner API. Fully backward compatible - existing handlers continue to work. Previous: v9.0.6 simplified PvNugs handler interfaces.