Version: 1.0.0
Target Framework: net10.0+
NuGet Package ID: Marai.Bridgetor
Introduction
Marai.Bridgetor is a lightweight in-process mediator for .NET applications following Clean Architecture and CQRS principles. It decouples application components by routing commands and queries to their respective handlers through a single dispatcher interface.
Major capabilities:
- Dispatch commands that produce a result (
ICommand<TResult>)
- Dispatch void commands that produce no result (
ICommand)
- Dispatch queries that return data (
IQuery<TResult>)
- Automatic handler discovery via assembly scanning
- Microsoft Dependency Injection integration
Table of Contents
Installation
| 1 |
dotnet add package Marai.Bridgetor |
| 1 |
Install-Package Marai.Bridgetor |
Getting Started
Register with Dependency Injection
Call AddMaraiBridgetor in your Program.cs, passing the assemblies that contain your handler implementations.
| 1
2
3 |
using Marai.Bridgetor.DependencyInjection;
builder.Services.AddMaraiBridgetor(typeof(Program).Assembly); |
You can pass multiple assemblies if handlers are spread across projects:
| 1
2
3 |
builder.Services.AddMaraiBridgetor(
typeof(Program).Assembly,
typeof(ApplicationAssemblyMarker).Assembly); |
AddMaraiBridgetor registers:
IDispatcher (scoped) — the single entry point for all requests
- All discovered
ICommandHandler<>, ICommandHandler<,>, and IQueryHandler<,> implementations (scoped)
Use without DI
| 1
2
3
4
5
6 |
var services = new ServiceCollection();
services.AddMaraiBridgetor(typeof(MyHandler).Assembly);
var provider = services.BuildServiceProvider();
var dispatcher = provider.GetRequiredService<IDispatcher>(); |
Commands
Command with Result
A command that produces a result implements ICommand<TResult>. Its handler implements ICommandHandler<TCommand, TResult>.
| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 |
// Define the command
public record CreateOrderCommand(string CustomerId, decimal Amount) : ICommand<Guid>;
// Define the handler
public class CreateOrderCommandHandler : ICommandHandler<CreateOrderCommand, Guid>
{
public async Task<Guid> Handle(CreateOrderCommand command, CancellationToken ct)
{
var orderId = Guid.NewGuid();
// … create order logic
return orderId;
}
}
// Dispatch
var orderId = await dispatcher.Send<Guid>(new CreateOrderCommand(“cust-1”, 99.99m)); |
Void Command
A command that produces no result implements ICommand. Its handler implements ICommandHandler<TCommand> and returns Task.
| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 |
// Define the command
public record SendEmailCommand(string To, string Subject) : ICommand;
// Define the handler
public class SendEmailCommandHandler : ICommandHandler<SendEmailCommand>
{
public async Task Handle(SendEmailCommand command, CancellationToken ct)
{
// … send email logic
}
}
// Dispatch
await dispatcher.Send(new SendEmailCommand(“user@example.com”, “Welcome”)); |
Queries
A query implements IQuery<TResult>. Its handler implements IQueryHandler<TQuery, TResult>.
| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 |
// Define the query
public record GetOrderByIdQuery(Guid OrderId) : IQuery<OrderDto>;
// Define the handler
public class GetOrderByIdQueryHandler : IQueryHandler<GetOrderByIdQuery, OrderDto>
{
public async Task<OrderDto> Handle(GetOrderByIdQuery query, CancellationToken ct)
{
// … fetch order logic
return new OrderDto(query.OrderId, “pending”);
}
}
// Dispatch
var order = await dispatcher.Send<OrderDto>(new GetOrderByIdQuery(orderId)); |
Dispatcher
IDispatcher is the single entry point for all request dispatching. Inject it into your controllers, services, or use-case classes.
| 1
2
3
4
5
6
7
8
9
10 |
public class OrdersController(IDispatcher dispatcher) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateOrderCommand command, CancellationToken ct)
{
var orderId = await dispatcher.Send<Guid>(command, ct);
return Ok(orderId);
}
} |
Method Signatures
| 1
2
3
4
5
6 |
public interface IDispatcher
{
Task<TResult> Send<TResult>(ICommand<TResult> command, CancellationToken ct = default);
Task Send(ICommand command, CancellationToken ct = default);
Task<TResult> Send<TResult>(IQuery<TResult> query, CancellationToken ct = default);
} |
Behavior
- The dispatcher resolves the handler from the DI container at dispatch time.
- Exactly one handler must be registered per request type. If zero or more than one handler is found, an exception is thrown.
null commands or queries throw ArgumentNullException immediately.
Models Reference
Unit
A value type used as the return type for void-like command results. It signals “no meaningful return value” without using void in generics.
| Member |
Type |
Description |
Value |
Unit (static) |
The single shared instance of Unit |
There are two Unit types in this library: Marai.Bridgetor.Models.Unit and Marai.Bridgetor.Abstractions.Unit. Both are identical readonly value types. ICommand uses Models.Unit internally.
Exceptions Reference
| Exception |
Thrown When |
Properties |
HandlerNotFoundException |
No handler is registered in DI for the given request type |
HandlerType — the handler interface type that was not found |
MultipleHandlersFoundException |
More than one handler is registered for the same request type |
HandlerType — the handler interface type with multiple registrations |
Both extend InvalidOperationException.
| 1
2
3
4
5
6
7
8
9
10
11
12 |
try
{
var result = await dispatcher.Send<Guid>(new CreateOrderCommand(“cust-1”, 99.99m));
}
catch (HandlerNotFoundException ex)
{
// No handler registered for CreateOrderCommand
Console.WriteLine($“Missing handler: {ex.HandlerType.FullName}”);
}
catch (MultipleHandlersFoundException ex)
{
// More than one handler found — check your DI registrations
Console.WriteLine($“Ambiguous handler: {ex.HandlerType.FullName}”);
} |
Full API Reference
| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 |
// Abstractions
interface ICommand<out TResult> { }
interface ICommand : ICommand<Unit> { } // Void command marker
interface IQuery<out TResult> { } // Query marker
interface ICommandHandler<in TCommand, TResult>
where TCommand : ICommand<TResult>
{
Task<TResult> Handle(TCommand command, CancellationToken ct); // Handle command with result
}
interface ICommandHandler<in TCommand>
where TCommand : ICommand
{
Task Handle(TCommand command, CancellationToken ct); // Handle void command
}
interface IQueryHandler<in TQuery, TResult>
where TQuery : IQuery<TResult>
{
Task<TResult> Handle(TQuery query, CancellationToken ct); // Handle query
}
interface IDispatcher
{
Task<TResult> Send<TResult>(ICommand<TResult> command, CancellationToken ct = default); // Dispatch command with result
Task Send(ICommand command, CancellationToken ct = default); // Dispatch void command
Task<TResult> Send<TResult>(IQuery<TResult> query, CancellationToken ct = default); // Dispatch query
}
// DI Registration
static IServiceCollection AddMaraiBridgetor(this IServiceCollection services, params Assembly[] assemblies);
// Exceptions
sealed class HandlerNotFoundException : InvalidOperationException
{
Type HandlerType { get; }
}
sealed class MultipleHandlersFoundException : InvalidOperationException
{
Type HandlerType { get; }
} |
License
This project is licensed under the Marai Proprietary Software License Agreement.
Free for personal and commercial use.
For the complete license agreement and terms, please visit: https://marai.dev/proprietary-software-license-agreement