Marai Bridgetor

NuGet License

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