SliceDispatch 1.2.0
dotnet add package SliceDispatch --version 1.2.0
NuGet\Install-Package SliceDispatch -Version 1.2.0
<PackageReference Include="SliceDispatch" Version="1.2.0" />
<PackageVersion Include="SliceDispatch" Version="1.2.0" />
<PackageReference Include="SliceDispatch" />
paket add SliceDispatch --version 1.2.0
#r "nuget: SliceDispatch, 1.2.0"
#:package SliceDispatch@1.2.0
#addin nuget:?package=SliceDispatch&version=1.2.0
#tool nuget:?package=SliceDispatch&version=1.2.0
SliceDispatch
╔═══════════════════════════════════════════════════════════════════════════╗
║ ║
║ ███████╗██╗ ██╗ ██████╗███████╗ ║
║ ██╔════╝██║ ██║██╔════╝██╔════╝ ║
║ ███████╗██║ ██║██║ █████╗ ║
║ ╚════██║██║ ██║██║ ██╔══╝ ║
║ ███████║███████╗██║╚██████╗███████╗ ║
║ ╚══════╝╚══════╝╚═╝ ╚═════╝╚══════╝ ║
║ ║
║ ██████╗ ██╗███████╗██████╗ █████╗ ████████╗ ██████╗██╗ ██╗ ║
║ ██╔══██╗██║██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██║ ██║ ║
║ ██║ ██║██║███████╗██████╔╝███████║ ██║ ██║ ███████║ ║
║ ██║ ██║██║╚════██║██╔═══╝ ██╔══██║ ██║ ██║ ██╔══██║ ║
║ ██████╔╝██║███████║██║ ██║ ██║ ██║ ╚██████╗██║ ██║ ║
║ ╚═════╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ║
║ ║
║ ▓▓▓▓▓ REQUEST DISPATCHING SYSTEM ONLINE ▓▓▓▓▓ ║
║ ║
║ >> Clean Architecture • CQRS • Vertical Slice ║
║ >> Developer: Adam Watson ║
║ >> STATUS: [■■■■■■■■■■] 100% OPERATIONAL ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════╝
Lightweight request dispatching for CQRS and Vertical Slice Architecture.
SliceDispatch is a lightweight .NET library for request/handler orchestration inspired by mediator-style patterns. It is designed for developers who want clean request handling without unnecessary complexity.
Features
- Request / response dispatching
- Void request dispatching
- Notification publishing (publish/subscribe with multiple handlers)
- Pipeline behaviors for requests and notifications
- Automatic handler and closed-behavior registration via assembly scanning
- Open-generic behavior registration for cross-cutting concerns
- Reflection-cached dispatch via compiled delegates (warm path has zero reflection overhead)
- Custom missing-handler exceptions
- Built for CQRS and Vertical Slice Architecture
Installation
dotnet add package SliceDispatch
Basic Example
using Microsoft.Extensions.DependencyInjection;
using SliceDispatch;
var services = new ServiceCollection();
services.AddSliceDispatch(typeof(PingRequestHandler).Assembly);
var provider = services.BuildServiceProvider();
var sender = provider.GetRequiredService<ISender>();
var result = await sender.Send(new PingRequest());
Console.WriteLine(result); // Pong
public sealed class PingRequest : IRequest<string>
{
}
public sealed class PingRequestHandler : IRequestHandler<PingRequest, string>
{
public Task<string> Handle(
PingRequest request,
CancellationToken cancellationToken)
{
return Task.FromResult("Pong");
}
}
Notifications (Publish/Subscribe)
Notifications enable the publish/subscribe pattern where a single event can trigger multiple independent handlers.
Basic Notification Example
using Microsoft.Extensions.DependencyInjection;
using SliceDispatch;
var services = new ServiceCollection();
services.AddSliceDispatch(typeof(OrderPlacedNotification).Assembly);
var provider = services.BuildServiceProvider();
var sender = provider.GetRequiredService<ISender>();
// Publish notification - all handlers execute
await sender.Publish(new OrderPlacedNotification
{
OrderId = "12345",
Amount = 99.99m
});
// Define notification
public sealed class OrderPlacedNotification : INotification
{
public string OrderId { get; init; } = string.Empty;
public decimal Amount { get; init; }
}
// Multiple handlers for the same notification
public sealed class SendEmailHandler : INotificationHandler<OrderPlacedNotification>
{
public async Task Handle(OrderPlacedNotification notification, CancellationToken cancellationToken)
{
// Send confirmation email
Console.WriteLine($"Email sent for order {notification.OrderId}");
await Task.CompletedTask;
}
}
public sealed class UpdateInventoryHandler : INotificationHandler<OrderPlacedNotification>
{
public async Task Handle(OrderPlacedNotification notification, CancellationToken cancellationToken)
{
// Update inventory
Console.WriteLine($"Inventory updated for order {notification.OrderId}");
await Task.CompletedTask;
}
}
public sealed class LogOrderHandler : INotificationHandler<OrderPlacedNotification>
{
public async Task Handle(OrderPlacedNotification notification, CancellationToken cancellationToken)
{
// Log order details
Console.WriteLine($"Order logged: {notification.OrderId} - ${notification.Amount}");
await Task.CompletedTask;
}
}
Key Points:
- Handlers execute sequentially in registration order
- If no handlers are registered,
Publish()returns silently (no exception) - All handlers receive the same notification instance
- Pipeline behaviors can wrap each handler individually
Pipeline Behaviors
Pipeline behaviors wrap handler execution and are ideal for cross-cutting concerns like logging, validation, and timing.
Open-generic behavior (applies to all requests of matching shape)
Register manually to keep ordering explicit:
// Register in DI — order of registration = outer-to-inner execution order
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
public sealed class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
Console.WriteLine($"Handling {typeof(TRequest).Name}");
var response = await next();
Console.WriteLine($"Handled {typeof(TRequest).Name}");
return response;
}
}
Closed behavior (applies to one specific request type)
Closed behaviors are discovered automatically by AddSliceDispatch assembly scanning — no manual registration needed:
// Automatically registered when the assembly is scanned
public sealed class AuditBehavior : IPipelineBehavior<PlaceOrderRequest, OrderResult>
{
public async Task<OrderResult> Handle(
PlaceOrderRequest request,
RequestHandlerDelegate<OrderResult> next,
CancellationToken cancellationToken)
{
var result = await next();
// audit logic here
return result;
}
}
Void request behaviors
Both open-generic and closed behaviors are supported for void requests (IRequest) using IPipelineBehavior<TRequest>:
public sealed class LoggingBehavior<TRequest> : IPipelineBehavior<TRequest>
where TRequest : IRequest
{
public async Task Handle(
TRequest request,
RequestHandlerDelegate next,
CancellationToken cancellationToken)
{
Console.WriteLine($"Handling {typeof(TRequest).Name}");
await next();
Console.WriteLine($"Handled {typeof(TRequest).Name}");
}
}
Notification Pipeline Behaviors
Notifications support pipeline behaviors using INotificationPipelineBehavior<TNotification>. Each handler is wrapped individually by the pipeline.
// Open-generic notification behavior (applies to all notifications)
public sealed class LoggingBehavior<TNotification> : INotificationPipelineBehavior<TNotification>
where TNotification : INotification
{
public async Task Handle(
TNotification notification,
RequestHandlerDelegate next,
CancellationToken cancellationToken)
{
Console.WriteLine($"Handling notification {typeof(TNotification).Name}");
await next();
Console.WriteLine($"Completed notification {typeof(TNotification).Name}");
}
}
// Register manually
builder.Services.AddTransient(typeof(INotificationPipelineBehavior<>), typeof(LoggingBehavior<>));
Closed notification behaviors are automatically discovered via assembly scanning:
// This will be automatically registered
public sealed class OrderAuditBehavior : INotificationPipelineBehavior<OrderPlacedNotification>
{
public async Task Handle(
OrderPlacedNotification notification,
RequestHandlerDelegate next,
CancellationToken cancellationToken)
{
// Audit before handlers
await next(); // Each handler wrapped individually
// Audit after handlers
}
}
ASP.NET Core Example
var builder = WebApplication.CreateBuilder(args);
// Scans assembly for handlers and closed pipeline behaviors
builder.Services.AddSliceDispatch(typeof(PingRequestHandler).Assembly);
// Register open-generic behaviors manually to control ordering
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
var app = builder.Build();
app.MapGet("/ping", async (ISender sender) =>
{
return await sender.Send(new PingRequest());
});
app.Run();
Migrating from MediatR
SliceDispatch uses the same core interfaces as MediatR (IRequest, IRequestHandler, IPipelineBehavior), making migration straightforward.
Step 1: Install SliceDispatch
dotnet add package SliceDispatch
Step 2: Update Service Registration
Before (MediatR):
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
cfg.AddOpenBehavior(typeof(ValidationBehavior<,>));
});
After (SliceDispatch):
services.AddSliceDispatch(typeof(Program).Assembly);
// Register open-generic behaviors separately for explicit ordering
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
Step 3: Replace IMediator with ISender
Before:
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
public ProductsController(IMediator mediator)
{
_mediator = mediator;
}
public async Task<IActionResult> GetProduct(Guid id)
{
var result = await _mediator.Send(new GetProductQuery { Id = id });
return Ok(result);
}
public async Task<IActionResult> CreateProduct(CreateProductCommand command)
{
await _mediator.Publish(new ProductCreatedNotification { ProductId = command.Id });
return Ok();
}
}
After:
public class ProductsController : ControllerBase
{
private readonly ISender _sender;
public ProductsController(ISender sender)
{
_sender = sender;
}
public async Task<IActionResult> GetProduct(Guid id)
{
var result = await _sender.Send(new GetProductQuery { Id = id });
return Ok(result);
}
public async Task<IActionResult> CreateProduct(CreateProductCommand command)
{
await _sender.Publish(new ProductCreatedNotification { ProductId = command.Id });
return Ok();
}
}
Step 4: Update Using Statements
Replace using MediatR; with using SliceDispatch; in your files.
What Stays the Same
✅ Request and handler interfaces (IRequest<T>, IRequestHandler<TRequest, TResponse>)
✅ Notification and handler interfaces (INotification, INotificationHandler<TNotification>)
✅ Pipeline behavior interface (IPipelineBehavior<TRequest, TResponse>)
✅ Handler implementations (no code changes needed)
✅ FluentValidation integration works identically
✅ All existing business logic
What's Different
🔄 Notification pipeline behaviors use INotificationPipelineBehavior<TNotification> instead of IPipelineBehavior<TNotification>
🔄 No IPublisher interface - use ISender.Publish() instead of IMediator.Publish()
🔄 Notification handlers execute sequentially (no parallel strategy option)
Migration Checklist
- Install SliceDispatch package
- Replace
AddMediatR()withAddSliceDispatch() - Move open-generic behaviors to separate
AddTransient()registrations - Replace
IMediatorwithISenderin constructors - Replace
IPublisherwithISender(if used separately) - Update notification pipeline behaviors to use
INotificationPipelineBehavior<TNotification> - Update
using MediatR;tousing SliceDispatch; - Remove MediatR package (optional)
- Test your pipeline behaviors
License
MIT
| 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 is compatible. 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. |
-
net10.0
-
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.