CoreMesh.Dispatching
1.1.1
See the version list below for details.
dotnet add package CoreMesh.Dispatching --version 1.1.1
NuGet\Install-Package CoreMesh.Dispatching -Version 1.1.1
<PackageReference Include="CoreMesh.Dispatching" Version="1.1.1" />
<PackageVersion Include="CoreMesh.Dispatching" Version="1.1.1" />
<PackageReference Include="CoreMesh.Dispatching" />
paket add CoreMesh.Dispatching --version 1.1.1
#r "nuget: CoreMesh.Dispatching, 1.1.1"
#:package CoreMesh.Dispatching@1.1.1
#addin nuget:?package=CoreMesh.Dispatching&version=1.1.1
#tool nuget:?package=CoreMesh.Dispatching&version=1.1.1
English | 繁體中文
CoreMesh.Dispatching
CoreMesh.Dispatching is a lightweight dispatching module for CoreMesh that provides:
Sendfor request/responseSendfor commands without a response payloadPublishfor notifications/events
Design goals:
- Simple
- Readable
- Easy to learn
- Low runtime overhead (lazy cache)
Current Features
Dispatcheruses wrapper + cache (lazy loading)- Notifications are executed sequentially by default (safety-first)
- Configurable notification publisher strategy (sequential / parallel)
- Supports
Microsoft.Extensions.DependencyInjectionregistration and assembly scanning - No pipeline (intentionally excluded)
Core Interfaces
IRequest<TResponse>: request with responseIRequest: command without response payloadIRequestHandler<TRequest, TResponse>IRequestHandler<TRequest>INotification: event/notificationINotificationHandler<TNotification>ISender: request sending entry pointIPublisher: notification publishing entry pointIDispatcher: combinesISenderandIPublisher
Quick Start
1. Define a request/response and handler
using CoreMesh.Dispatching;
public sealed record SampleQuery(string Foo, string Bar) : IRequest<SampleResponse>;
public sealed record SampleResponse(string Foo, string Bar);
public sealed class SampleHandler : IRequestHandler<SampleQuery, SampleResponse>
{
public Task<SampleResponse> Handle(SampleQuery request, CancellationToken cancellationToken = default)
=> Task.FromResult(new SampleResponse(request.Foo, request.Bar));
}
2. Register Dispatcher and handlers
using CoreMesh.Dispatching.Extensions;
builder.Services.AddDispatching(typeof(SampleHandler).Assembly);
Or register manually:
using CoreMesh.Dispatching.Notification;
using CoreMesh.Dispatching.Notification.Publisher;
builder.Services.AddSingleton<INotificationPublisher, ForeachAwaitPublisher>();
builder.Services.AddScoped<IDispatcher, Dispatcher>();
builder.Services.AddScoped<IRequestHandler<SampleQuery, SampleResponse>, SampleHandler>();
3. Call Send
app.MapGet("/sample", async (IDispatcher dispatcher, CancellationToken ct) =>
{
var result = await dispatcher.Send(new SampleQuery("Foo", "Bar"), ct);
return Results.Ok(result);
});
Notification Example (Publish)
using CoreMesh.Dispatching;
public sealed record UserCreated(int UserId, string Email) : INotification;
public sealed class AuditLogOnUserCreatedHandler : INotificationHandler<UserCreated>
{
public Task Handle(UserCreated notification, CancellationToken cancellationToken = default)
{
Console.WriteLine($"[Audit] User created: {notification.UserId}");
return Task.CompletedTask;
}
}
public sealed class WelcomeEmailOnUserCreatedHandler : INotificationHandler<UserCreated>
{
public Task Handle(UserCreated notification, CancellationToken cancellationToken = default)
{
Console.WriteLine($"[Mail] Send welcome email to {notification.Email}");
return Task.CompletedTask;
}
}
Registration:
builder.Services.AddScoped<INotificationHandler<UserCreated>, AuditLogOnUserCreatedHandler>();
builder.Services.AddScoped<INotificationHandler<UserCreated>, WelcomeEmailOnUserCreatedHandler>();
Invocation:
await dispatcher.Publish(new UserCreated(123, "demo@coremesh.dev"), ct);
Behavior Notes
Send
Send(IRequest<TResponse>): requires exactly one matching handlerSend(IRequest): requires exactly one matching handler- If no handler is registered, DI resolution throws (
InvalidOperationException)
Publish
Publish(INotification)dispatches to all matchingINotificationHandler<T>- Execution is sequential by default (in registration order)
- This is safer when handlers share scoped dependencies
- Can be configured to run in parallel for better throughput:
builder.Services.AddDispatching(
options => options.UseParallelPublisher(),
typeof(Program).Assembly);
Use Cases (When This Pattern Fits)
CoreMesh.Dispatching follows an application-layer request dispatching (Mediator-like) pattern. It is suitable for:
1. Application orchestration in Web API / Minimal API
When endpoints should stay thin and delegate use-case execution to handlers instead of calling services/repositories directly.
Good for:
- Queries
- Commands
- Application workflow orchestration
Benefits:
- Thinner endpoints
- Use-case logic is centralized in handlers
- Easier testing
2. Splitting large application services (avoiding a God Service)
Replace a large ApplicationService with many focused request handlers, each handling one use case.
Good for:
- Growing codebases
- Team collaboration (ownership by feature/handler)
3. Notification-driven follow-up actions
After a primary workflow completes, use Publish to trigger multiple follow-up side effects.
Examples:
- After user creation: audit log, welcome email, metrics
- After payment: reporting update, external notification, outbox write
4. A lightweight unified entry point without a full framework
When you do not want a full CQRS/DDD framework yet, but still want a small, predictable dispatching abstraction.
What This Module Handles
CoreMesh.Dispatching is responsible for request/notification dispatching and invocation, not business logic itself.
Included Responsibilities
Request -> Handlerdispatch (Send)Notification -> Handlersdispatch (Publish)- Handler resolution from DI
- Handler discovery and registration (assembly scanning)
- Runtime wrapper cache (lazy cache)
- Wrapper factory cache for first-time type wrapping
Intentionally Not Included (Current Version)
- Validation pipeline
- Logging pipeline
- Retry / Circuit breaker
- Transaction / Unit of Work
- Authorization
- ASP.NET Core endpoint abstraction
- Outbox / message broker delivery
These are cross-cutting concerns or infrastructure integrations and are better handled in outer modules (endpoint layer, middleware, decorators, background jobs).
Design Trade-offs
Why no Pipeline?
This version intentionally excludes pipeline support to keep:
- Lower latency
- Fewer allocations
- A simpler execution path
Cross-cutting concerns such as validation, logging, or proxy behavior can be handled outside the dispatcher (for example: endpoint layer, middleware, decorators).
Notes
AddDispatching()requires at least one assembly parameter- Prefer explicit assembly scanning (for example
typeof(SomeHandler).Assembly) - A request type should correspond to a single response type (
IRequest<TResponse>)
Performance
Benchmarks comparing CoreMesh.Dispatching with MediatR 12.x (last free MIT version):
| Method | Mean | Allocated |
|---|---|---|
| Baseline (Direct Handler) | 10.97 ns | 104 B |
| CoreMesh Send | 27.55 ns | 104 B |
| MediatR Send | 50.20 ns | 232 B |
| CoreMesh Publish (1) | 59.29 ns | 232 B |
| MediatR Publish (1) | 68.73 ns | 288 B |
| CoreMesh Publish (5) | 143.98 ns | 712 B |
| MediatR Publish (5) | 166.18 ns | 896 B |
CoreMesh is ~45% faster on Send and ~15-20% faster on Publish, with ~20-55% less memory allocation.
Future Directions
AddDispatchingFromAssemblyContaining<T>()- More DI registration options (lifetime / filters)
- Source generator support to further reduce runtime type-resolution overhead
| 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
- CoreMesh.Dispatching.Abstractions (>= 1.1.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 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.