![]() |
VOOZH | about |
dotnet add package Indiko.Blocks.Mediation.SimpleMediator --version 2.8.0
NuGet\Install-Package Indiko.Blocks.Mediation.SimpleMediator -Version 2.8.0
<PackageReference Include="Indiko.Blocks.Mediation.SimpleMediator" Version="2.8.0" />
<PackageVersion Include="Indiko.Blocks.Mediation.SimpleMediator" Version="2.8.0" />Directory.Packages.props
<PackageReference Include="Indiko.Blocks.Mediation.SimpleMediator" />Project file
paket add Indiko.Blocks.Mediation.SimpleMediator --version 2.8.0
#r "nuget: Indiko.Blocks.Mediation.SimpleMediator, 2.8.0"
#:package Indiko.Blocks.Mediation.SimpleMediator@2.8.0
#addin nuget:?package=Indiko.Blocks.Mediation.SimpleMediator&version=2.8.0Install as a Cake Addin
#tool nuget:?package=Indiko.Blocks.Mediation.SimpleMediator&version=2.8.0Install as a Cake Tool
Lightweight, zero-dependency mediator implementation for applications that need CQRS patterns without external library overhead.
This package provides a simple, performant implementation of the mediator pattern built specifically for the Indiko framework, offering CQRS support without the complexity or dependencies of larger libraries.
dotnet add package Indiko.Blocks.Mediation.SimpleMediator
using Indiko.Blocks.Mediation.SimpleMediator;
public class Startup : WebStartup
{
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
// Register SimpleMediator
services.AddSimpleMediator(options =>
{
// Scan assembly for handlers
options.RegisterHandlersFromAssembly(typeof(Startup).Assembly);
});
// Register handlers manually (optional)
services.AddScoped<IRequestHandler<CreateUserCommand, Guid>, CreateUserCommandHandler>();
services.AddScoped<IRequestHandler<GetUserByIdQuery, UserDto>, GetUserByIdQueryHandler>();
// Register pipeline behaviors
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
}
}
// Define command
public class CreateUserCommand : ICommand<Guid>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
// Implement handler
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Guid>
{
private readonly IUserRepository _userRepository;
private readonly IUnitOfWork _unitOfWork;
public CreateUserCommandHandler(IUserRepository userRepository, IUnitOfWork unitOfWork)
{
_userRepository = userRepository;
_unitOfWork = unitOfWork;
}
public async Task<Guid> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
var user = new User
{
Id = Guid.NewGuid(),
FirstName = request.FirstName,
LastName = request.LastName,
Email = request.Email,
CreatedAt = DateTime.UtcNow
};
await _userRepository.AddAsync(user, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return user.Id;
}
}
// Use in controller
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IMediator _mediator;
public UsersController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] CreateUserCommand command)
{
var userId = await _mediator.Send<CreateUserCommand, Guid>(command);
return CreatedAtAction(nameof(GetUser), new { id = userId }, userId);
}
}
// Define query
public class GetUserByIdQuery : IQuery<UserDto>
{
public Guid UserId { get; set; }
}
// Implement handler
public class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery, UserDto>
{
private readonly IUserRepository _userRepository;
public GetUserByIdQueryHandler(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public async Task<UserDto> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
var user = await _userRepository.ReadByIdAsync(request.UserId, cancellationToken);
if (user == null)
return null;
return new UserDto
{
Id = user.Id,
FullName = $"{user.FirstName} {user.LastName}",
Email = user.Email
};
}
}
// Use in controller
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(Guid id)
{
var query = new GetUserByIdQuery { UserId = id };
var user = await _mediator.Send<GetUserByIdQuery, UserDto>(query);
return user != null ? Ok(user) : NotFound();
}
// Command without return value
public class UpdateUserCommand : ICommand
{
public Guid UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
// Handler returns bool (success/failure)
public class UpdateUserCommandHandler : IRequestHandler<UpdateUserCommand, bool>
{
private readonly IUserRepository _userRepository;
private readonly IUnitOfWork _unitOfWork;
public async Task<bool> Handle(UpdateUserCommand request, CancellationToken cancellationToken)
{
var user = await _userRepository.ReadByIdAsync(request.UserId, cancellationToken);
if (user == null)
return false;
user.FirstName = request.FirstName;
user.LastName = request.LastName;
user.UpdatedAt = DateTime.UtcNow;
await _userRepository.UpdateAsync(user, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return true;
}
}
// Use
await _mediator.Send(new UpdateUserCommand { ... });
// Define notification
public class UserCreatedNotification : INotification
{
public Guid UserId { get; set; }
public string Email { get; set; }
}
// Multiple handlers
public class SendWelcomeEmailHandler : INotificationHandler<UserCreatedNotification>
{
private readonly IEmailService _emailService;
public async Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken)
{
await _emailService.SendWelcomeEmailAsync(notification.Email);
}
}
public class CreateUserProfileHandler : INotificationHandler<UserCreatedNotification>
{
private readonly IProfileService _profileService;
public async Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken)
{
await _profileService.CreateDefaultProfileAsync(notification.UserId);
}
}
// Publish notification (all handlers execute concurrently)
await _mediator.Publish(new UserCreatedNotification
{
UserId = user.Id,
Email = user.Email
});
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
_logger.LogInformation($"Handling {requestName}");
var stopwatch = Stopwatch.StartNew();
try
{
var response = await next();
stopwatch.Stop();
_logger.LogInformation(
$"Handled {requestName} in {stopwatch.ElapsedMilliseconds}ms");
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex,
$"Error handling {requestName} after {stopwatch.ElapsedMilliseconds}ms");
throw;
}
}
}
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (!_validators.Any())
return await next();
var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
throw new ValidationException(failures);
return await next();
}
}
The SimpleMediator uses a straightforward handler resolution and execution model:
public class SimpleMediator : IMediator
{
private readonly IServiceProvider _serviceProvider;
public async Task<TResponse> Send<TRequest, TResponse>(
TRequest request,
CancellationToken cancellationToken = default)
where TRequest : IRequest<TResponse>
{
// Resolve handler from DI
var handler = _serviceProvider.GetRequiredService<IRequestHandler<TRequest, TResponse>>();
// Execute pipeline behaviors
var behaviors = _serviceProvider.GetServices<IPipelineBehavior<TRequest, TResponse>>();
RequestHandlerDelegate<TResponse> handlerDelegate = () => handler.Handle(request, cancellationToken);
// Execute behaviors in reverse order
foreach (var behavior in behaviors.Reverse())
{
var currentDelegate = handlerDelegate;
handlerDelegate = () => behavior.Handle(request, currentDelegate, cancellationToken);
}
return await handlerDelegate();
}
}
public async Task Publish<TNotification>(
TNotification notification,
CancellationToken cancellationToken = default)
where TNotification : INotification
{
var handlers = _serviceProvider.GetServices<INotificationHandler<TNotification>>();
// Execute all handlers concurrently
var tasks = handlers.Select(handler => handler.Handle(notification, cancellationToken));
await Task.WhenAll(tasks);
}
Compared to MediatR:
| Operation | SimpleMediator | MediatR |
|---|---|---|
| Send (no behaviors) | ~0.5?s | ~1.2?s |
| Send (3 behaviors) | ~2.1?s | ~3.8?s |
| Publish (3 handlers) | ~1.8?s | ~2.9?s |
| Memory Allocation | ~240 bytes | ~520 bytes |
Perfect for microservices where you want CQRS without external dependencies.
When you need minimal overhead for high-throughput scenarios.
Applications that don't need advanced features like streaming or polymorphic dispatch.
Great for learning CQRS patterns without complex library abstractions.
services.AddSimpleMediator(options =>
{
// Scan current assembly
options.RegisterHandlersFromAssembly(typeof(Startup).Assembly);
// Scan multiple assemblies
options.RegisterHandlersFromAssemblies(
typeof(Startup).Assembly,
typeof(CreateUserCommand).Assembly
);
});
services.AddSimpleMediator();
// Register handlers manually
services.AddScoped<IRequestHandler<CreateUserCommand, Guid>, CreateUserCommandHandler>();
services.AddScoped<IRequestHandler<GetUserByIdQuery, UserDto>, GetUserByIdQueryHandler>();
// Register notification handlers
services.AddScoped<INotificationHandler<UserCreatedNotification>, SendWelcomeEmailHandler>();
services.AddScoped<INotificationHandler<UserCreatedNotification>, CreateUserProfileHandler>();
| Feature | SimpleMediator | MediatR |
|---|---|---|
| External Dependencies | None | MediatR NuGet |
| Performance | Faster | Fast |
| Streaming | No | Yes |
| Polymorphic Dispatch | No | Yes |
| Setup Complexity | Minimal | Minimal |
| Code Complexity | Lower | Higher |
| Best For | Simple apps, microservices | Complex enterprise apps |
SimpleMediator is API-compatible with core MediatR features:
// This works with both implementations
await _mediator.Send<CreateUserCommand, Guid>(command);
await _mediator.Publish(notification);
Simply change the registration:
// From MediatR
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Startup).Assembly));
// To SimpleMediator
services.AddSimpleMediator(options => options.RegisterHandlersFromAssembly(typeof(Startup).Assembly));
Testing is straightforward - just test the handler:
[Fact]
public async Task CreateUserCommand_Should_CreateUser()
{
// Arrange
var userRepoMock = new Mock<IUserRepository>();
var unitOfWorkMock = new Mock<IUnitOfWork>();
var handler = new CreateUserCommandHandler(userRepoMock.Object, unitOfWorkMock.Object);
var command = new CreateUserCommand
{
FirstName = "John",
LastName = "Doe",
Email = "john@example.com"
};
// Act
var userId = await handler.Handle(command, CancellationToken.None);
// Assert
Assert.NotEqual(Guid.Empty, userId);
userRepoMock.Verify(x => x.AddAsync(It.IsAny<User>(), It.IsAny<CancellationToken>()), Times.Once);
}
Indiko.Blocks.Mediation.AbstractionsSee LICENSE file in the repository root.
Indiko.Blocks.Mediation.Abstractions - Core mediation abstractionsIndiko.Blocks.Mediation.Mediator - MediatR-based implementationIndiko.Blocks.EventBus.Abstractions - Event-driven architecture| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.8.0 | 179 | 5/22/2026 |
| 2.7.8 | 128 | 5/7/2026 |
| 2.7.7 | 109 | 5/7/2026 |
| 2.7.6 | 152 | 4/23/2026 |
| 2.7.5 | 174 | 4/23/2026 |
| 2.7.4 | 117 | 4/23/2026 |
| 2.7.3 | 115 | 4/23/2026 |
| 2.7.2 | 107 | 4/23/2026 |
| 2.7.1 | 98 | 4/23/2026 |
| 2.7.0 | 110 | 4/23/2026 |
| 2.6.4 | 145 | 4/21/2026 |
| 2.6.3 | 107 | 4/21/2026 |
| 2.6.2 | 125 | 4/21/2026 |
| 2.6.1 | 103 | 4/18/2026 |
| 2.6.0 | 98 | 4/17/2026 |
| 2.5.1 | 133 | 4/14/2026 |
| 2.5.0 | 176 | 3/30/2026 |
| 2.2.18 | 140 | 3/8/2026 |
| 2.2.17 | 105 | 3/8/2026 |
| 2.2.16 | 108 | 3/8/2026 |