SvelteMediator 1.0.3
dotnet add package SvelteMediator --version 1.0.3
NuGet\Install-Package SvelteMediator -Version 1.0.3
<PackageReference Include="SvelteMediator" Version="1.0.3" />
<PackageVersion Include="SvelteMediator" Version="1.0.3" />
<PackageReference Include="SvelteMediator" />
paket add SvelteMediator --version 1.0.3
#r "nuget: SvelteMediator, 1.0.3"
#:package SvelteMediator@1.0.3
#addin nuget:?package=SvelteMediator&version=1.0.3
#tool nuget:?package=SvelteMediator&version=1.0.3
SvelteMediator
A lightweight, free, and open-source implementation of the .NET Mediator pattern, serving as an alternative to the commercial MediatR library. Built on .NET 10 with Microsoft.Extensions.DependencyInjection.
Features
- Request / Response — CQRS-style command/query separation, supporting both void and typed responses
- Notification / Event — Publish notifications with multiple concurrent handlers
- Streaming —
IAsyncEnumerable-based streaming responses - Pipeline Behaviors — Middleware pipeline around handlers for cross-cutting concerns (logging, validation, caching, etc.)
- Custom Publish Strategies — Override
PublishCorefor parallel or custom notification dispatch - Auto Assembly Scanning — Automatically register handlers from assemblies
- Object Dispatch — Runtime-reflection
Send(object)/CreateStream(object)support
Quick Start
Installation
dotnet add package SvelteMediator
1. Define Requests and Handlers
// Request with response
public record UpdateUserCommand(string Id, string Name, string Email) : IRequest<string>;
public class UpdateUserCommandHandler : IRequestHandler<UpdateUserCommand, string>
{
public Task<string> Handle(UpdateUserCommand request, CancellationToken cancellationToken)
{
return Task.FromResult($"Updated user {request.Id}");
}
}
// Request with void response
public record DeleteUserCommand(string Id) : IRequest;
public class DeleteUserCommandHandler : IRequestHandler<DeleteUserCommand>
{
public Task Handle(DeleteUserCommand request, CancellationToken cancellationToken)
{
// Perform deletion...
return Task.CompletedTask;
}
}
2. Register Services
services.AddMediator(cfg =>
{
cfg.RegisterServicesFromAssemblyContaining<Program>();
});
3. Send Requests
public class UserController
{
private readonly IMediator _mediator;
public UserController(IMediator mediator)
{
_mediator = mediator;
}
public async Task<string> UpdateUser(string id, string name, string email)
{
// With response
return await _mediator.Send(new UpdateUserCommand(id, name, email));
}
public async Task DeleteUser(string id)
{
// Void response
await _mediator.Send(new DeleteUserCommand(id));
}
}
Notifications / Events
// Define a notification
public record UserUpdatedEvent(string UserId) : INotification;
// Handler 1 — send email
public class EmailNotificationHandler : INotificationHandler<UserUpdatedEvent>
{
public Task Handle(UserUpdatedEvent notification, CancellationToken cancellationToken)
{
Console.WriteLine($"Sending email to user {notification.UserId}");
return Task.CompletedTask;
}
}
// Handler 2 — log
public class LogNotificationHandler : INotificationHandler<UserUpdatedEvent>
{
public Task Handle(UserUpdatedEvent notification, CancellationToken cancellationToken)
{
Console.WriteLine($"Log: user {notification.UserId} updated");
return Task.CompletedTask;
}
}
// Publish notification
await mediator.Publish(new UserUpdatedEvent("42"));
// Output:
// Sending email to user 42
// Log: user 42 updated
Handlers execute sequentially by default. For parallel dispatch, subclass
Mediatorand overridePublishCore:public class ParallelPublishMediator : Mediator { public ParallelPublishMediator(IServiceProvider sp) : base(sp) { } protected override Task PublishCore( IEnumerable<NotificationHandlerExecutor> handlers, INotification notification, CancellationToken ct) { return Task.WhenAll( handlers.Select(h => h.HandlerCallback(notification, ct))); } }Register your custom Mediator:
cfg.MediatorImplementationType = typeof(ParallelPublishMediator);
Streaming
// Define a streaming request
public record GetUsersStream : IStreamRequest<string>;
public class GetUsersStreamHandler : IStreamRequestHandler<GetUsersStream, string>
{
public async IAsyncEnumerable<string> Handle(
GetUsersStream request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
yield return "Alice";
yield return "Bob";
yield return "Charlie";
}
}
// Usage
await foreach (var name in mediator.CreateStream(new GetUsersStream()))
{
Console.WriteLine(name);
}
Object Dispatch
Supports runtime reflection-based dispatch when the request type cannot be determined at compile time:
// Send via object (caches GetInterfaces() results to avoid repeated reflection)
object request = new UpdateUserCommand("42", "Alice", "alice@test.com");
string result = await mediator.Send(request);
// Also works for void requests and streaming requests
object voidRequest = new DeleteUserCommand("42");
await mediator.Send(voidRequest);
Object dispatch caches GetInterfaces() results to avoid repeated reflection overhead, achieving performance close to typed dispatch.
Pipeline Behaviors (Middleware)
Pipeline behaviors wrap handler execution, ideal for cross-cutting concerns:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
private readonly ILogger _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
_logger.LogInformation("Before handling {Request}", typeof(TRequest).Name);
var response = await next(cancellationToken);
_logger.LogInformation("After handling {Request}", typeof(TRequest).Name);
return response;
}
}
// Register (applies to all requests)
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
// Or register for specific request types only
cfg.AddBehavior(
typeof(IPipelineBehavior<UpdateUserCommand, string>),
typeof(MySpecificBehavior));
Behaviors form an onion model based on registration order: the first registered behavior runs outermost.
Streaming requests also support pipeline behaviors (IStreamPipelineBehavior):
public class StreamLoggingBehavior<TRequest, TResponse> : IStreamPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
public async IAsyncEnumerable<TResponse> Handle(
TRequest request,
StreamHandlerDelegate<TResponse> next,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
Console.WriteLine($"Stream start: {typeof(TRequest).Name}");
await foreach (var item in next().WithCancellation(cancellationToken))
{
yield return item;
}
Console.WriteLine($"Stream end: {typeof(TRequest).Name}");
}
}
Synchronous Notification Handler
For synchronous handler logic, extend NotificationHandler<T> to avoid explicitly returning Task:
public class MyHandler : NotificationHandler<UserUpdatedEvent>
{
protected override void Handle(UserUpdatedEvent notification)
{
Console.WriteLine($"Handled: {notification.UserId}");
}
}
Full Configuration Options
services.AddMediator(cfg =>
{
// Scan assemblies for handlers
cfg.RegisterServicesFromAssemblyContaining<Program>();
cfg.RegisterServicesFromAssembly(typeof(OtherHandler).Assembly);
// or cfg.RegisterServicesFromAssemblies(asm1, asm2);
// Register pipeline behaviors
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
cfg.AddOpenBehavior(typeof(ValidationBehavior<,>));
// Use a custom Mediator implementation
cfg.MediatorImplementationType = typeof(MyCustomMediator);
// Service lifetime (default: Transient)
cfg.Lifetime = ServiceLifetime.Scoped;
// Type filter (optional)
cfg.TypeEvaluator = t => !t.Name.Contains("Obsolete");
});
Project Structure
src/SvelteMediator/
├── IMediator.cs # Combines ISender + IPublisher
├── Mediator.cs # Core implementation
├── ISender.cs # Send / CreateStream
├── IPublisher.cs # Publish
├── IRequestHandler.cs # Handler contracts
├── INotification.cs / INotificationHandler.cs
├── IStreamRequest.cs / IStreamRequestHandler.cs
├── IPipelineBehavior.cs # Pipeline middleware
├── IStreamPipelineBehavior.cs
├── Unit.cs # Void return type substitute
├── MediatorServiceConfiguration.cs # Configuration
├── MediatorServiceCollectionExtensions.cs # DI extensions
├── ServiceRegistrar.cs # Assembly scanning & registration
└── Wrappers/ # Internal handler wrappers
├── RequestHandlerWrapper.cs
├── NotificationHandlerWrapper.cs
└── StreamRequestHandlerWrapper.cs
Design Highlights
- No source generators — Uses runtime reflection +
Activator.CreateInstancefor generic wrappers - Static handler wrapper caching — Created once per request type via
ConcurrentDictionary - Pipeline reversal — First registered behavior executes outermost
- Notification deduplication — Only the first instance of identical handler types is retained
- Unit type —
voidsubstitute for generic type positions, withUnit.ValueandUnit.Tasksingletons
Performance
All operations complete in sub-microsecond time, with 1–8 bytes allocated per operation — GC-friendly.
Full performance report: BENCHMARK-REPORT.md
| Typical Operation | Time | Throughput |
|---|---|---|
| Send<T> (1 behavior) | ~208 ns | ~4.8M ops/sec |
| Publish (2 handlers) | ~141 ns | ~7.1M ops/sec |
| CreateStream (10 items) | ~509 ns | ~1.9M ops/sec |
| Concurrent (4 tasks) | ~72 ns | ~14M ops/sec |
Tests
Integration tests live in tests/SvelteMediator.IntegrationTests/:
dotnet test tests/SvelteMediator.IntegrationTests/SvelteMediator.IntegrationTests.csproj
Covers Send, Publish, Stream, Pipeline Behavior, lifecycle management, custom publish strategies, and more — 64 test cases total.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.Logging (>= 10.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.