Quick Start
Mediator pattern and Command Query Responsibility Segregation (CQRS) implementation in .NET
| Library | Package | Description |
|---|---|---|
| Arbiter.Mediation | Lightweight and extensible implementation of the Mediator pattern | |
| Arbiter.Mediation.OpenTelemetry | OpenTelemetry support for Arbiter.Mediation library | |
| Arbiter.CommandQuery | Base package for Commands, Queries and Behaviors | |
| Arbiter.CommandQuery.EntityFramework | Entity Framework Core handlers for the base Commands and Queries | |
| Arbiter.CommandQuery.MongoDB | Mongo DB handlers for the base Commands and Queries | |
| Arbiter.CommandQuery.Endpoints | Minimal API endpoints for base Commands and Queries | |
| Arbiter.CommandQuery.Mvc | MVC Controllers for base Commands and Queries | |
| Arbiter.Dispatcher.Server | Server-side endpoint for Blazor WASM dispatcher requests | |
| Arbiter.Dispatcher.Client | Client dispatcher for WASM and Server Interactive modes |
Arbiter.Mediation
A lightweight and extensible implementation of the Mediator pattern for .NET applications, designed for clean, modular architectures like Vertical Slice Architecture and CQRS.
Mediation Installation
The Arbiter Mediation library is available on nuget.org via package name Arbiter.Mediation.
To install Arbiter Mediation, run the following command in the Package Manager Console
Install-Package Arbiter.Mediation
OR
dotnet add package Arbiter.Mediation
Mediation Features
- Request with response handling using
IRequest<TResponse>andIRequestHandler<TRequest, TResponse> - Notifications (Events) using
INotificationandINotificationHandler<TNotification> - Pipeline Behaviors, like middleware, using
IPipelineBehavior<TRequest, TResponse> - Dependence Injection based resolution of handlers and behaviors via scoped
IServiceProvider - Supports OpenTelemetry tracing and meters via
Arbiter.Mediation.OpenTelemetry
Define Request
public class Ping : IRequest<Pong>
{
public string? Message { get; set; }
}
Implement Handler
public class PingHandler : IRequestHandler<Ping, Pong>
{
public async ValueTask<Pong?> Handle(
Ping request,
CancellationToken cancellationToken = default)
{
// Simulate some work
await Task.Delay(100, cancellationToken);
return new Pong { Message = $"{request.Message} Pong" };
}
}
Define Pipeline Behavior
public class PingBehavior : IPipelineBehavior<Ping, Pong>
{
public async ValueTask<Pong?> Handle(
Ping request,
RequestHandlerDelegate<Pong> next,
CancellationToken cancellationToken = default)
{
// Do something before the request is handled
var response = await next(cancellationToken);
// Do something after the request is handled
return response;
}
}
Register Handlers
// Register Mediator services
services.AddMediator();
// Register handlers
services.TryAddTransient<IRequestHandler<Ping, Pong>, PingHandler>();
// Optionally register pipeline behaviors, supports multiple behaviors
services.AddTransient<IPipelineBehavior<Ping, Pong>, PingBehavior>();
Send Request
public class PingController : ControllerBase
{
private readonly IMediator _mediator;
public PingController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> Get(
string? message = null,
CancellationToken? cancellationToken = default)
{
var request = new Ping { Message = message };
var response = await _mediator.Send<Ping, Pong>(request, cancellationToken);
return Ok(response);
}
}
Arbiter.Mediation.OpenTelemetry
OpenTelemetry support for Arbiter.Mediation library
OpenTelemetry Installation
Install-Package Arbiter.Mediation.OpenTelemetry
OR
dotnet add package Arbiter.Mediation.OpenTelemetry
OpenTelemetry Usage
Register via dependency injection
services.AddMediatorDiagnostics();
services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddMediatorInstrumentation()
.AddConsoleExporter()
)
.WithMetrics(metrics => metrics
.AddMediatorInstrumentation()
.AddConsoleExporter()
);
Arbiter.CommandQuery
Command Query Responsibility Segregation (CQRS) framework based on mediator pattern
CommandQuery Installation
The Arbiter Command Query library is available on nuget.org via package name Arbiter.CommandQuery.
To install Arbiter Command Query, run the following command in the Package Manager Console
Install-Package Arbiter.CommandQuery
OR
dotnet add package Arbiter.CommandQuery
CommandQuery Features
- Base commands and queries for common CRUD operations
- Generics base handlers for implementing common CRUD operations
- Common behaviors for audit, caching, soft delete, multi-tenant
- View model to data modal mapping
- Entity Framework Core handlers for common CRUD operations
- MongoDB handlers for common CRUD operations
CommandQuery Usage
Register Command Query services via dependency injection
services.AddCommandQuery();
Query By ID
// sample user claims
var principal = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "JohnDoe") }));
var query = new EntityIdentifierQuery<int, ProductReadModel>(principal, 123);
// Send the query to the mediator for execution
var result = await mediator.Send(query);
Query By Filter
var entityQuery = new EntityQuery
{
Filter = new EntityFilter { Name = "Status", Operator = FilterOperators.Equal, Value = "Active" },
Sort = new[] { new EntitySort { Name = "Name", Direction = SortDirections.Ascending } },
Page = 1,
PageSize = 20
};
var query = new EntityPagedQuery<ProductReadModel>(principal, entityQuery);
// Send the query to the mediator for execution
var pagedResult = await mediator.Send(query);
Update Command
var id = 123; // The ID of the entity to update
var updateModel = new ProductUpdateModel
{
Name = "Updated Product",
Description = "Updated description of the product",
Price = 29.99m
};
var command = new EntityUpdateCommand<int, ProductUpdateModel, ProductReadModel>(principal, id, updateModel);
// Send the command to the mediator for execution
var result = await mediator.Send(command);
Arbiter.CommandQuery.EntityFramework
Entity Framework Core handlers for the base Commands and Queries
EntityFramework Installation
Install-Package Arbiter.CommandQuery.EntityFramework
OR
dotnet add package Arbiter.CommandQuery.EntityFramework
EntityFramework Usage
Register via dependency injection
// Add Entity Framework Core services
services.AddDbContext<TrackerContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("TrackerConnection"))
);
// Register Command Query services
services.AddCommandQuery();
// Implement and register IMapper
services.AddSingleton<IMapper, MyMapper>();
// Implement and register IValidator
services.AddSingleton<IValidator, MyValidator>();
// Register Entity Framework Core handlers for each Entity
services.AddEntityQueries<TrackerContext, Product, int, ProductReadModel>();
services.AddEntityCommands<TrackerContext, Product, int, ProductReadModel, ProductCreateModel, ProductUpdateModel>();
Arbiter.CommandQuery.MongoDB
Mongo DB handlers for the base Commands and Queries
MongoDB Installation
Install-Package Arbiter.CommandQuery.MongoDB
OR
dotnet add package Arbiter.CommandQuery.MongoDB
MongoDB Usage
Register via dependency injection
// Add MongoDB Repository services
services.AddMongoRepository("Tracker");
// Register Command Query services
services.AddCommandQuery();
// Implement and register IMapper
services.AddSingleton<IMapper, MyMapper>();
// Implement and register IValidator
services.AddSingleton<IValidator, MyValidator>();
// Register MongoDB handlers for each Entity
services.AddEntityQueries<IMongoEntityRepository<Product>, Product, string, ProductReadModel>();
services.AddEntityCommands<IMongoEntityRepository<Product>, Product, string, ProductReadModel, ProductCreateModel, ProductUpdateModel>();
Arbiter.CommandQuery.Endpoints
Minimal API endpoints for base Commands and Queries
Endpoints Installation
Install-Package Arbiter.CommandQuery.Endpoints
OR
dotnet add package Arbiter.CommandQuery.Endpoints
Endpoints Usage
Register via dependency injection
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// add endpoints services
builder.Services.AddEndpointRoutes();
var app = builder.Build();
// map endpoint routes
app.MapEndpointRoutes();
Example Endpoint
public class ProductEndpoint : EntityCommandEndpointBase<int, ProductReadModel, ProductReadModel, ProductCreateModel, ProductUpdateModel>
{
public ProductEndpoint(ILoggerFactory loggerFactory)
: base(loggerFactory, "Product")
{
}
}
// Register endpoint, must support duplicate (IEnumerable) IEndpointRoute registrations
builder.Services.AddSingleton<IEndpointRoute, ProductEndpoint>();
Arbiter.CommandQuery.Mvc
MVC Controllers for base Commands and Queries
MVC Installation
Install-Package Arbiter.CommandQuery.Mvc
OR
dotnet add package Arbiter.CommandQuery.Mvc
Arbiter.Dispatcher.Server
ASP.NET Core endpoint that receives dispatcher requests from Blazor WebAssembly clients and routes them through the mediator pipeline.
Dispatcher Server Installation
Install-Package Arbiter.Dispatcher.Server
OR
dotnet add package Arbiter.Dispatcher.Server
Dispatcher Server Features
- Single
POST /api/dispatcher/sendendpoint accepting JSON and MessagePack payloads - Resolves the request CLR type from the
X-Message-Request-TypeHTTP header - Injects the current
ClaimsPrincipalinto any request that implementsIRequestPrincipal - Returns RFC 7807 Problem Details on error
Dispatcher Server Usage
Register and map the dispatcher endpoint in the Blazor host project:
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services
.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
// Register the server dispatcher for Interactive Server rendering
builder.Services.AddServerDispatcher();
// Register the DispatcherEndpoint and MessagePack serializer options
builder.Services.AddDispatcherService();
var app = builder.Build();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(Client.Routes).Assembly);
// Map the POST /api/dispatcher/send route and require authorization
app.MapDispatcherService().RequireAuthorization();
app.Run();
Arbiter.Dispatcher.Client
Client-side IDispatcher abstraction for Blazor components, with implementations for WebAssembly (JSON and MessagePack) and Server Interactive render modes.
Dispatcher Client Installation
Install-Package Arbiter.Dispatcher.Client
OR
dotnet add package Arbiter.Dispatcher.Client
Dispatcher Client Features
IDispatcherabstraction used by Blazor components — transport is resolved by the DI containerMessagePackDispatcherandJsonDispatcherfor WebAssembly: serialize requests and POST to/api/dispatcher/sendServerDispatcherfor Interactive Server: delegates directly toIMediatorwith no HTTP hopDispatcherDataServicewith high-levelGet,Page,Create,Update,Save,DeletemethodsModelStateLoaderandModelStateEditorfor loading and editing entities in Blazor components
Dispatcher Client Usage
WASM client project
var builder = WebAssemblyHostBuilder.CreateDefault(args);
// Register the MessagePack dispatcher for WebAssembly rendering
builder.Services
.AddMessagePackDispatcher((sp, client) =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
});
await builder.Build().RunAsync();
Send a query from a Blazor component
public class UserDetailPage : ComponentBase
{
[Inject] public required IDispatcher Dispatcher { get; set; }
private UserReadModel? _user;
protected override async Task OnInitializedAsync()
{
var principal = new ClaimsPrincipal(); // supply from AuthenticationStateProvider
var query = new EntityIdentifierQuery<int, UserReadModel>(principal, userId);
_user = await Dispatcher
.Send<EntityIdentifierQuery<int, UserReadModel>, UserReadModel>(
query,
CancellationToken.None);
}
}
Use DispatcherDataService for higher-level access
// Subclass DispatcherDataService to supply the authenticated user
public class DataService : DispatcherDataService
{
private readonly AuthenticationStateProvider _authenticationStateProvider;
public DataService(IDispatcher dispatcher, AuthenticationStateProvider authProvider)
: base(dispatcher)
{
_authenticationStateProvider = authProvider;
}
public override async ValueTask<ClaimsPrincipal?> GetUser(
CancellationToken cancellationToken = default)
{
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
return state?.User;
}
}
// Register the subclass
builder.Services.AddTransient<IDispatcherDataService, DataService>();
// Use in a component
var user = await dataService.Get<int, UserReadModel>(userId);
var pagedResult = await dataService.Page<UserReadModel>(entityQuery);
var newUser = await dataService.Create<UserCreateModel, UserReadModel>(createModel);