Tiny.Dispatcher
1.0.5
See the version list below for details.
dotnet add package Tiny.Dispatcher --version 1.0.5
NuGet\Install-Package Tiny.Dispatcher -Version 1.0.5
<PackageReference Include="Tiny.Dispatcher" Version="1.0.5" />
<PackageVersion Include="Tiny.Dispatcher" Version="1.0.5" />
<PackageReference Include="Tiny.Dispatcher" />
paket add Tiny.Dispatcher --version 1.0.5
#r "nuget: Tiny.Dispatcher, 1.0.5"
#:package Tiny.Dispatcher@1.0.5
#addin nuget:?package=Tiny.Dispatcher&version=1.0.5
#tool nuget:?package=Tiny.Dispatcher&version=1.0.5
TinyDispatcher
A lightweight, high-performance dispatcher library for C# as a replacement for MediatR. Built for simplicity, minimal overhead, and easy integration with Microsoft.Extensions.DependencyInjection.
Features
- ๐ Lightweight: Minimal dependencies and overhead
- โก High Performance: Avoids heavy reflection and over-abstraction
- ๐ง Simple API: Easy to understand and use
- ๐ฆ DI Integration: Seamless integration with Microsoft.Extensions.DependencyInjection
- ๐งช Unit Test Friendly: Designed with testability in mind
- ๐ฏ CQRS Support: Built-in support for Command Query Responsibility Segregation
- ๐ก Event Handling: Support for domain events with multiple handlers
Installation
dotnet add package TinyDispatcher
Quick Start
1. Register TinyDispatcher in DI Container
using TinyDispatcher;
var services = new ServiceCollection();
services.AddTinyDispatcher(typeof(Program).Assembly);
var serviceProvider = services.BuildServiceProvider();
var dispatcher = serviceProvider.GetRequiredService<IDispatcher>();
2. Create Commands and Handlers
// Command
public class CreateInvoiceCommand : ICommand<CreateInvoiceResponse>
{
public string CustomerName { get; set; } = string.Empty;
public decimal Amount { get; set; }
}
// Response
public class CreateInvoiceResponse
{
public int InvoiceId { get; set; }
public string Status { get; set; } = string.Empty;
}
// Handler
public class CreateInvoiceCommandHandler : ICommandHandler<CreateInvoiceCommand, CreateInvoiceResponse>
{
public Task<CreateInvoiceResponse> HandleAsync(CreateInvoiceCommand command, CancellationToken cancellationToken = default)
{
// Your business logic here
var response = new CreateInvoiceResponse
{
InvoiceId = new Random().Next(1000, 9999),
Status = "Created"
};
return Task.FromResult(response);
}
}
3. Execute Commands
var command = new CreateInvoiceCommand
{
CustomerName = "John Doe",
Amount = 100.50m
};
var result = await dispatcher.SendAsync(command);
Console.WriteLine($"Invoice {result.InvoiceId} created with status: {result.Status}");
Core Components
Component | Purpose |
---|---|
IDispatcher |
Entry point to send commands, queries, and publish events |
ICommand<TResponse> |
Marker interface for commands |
ICommandHandler<TCommand, TResponse> |
Interface for command handlers |
IQuery<TResponse> |
Marker interface for queries |
IQueryHandler<TQuery, TResponse> |
Interface for query handlers |
IDomainEvent |
Marker interface for domain events |
IDomainEventHandler<TEvent> |
Interface for domain event handlers |
Usage Examples
Commands
Commands are used for operations that modify state and have exactly one handler.
// Send a command
var result = await dispatcher.SendAsync(new CreateInvoiceCommand
{
CustomerName = "Jane Smith",
Amount = 250.00m
});
Queries
Queries are used for read operations and have exactly one handler.
// Query definition
public class GetInvoiceQuery : IQuery<GetInvoiceResponse>
{
public int InvoiceId { get; set; }
}
// Query handler
public class GetInvoiceQueryHandler : IQueryHandler<GetInvoiceQuery, GetInvoiceResponse>
{
public Task<GetInvoiceResponse> HandleAsync(GetInvoiceQuery query, CancellationToken cancellationToken = default)
{
// Your data retrieval logic here
var response = new GetInvoiceResponse
{
InvoiceId = query.InvoiceId,
CustomerName = "John Doe",
Amount = 100.50m,
Status = "Active"
};
return Task.FromResult(response);
}
}
// Execute query
var invoice = await dispatcher.QueryAsync(new GetInvoiceQuery { InvoiceId = 123 });
Domain Events
Domain events can have zero or more handlers and are processed in parallel.
// Event definition
public class InvoiceCreatedEvent : IDomainEvent
{
public int InvoiceId { get; set; }
public string CustomerName { get; set; } = string.Empty;
public decimal Amount { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
// Multiple event handlers
public class EmailNotificationHandler : IDomainEventHandler<InvoiceCreatedEvent>
{
public Task HandleAsync(InvoiceCreatedEvent domainEvent, CancellationToken cancellationToken = default)
{
// Send email notification
Console.WriteLine($"Sending email notification for invoice {domainEvent.InvoiceId}");
return Task.CompletedTask;
}
}
public class AuditLogHandler : IDomainEventHandler<InvoiceCreatedEvent>
{
public Task HandleAsync(InvoiceCreatedEvent domainEvent, CancellationToken cancellationToken = default)
{
// Log audit entry
Console.WriteLine($"Logging audit entry for invoice {domainEvent.InvoiceId}");
return Task.CompletedTask;
}
}
// Publish event
await dispatcher.PublishAsync(new InvoiceCreatedEvent
{
InvoiceId = 123,
CustomerName = "John Doe",
Amount = 100.50m
});
Registration Options
Auto-registration by Assembly
services.AddTinyDispatcher(typeof(Program).Assembly);
Auto-registration by Multiple Assemblies
services.AddTinyDispatcher(
typeof(Program).Assembly,
typeof(SomeOtherClass).Assembly
);
Auto-registration by Marker Types
services.AddTinyDispatcher(
typeof(CreateInvoiceCommandHandler),
typeof(GetInvoiceQueryHandler)
);
Behavior
Commands
- Single Handler: Commands must have exactly one handler
- Synchronous Processing: Commands are processed synchronously
- Exception on Missing Handler: Throws
InvalidOperationException
if no handler is found
Queries
- Single Handler: Queries must have exactly one handler
- Synchronous Processing: Queries are processed synchronously
- Exception on Missing Handler: Throws
InvalidOperationException
if no handler is found
Domain Events
- Multiple Handlers: Events can have zero or more handlers
- Parallel Processing: All handlers are executed in parallel using
Task.WhenAll
- No Return Values: Event handlers don't return responses
- No Exception on Missing Handlers: Events without handlers are silently ignored
Error Handling
try
{
var result = await dispatcher.SendAsync(command);
}
catch (InvalidOperationException ex)
{
// Handler not found
Console.WriteLine($"No handler found: {ex.Message}");
}
catch (ArgumentNullException ex)
{
// Null command/query/event
Console.WriteLine($"Null input: {ex.Message}");
}
Testing
TinyDispatcher is designed to be unit test friendly:
[Test]
public async Task Should_Handle_Command_Successfully()
{
// Arrange
var services = new ServiceCollection();
services.AddTinyDispatcher(typeof(CreateInvoiceCommandHandler).Assembly);
var serviceProvider = services.BuildServiceProvider();
var dispatcher = serviceProvider.GetRequiredService<IDispatcher>();
var command = new CreateInvoiceCommand
{
CustomerName = "Test Customer",
Amount = 123.45m
};
// Act
var result = await dispatcher.SendAsync(command);
// Assert
Assert.NotNull(result);
Assert.Equal("Created", result.Status);
Assert.True(result.InvoiceId > 0);
}
Performance Considerations
- Minimal Reflection: Uses reflection only during handler resolution, not during execution
- Service Lifetime: Handlers are registered as
Scoped
by default - Parallel Event Processing: Domain events are processed in parallel for better performance
- No Boxing: Generic constraints prevent boxing/unboxing overhead
Requirements
- .NET 8.0 or later
- Microsoft.Extensions.DependencyInjection.Abstractions 8.0.0+
- Microsoft.Extensions.DependencyInjection 8.0.0+
License
MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Changelog
Version 1.0.0
- Initial release
- Support for Commands, Queries, and Domain Events
- Auto-registration via DI extensions
- .NET 8 support
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. 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. |
-
net8.0
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.