Arcanic.Mediator.Query.Abstractions
10.0.0
dotnet add package Arcanic.Mediator.Query.Abstractions --version 10.0.0
NuGet\Install-Package Arcanic.Mediator.Query.Abstractions -Version 10.0.0
<PackageReference Include="Arcanic.Mediator.Query.Abstractions" Version="10.0.0" />
<PackageVersion Include="Arcanic.Mediator.Query.Abstractions" Version="10.0.0" />
<PackageReference Include="Arcanic.Mediator.Query.Abstractions" />
paket add Arcanic.Mediator.Query.Abstractions --version 10.0.0
#r "nuget: Arcanic.Mediator.Query.Abstractions, 10.0.0"
#:package Arcanic.Mediator.Query.Abstractions@10.0.0
#addin nuget:?package=Arcanic.Mediator.Query.Abstractions&version=10.0.0
#tool nuget:?package=Arcanic.Mediator.Query.Abstractions&version=10.0.0
Arcanic Mediator
A high-performance, modular mediator pattern implementation for .NET that provides clean separation of concerns through Command Query Responsibility Segregation (CQRS) and event-driven architecture.
Installation
Install the packages you need:
dotnet add package Arcanic.Mediator.Command
dotnet add package Arcanic.Mediator.Query
dotnet add package Arcanic.Mediator.Event
Quick Start
1. Configure Services
using Arcanic.Mediator;
using Arcanic.Mediator.Command;
using Arcanic.Mediator.Query;
using Arcanic.Mediator.Event;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
// Add Arcanic Mediator with modules
builder.Services.AddArcanicMediator()
.AddCommands(Assembly.GetExecutingAssembly())
.AddQueries(Assembly.GetExecutingAssembly())
.AddEvents(Assembly.GetExecutingAssembly());
var app = builder.Build();
2. Define Messages and Handlers
Commands
using Arcanic.Mediator.Command.Abstractions;
using Arcanic.Mediator.Command.Abstractions.Handler;
using Arcanic.Mediator.Event.Abstractions;
// Command without return value
public class CreateProductCommand : ICommand
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
// Command handler with return value
public class CreateProductCommandHandler : ICommandHandler<AddProductCommand, int>
{
private readonly IPublisher _publisher;
public CreateProductCommandHandler(IPublisher publisher)
{
_publisher = publisher;
}
public async Task<int> HandleAsync(AddProductCommand request, CancellationToken cancellationToken = default)
{
// Save product and get ID
var productId = await CreateProductAsync(request.Name, request.Price);
// Publish domain event
await _publisher.PublishAsync(new ProductCreatedEvent
{
Id = Guid.NewGuid(),
Name = request.Name,
Price = request.Price
}, cancellationToken);
return productId;
}
private async Task<int> CreateProductAsync(string name, decimal price)
{
// Implementation here
await Task.Delay(100); // Simulate async work
return 1;
}
}
Note: Commands implement
ICommandorICommand<TResult>, which inherit fromIRequestfor unified mediator processing.
Queries
using Arcanic.Mediator.Query.Abstractions;
using Arcanic.Mediator.Query.Abstractions.Handler;
public record ProductDto(int Id, string Name, decimal Price);
public class GetProductQuery : IQuery<ProductDto>
{
public int Id { get; set; }
}
public class GetProductQueryHandler : IQueryHandler<GetProductQuery, ProductDto>
{
public async Task<ProductDto> HandleAsync(GetProductQuery request, CancellationToken cancellationToken = default)
{
// Implementation here
return await Task.FromResult(new ProductDto(request.Id, "Sample Product", 19.99m));
}
}
Note: Queries implement
IQuery<TResult>, which inherits fromIRequestfor unified mediator processing.
Events
using Arcanic.Mediator.Event.Abstractions;
using Arcanic.Mediator.Event.Abstractions.Handler;
public class ProductCreatedEvent : IEvent
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
// Multiple event handlers can exist for the same event
public class ProductCreatedEmailHandler : IEventHandler<ProductCreatedEvent>
{
public async Task HandleAsync(ProductCreatedEvent request, CancellationToken cancellationToken = default)
{
// Send notification email
Console.WriteLine($"Sending email for product: {request.Name}");
await Task.CompletedTask;
}
private async Task SendEmailAsync(Guid productId, string productName)
{
// Implementation here
await Task.CompletedTask;
}
}
public class ProductCreatedLoggingHandler : IEventHandler<ProductCreatedEvent>
{
public async Task HandleAsync(ProductCreatedEvent request, CancellationToken cancellationToken = default)
{
// Log the event
Console.WriteLine($"Product created: {request.Id} - {request.Name}");
await Task.CompletedTask;
}
}
3. Use in Controllers
using Microsoft.AspNetCore.Mvc;
using Arcanic.Mediator.Request.Abstractions;
[ApiController]
[Route("[controller]")]
public class ProductController : ControllerBase
{
private readonly IMediator _mediator;
public ProductController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet("{id}")]
public async Task<ProductDto> GetProduct(int id)
{
return await _mediator.SendAsync(new GetProductQuery { Id = id });
}
[HttpPost()]
public async Task CreateProduct(CreateProductCommand command)
{
await _mediator.SendAsync(command);
}
}
Pipeline Behaviors
Arcanic Mediator provides a powerful pipeline system that allows you to implement cross-cutting concerns through different types of pipeline behaviors. Each message type (Commands, Queries, Events, and generic Requests) has its own dedicated pipeline interface, enabling type-safe and context-aware processing.
Pipeline Types and Their Differences
| Pipeline Type | Interface | Purpose | Key Characteristics |
|---|---|---|---|
| Generic Pipeline | IPipelineBehavior<TMessage, TResponse> |
Universal message processing | Works with all message types (Commands, Queries, Events), most flexible |
| Request Pipeline | IRequestPipelineBehavior<TRequest, TResponse> |
Commands and queries processing | Works with ICommand and IQuery messages, shared concerns |
| Command Pipeline | ICommandPipelineBehavior<TCommand, TResponse> |
Write operations processing | Optimized for state changes, transaction support |
| Query Pipeline | IQueryPipelineBehavior<TQuery, TResponse> |
Read operations processing | Caching-aware, performance monitoring focused |
| Event Pipeline | IEventPipelineBehavior<TEvent, TResponse> |
Domain event processing | Audit logging, event sourcing support |
When to Use Each Pipeline Type
- Generic Pipeline: Use for cross-cutting concerns that apply to ALL message types including events (correlation tracking, global error handling, metrics collection)
- Request Pipeline: Use for cross-cutting concerns that apply to both commands and queries only (request validation, authorization)
- Command Pipeline: Use for write-specific concerns (transactions, authorization for modifications, business rule validation)
- Query Pipeline: Use for read-specific optimizations (caching, query performance monitoring, read authorization)
- Event Pipeline: Use for event-specific concerns (audit trails, event sourcing, notification reliability)
Generic Pipeline exemple
The most universal pipeline that works with all message types - Commands, Queries, and Events:
using Arcanic.Mediator.Abstractions.Pipeline;
public class GlobalExceptionPipelineBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
where TMessage : notnull
{
private readonly ILogger<GlobalExceptionPipelineBehavior<TMessage, TResponse>> _logger;
public GlobalExceptionPipelineBehavior(ILogger<GlobalExceptionPipelineBehavior<TMessage, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> HandleAsync(TMessage message, PipelineDelegate<TResponse> next, CancellationToken cancellationToken = default)
{
try
{
return await next(cancellationToken);
}
catch (Exception ex)
{
var messageName = typeof(TMessage).Name;
// Log with structured data for monitoring systems
_logger.LogError(ex,
"[GLOBAL ERROR] Unhandled exception in {MessageName}. Message: {@Message}",
messageName, message);
// You could implement circuit breaker, dead letter queue, etc. here
throw; // Re-throw to maintain the original exception flow
}
}
}
Pipeline Registration and Configuration
Register different pipeline behaviors based on your needs:
var builder = WebApplication.CreateBuilder(args);
// Add Arcanic Mediator with all pipeline types
builder.Services.AddArcanicMediator()
// Generic pipelines (apply to ALL message types - Commands, Queries, Events)
.AddPipelineBehavior(typeof(GlobalMetricsPipelineBehavior<,>))
.AddPipelineBehavior(typeof(GlobalExceptionPipelineBehavior<,>))
// Request pipelines (apply to commands and queries)
.AddRequestPipelineBehavior(typeof(LoggingRequestPipelineBehavior<,>))
// Command-specific pipelines
.AddCommandPipelineBehavior(typeof(AuthorizationCommandPipelineBehavior<,>))
.AddCommandPipelineBehavior(typeof(TransactionCommandPipelineBehavior<,>))
// Query-specific pipelines
.AddQueryPipelineBehavior(typeof(CachingQueryPipelineBehavior<,>))
.AddQueryPipelineBehavior(typeof(PerformanceQueryPipelineBehavior<,>))
// Event-specific pipelines
.AddEventPipelineBehavior(typeof(AuditEventPipelineBehavior<,>))
.AddEventPipelineBehavior(typeof(ReliabilityEventPipelineBehavior<,>))
// Register modules
.AddCommands(Assembly.GetExecutingAssembly())
.AddQueries(Assembly.GetExecutingAssembly())
.AddEvents(Assembly.GetExecutingAssembly());
var app = builder.Build();
Pipeline Execution Order
Pipelines execute in the order they are registered:
- Generic Pipelines (outermost) - Execute for all message types (Commands, Queries, Events)
- Request Pipelines - Execute for commands and queries
- Type-specific Pipelines (Command/Query/Event) - Execute based on message type
- Pre-handlers - Execute before main handler
- Main handler - Executes the core business logic
- Post-handlers - Execute after main handler
Example execution flow for a command:
Generic Pipeline (Metrics)
→ Generic Pipeline (Exception Handling)
→ Request Pipeline (Logging)
→ Command Pipeline (Transaction)
→ Pre-handler (Validation)
→ Main Handler (Business Logic)
→ Post-handler (Notifications)
Example execution flow for an event:
Generic Pipeline (Metrics)
→ Generic Pipeline (Exception Handling)
→ Event Pipeline (Audit)
→ Event Pipeline (Reliability/Retry)
→ Event Handlers (Multiple, in parallel)
Configuration
Configure mediator services with custom settings:
// Configure service lifetime (default is Transient)
builder.Services.AddArcanicMediator(config =>
{
config.InstanceLifetime = InstanceLifetime.Scoped; // or Singleton, Transient
})
.AddCommands(Assembly.GetExecutingAssembly())
.AddQueries(Assembly.GetExecutingAssembly())
.AddEvents(Assembly.GetExecutingAssembly());
ArcanicMediatorConfiguration Options
| Property | Type | Default | Description |
|---|---|---|---|
InstanceLifetime |
InstanceLifetime |
Transient |
Controls how mediator service instances are created and managed by the DI container |
Instance Lifetime Options:
- Transient - New instance created each time (default)
- Scoped - One instance per request/scope
- Singleton - Single instance for the application lifetime
Architecture
The library follows a modular architecture with clear separation:
Core Packages
- Arcanic.Mediator - Core dependency injection extensions and configuration
- Arcanic.Mediator.Abstractions - Common abstractions and pipeline interfaces
- Arcanic.Mediator.Request / Request.Abstractions - Unified request processing and mediation
Feature Packages
- Arcanic.Mediator.Command / Command.Abstractions - Command handling (write operations)
- Arcanic.Mediator.Query / Query.Abstractions - Query handling (read operations)
- Arcanic.Mediator.Event / Event.Abstractions - Event publishing (notifications)
Key Concepts
- Commands - Actions that change state (write operations)
- Queries - Requests for data (read operations)
- Events - Things that have happened (notifications)
- Pre/Post Handlers - Cross-cutting concerns that execute before/after main handlers
- Pipeline Behaviors - Reusable behaviors that wrap message execution
Pipeline Execution Order
- Pipeline Behaviors - Execute in registration order (outermost first)
- Pre-handlers - Execute before main handler for validation, authentication, etc.
- Main handler - Executes the core business logic
- Post-handlers - Execute after main handler for notifications, cleanup, etc.
Performance
- Cached Dispatchers - Avoid reflection overhead with cached dispatcher instances
- Efficient Routing - Direct handler resolution without runtime type discovery
- Minimal Allocations - Optimized for low garbage collection pressure
- Parallel Events - Multiple event handlers execute concurrently
Samples
Check out the samples/CleanArchitecture for a complete working example demonstrating:
- Clean Architecture implementation
- Command and query handlers
- Event publishing and handling
- Pre/post handlers
- Pipeline behaviors
Run the sample:
cd samples/CleanArchitecture/CleanArchitecture.WebApi
dotnet run
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- 📋 Issues
- 📖 Documentation
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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
- Arcanic.Mediator.Request.Abstractions (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.2)
-
net8.0
- Arcanic.Mediator.Request.Abstractions (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
-
net9.0
- Arcanic.Mediator.Request.Abstractions (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.12)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Arcanic.Mediator.Query.Abstractions:
| Package | Downloads |
|---|---|
|
Arcanic.Mediator.Query
Query handling implementation for Arcanic.Mediator providing CQRS query processing capabilities with dependency injection support. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Initial release of Arcanic.Mediator query abstractions.