pvNugsMediatorNc9Abstractions 9.0.7
dotnet add package pvNugsMediatorNc9Abstractions --version 9.0.7
NuGet\Install-Package pvNugsMediatorNc9Abstractions -Version 9.0.7
<PackageReference Include="pvNugsMediatorNc9Abstractions" Version="9.0.7" />
<PackageVersion Include="pvNugsMediatorNc9Abstractions" Version="9.0.7" />
<PackageReference Include="pvNugsMediatorNc9Abstractions" />
paket add pvNugsMediatorNc9Abstractions --version 9.0.7
#r "nuget: pvNugsMediatorNc9Abstractions, 9.0.7"
#:package pvNugsMediatorNc9Abstractions@9.0.7
#addin nuget:?package=pvNugsMediatorNc9Abstractions&version=9.0.7
#tool nuget:?package=pvNugsMediatorNc9Abstractions&version=9.0.7
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>(usesHandlemethod)INotification,INotificationHandler<TNotification>(usesHandlemethod)IPipelineBehavior<TRequest, TResponse>(usesHandlemethod)
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>(usesHandleAsyncmethod)IPvNugsMediatorNotificationHandler<TNotification>(usesHandleAsyncmethod)IPvNugsMediatorPipelineRequestHandler<TRequest, TResponse>(usesHandleAsyncmethod)
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 notificationsIPvNugsMediator: PvNugs-branded mediator that extendsIMediatorfor backward compatibility
๐จ Request/Response
Base Interfaces:
IRequest<TResponse>: Base marker interface for requests expecting a responseIRequest: Base convenience interface for requests returningUnit(void-like)IRequestHandler<TRequest, TResponse>: Base handler for processing requests (returnsTask<TResponse>)IRequestHandler<TRequest>: Base handler for void-like requests (returnsTask, notTask<Unit>)
PvNugs Interfaces:
IPvNugsMediatorRequest<TResponse>: PvNugs-branded request interfaceIPvNugsMediatorRequest: PvNugs-branded void-like request interfaceIPvNugsMediatorRequestHandler<TRequest, TResponse>: PvNugs-branded request handler (returnsTask<TResponse>)IPvNugsMediatorRequestHandler<TRequest>: PvNugs-branded void-like request handler (returnsTask, notTask<Unit>)
๐ข Publish/Subscribe
Base Interfaces:
INotification: Base marker interface for notificationsINotificationHandler<TNotification>: Base handler for processing notifications
PvNugs Interfaces:
IPvNugsMediatorNotification: PvNugs-branded notification interfaceIPvNugsMediatorNotificationHandler<TNotification>: PvNugs-branded notification handler
๐ Pipeline Behaviors
Base Interfaces:
IPipelineBehavior<TRequest, TResponse>: Base interface for pipeline behaviorsRequestHandlerDelegate<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 discoveryServiceLifetime: 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>โHandleAsyncmethodIPvNugsMediatorNotificationHandler<TNotification>โHandleAsyncmethodIPvNugsMediatorPipelineRequestHandler<TRequest, TResponse>โHandleAsyncmethod
Base MediatR interfaces use Handle - traditional naming:
IRequestHandler<TRequest, TResponse>โHandlemethodINotificationHandler<TNotification>โHandlemethodIPipelineBehavior<TRequest, TResponse>โHandlemethod
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 |
1๏ธโฃ Manual Mode (Recommended for Production)
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
- Keep Handlers Focused: Each handler should do one thing well
- Use Pipelines for Cross-Cutting Concerns: Don't repeat logging/validation in every handler
- Prefer
Unitoverboolfor Commands: More explicit than returning success flags - Name Requests Clearly: Use suffixes like
Request,Command,Query, orNotification - Validate Early: Use validation pipelines to fail fast
- 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
๐ก Tip: Combine with pvNugsLoggerNc9Abstractions for comprehensive logging in your handlers and pipelines!
| Product | Versions 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. |
-
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.
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.