Tiny.Dispatcher 1.0.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package Tiny.Dispatcher --version 1.0.3
                    
NuGet\Install-Package Tiny.Dispatcher -Version 1.0.3
                    
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="Tiny.Dispatcher" Version="1.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Tiny.Dispatcher" Version="1.0.3" />
                    
Directory.Packages.props
<PackageReference Include="Tiny.Dispatcher" />
                    
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 Tiny.Dispatcher --version 1.0.3
                    
#r "nuget: Tiny.Dispatcher, 1.0.3"
                    
#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 Tiny.Dispatcher@1.0.3
                    
#: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=Tiny.Dispatcher&version=1.0.3
                    
Install as a Cake Addin
#tool nuget:?package=Tiny.Dispatcher&version=1.0.3
                    
Install as a Cake Tool

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.10 64 7/27/2025
1.0.9 209 7/26/2025
1.0.8 213 7/26/2025
1.0.7 219 7/26/2025
1.0.6 220 7/26/2025
1.0.5 219 7/26/2025
1.0.4 219 7/26/2025
1.0.3 218 7/26/2025
1.0.2 216 7/26/2025
1.0.1 219 7/26/2025
1.0.0 227 7/26/2025