Indiko.Blocks.Mediation.Abstractions
2.1.1
dotnet add package Indiko.Blocks.Mediation.Abstractions --version 2.1.1
NuGet\Install-Package Indiko.Blocks.Mediation.Abstractions -Version 2.1.1
<PackageReference Include="Indiko.Blocks.Mediation.Abstractions" Version="2.1.1" />
<PackageVersion Include="Indiko.Blocks.Mediation.Abstractions" Version="2.1.1" />
<PackageReference Include="Indiko.Blocks.Mediation.Abstractions" />
paket add Indiko.Blocks.Mediation.Abstractions --version 2.1.1
#r "nuget: Indiko.Blocks.Mediation.Abstractions, 2.1.1"
#:package Indiko.Blocks.Mediation.Abstractions@2.1.1
#addin nuget:?package=Indiko.Blocks.Mediation.Abstractions&version=2.1.1
#tool nuget:?package=Indiko.Blocks.Mediation.Abstractions&version=2.1.1
Indiko.Blocks.Mediation.Abstractions
Core abstractions for implementing CQRS (Command Query Responsibility Segregation) and Mediator patterns in the Indiko framework.
Overview
This package provides the fundamental contracts for building applications using the Mediator pattern and CQRS architecture, enabling clean separation between queries and commands with support for pipeline behaviors and notifications.
Features
- IMediator Interface: Central mediator for request/response handling
- CQRS Support: Separate interfaces for Commands and Queries
- ICommand<TResult>: Command pattern abstraction
- IQuery<TResult>: Query pattern abstraction
- INotification: Pub/sub notification pattern
- Pipeline Behaviors: Cross-cutting concerns (logging, validation, caching)
- Request/Response Pattern: Type-safe request handling
- Saga Support: Long-running transaction patterns
- Async/Await: Full asynchronous support
Installation
dotnet add package Indiko.Blocks.Mediation.Abstractions
Key Interfaces
IMediator
Central interface for sending requests and publishing notifications.
public interface IMediator
{
// Send a request and get a response
Task<TResponse> Send<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default)
where TRequest : IRequest<TResponse>;
// Send a command (returns bool)
Task Send<TRequest>(TRequest request, CancellationToken cancellationToken = default)
where TRequest : IRequest;
// Publish a notification to multiple handlers
Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
where TNotification : INotification;
}
ICommand<TResult>
Marker interface for commands (write operations).
// Command that returns a specific result
public interface ICommand<out TResult> : IRequest<TResult>
{
}
// Command that returns bool (success/failure)
public interface ICommand : ICommand<bool>
{
}
IQuery<TResult>
Marker interface for queries (read operations).
// Query that returns a specific result
public interface IQuery<out TResult> : IRequest<TResult>
{
}
// Query that returns a collection
public interface IQuery : IQuery<IEnumerable<IResult>>
{
}
IRequestHandler<TRequest, TResponse>
Handler interface for processing requests.
public interface IRequestHandler<in TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}
INotification & INotificationHandler
Pub/sub notification pattern.
public interface INotification
{
// Marker interface
}
public interface INotificationHandler<in TNotification>
where TNotification : INotification
{
Task Handle(TNotification notification, CancellationToken cancellationToken);
}
IPipelineBehavior<TRequest, TResponse>
Cross-cutting concern pipeline.
public interface IPipelineBehavior<in TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken);
}
CQRS Pattern
Commands (Write Operations)
// Define a command
public class CreateUserCommand : ICommand<Guid>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
// Implement command handler
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Guid>
{
private readonly IUserRepository _userRepository;
private readonly IUnitOfWork _unitOfWork;
public CreateUserCommandHandler(IUserRepository userRepository, IUnitOfWork unitOfWork)
{
_userRepository = userRepository;
_unitOfWork = unitOfWork;
}
public async Task<Guid> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
var user = new User
{
Id = Guid.NewGuid(),
FirstName = request.FirstName,
LastName = request.LastName,
Email = request.Email
};
await _userRepository.AddAsync(user, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return user.Id;
}
}
// Use in controller
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] CreateUserCommand command)
{
var userId = await _mediator.Send<CreateUserCommand, Guid>(command);
return CreatedAtAction(nameof(GetUser), new { id = userId }, userId);
}
Queries (Read Operations)
// Define a query
public class GetUserByIdQuery : IQuery<UserDto>
{
public Guid UserId { get; set; }
}
// Implement query handler
public class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery, UserDto>
{
private readonly IUserRepository _userRepository;
public GetUserByIdQueryHandler(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public async Task<UserDto> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
var user = await _userRepository.ReadByIdAsync(request.UserId, cancellationToken);
if (user == null)
return null;
return new UserDto
{
Id = user.Id,
FullName = $"{user.FirstName} {user.LastName}",
Email = user.Email
};
}
}
// Use in controller
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(Guid id)
{
var query = new GetUserByIdQuery { UserId = id };
var user = await _mediator.Send<GetUserByIdQuery, UserDto>(query);
return user != null ? Ok(user) : NotFound();
}
Paged Queries
public class GetUsersPagedQuery : IQuery<PagedList<UserDto>>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 20;
public string SearchTerm { get; set; }
}
public class GetUsersPagedQueryHandler : IRequestHandler<GetUsersPagedQuery, PagedList<UserDto>>
{
private readonly IUserRepository _userRepository;
public async Task<PagedList<UserDto>> Handle(GetUsersPagedQuery request, CancellationToken cancellationToken)
{
var users = await _userRepository.ReadManyByQueryPagedAsync(
where: u => string.IsNullOrEmpty(request.SearchTerm) ||
u.FirstName.Contains(request.SearchTerm) ||
u.LastName.Contains(request.SearchTerm),
page: request.PageNumber,
pageSize: request.PageSize,
cancellationToken: cancellationToken
);
return new PagedList<UserDto>(
users.Select(u => new UserDto { /* map properties */ }),
users.TotalCount,
users.CurrentPage,
users.PageSize
);
}
}
Notifications (Pub/Sub)
// Define notification
public class UserCreatedNotification : INotification
{
public Guid UserId { get; set; }
public string Email { get; set; }
}
// Multiple handlers can handle the same notification
public class SendWelcomeEmailHandler : INotificationHandler<UserCreatedNotification>
{
private readonly IEmailService _emailService;
public async Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken)
{
await _emailService.SendWelcomeEmailAsync(notification.Email);
}
}
public class CreateUserProfileHandler : INotificationHandler<UserCreatedNotification>
{
private readonly IProfileService _profileService;
public async Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken)
{
await _profileService.CreateDefaultProfileAsync(notification.UserId);
}
}
// Publish notification
await _mediator.Publish(new UserCreatedNotification
{
UserId = user.Id,
Email = user.Email
});
Pipeline Behaviors
Validation Behavior
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
{
throw new ValidationException(failures);
}
}
return await next();
}
}
Logging Behavior
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
_logger.LogInformation($"Handling {requestName}");
var stopwatch = Stopwatch.StartNew();
try
{
var response = await next();
stopwatch.Stop();
_logger.LogInformation(
$"Handled {requestName} in {stopwatch.ElapsedMilliseconds}ms");
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex,
$"Error handling {requestName} after {stopwatch.ElapsedMilliseconds}ms");
throw;
}
}
}
Caching Behavior
public class CachingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>, ICacheableQuery
{
private readonly IDistributedCache _cache;
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var cacheKey = request.GetCacheKey();
var cachedResponse = await _cache.GetStringAsync(cacheKey, cancellationToken);
if (cachedResponse != null)
{
return JsonSerializer.Deserialize<TResponse>(cachedResponse);
}
var response = await next();
await _cache.SetStringAsync(
cacheKey,
JsonSerializer.Serialize(response),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
},
cancellationToken);
return response;
}
}
Saga Pattern
public interface ISaga
{
Task ExecuteAsync(CancellationToken cancellationToken = default);
Task CompensateAsync(CancellationToken cancellationToken = default);
}
// Example: Order Processing Saga
public class OrderProcessingSaga : ISaga
{
private readonly IMediator _mediator;
public OrderProcessingSaga(IMediator mediator)
{
_mediator = mediator;
}
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
// Step 1: Reserve inventory
await _mediator.Send(new ReserveInventoryCommand { ... }, cancellationToken);
// Step 2: Process payment
await _mediator.Send(new ProcessPaymentCommand { ... }, cancellationToken);
// Step 3: Create shipment
await _mediator.Send(new CreateShipmentCommand { ... }, cancellationToken);
}
public async Task CompensateAsync(CancellationToken cancellationToken = default)
{
// Rollback in reverse order
await _mediator.Send(new CancelShipmentCommand { ... }, cancellationToken);
await _mediator.Send(new RefundPaymentCommand { ... }, cancellationToken);
await _mediator.Send(new ReleaseInventoryCommand { ... }, cancellationToken);
}
}
Service Registration
public class Startup : WebStartup
{
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
// Register mediator implementation
services.AddMediator(options =>
{
// Scan assemblies for handlers
options.RegisterServicesFromAssembly(typeof(Startup).Assembly);
// Register pipeline behaviors
options.AddBehavior<ValidationBehavior<,>>();
options.AddBehavior<LoggingBehavior<,>>();
options.AddBehavior<CachingBehavior<,>>();
});
}
}
Best Practices
- Single Responsibility: One handler per request type
- Immutable Requests: Use read-only properties
- Thin Controllers: Keep controllers thin, move logic to handlers
- Query Optimization: Use projections, avoid N+1 queries
- Command Validation: Validate commands before processing
- Error Handling: Use pipeline behaviors for consistent error handling
- Naming Conventions:
- Commands:
CreateUserCommand,UpdateOrderCommand - Queries:
GetUserByIdQuery,GetOrdersPagedQuery - Handlers:
CreateUserCommandHandler,GetUserByIdQueryHandler
- Commands:
Benefits
- Decoupling: Loose coupling between sender and receiver
- Single Responsibility: Each handler has one responsibility
- Testability: Easy to unit test handlers in isolation
- Cross-Cutting Concerns: Pipeline behaviors for logging, validation, caching
- CQRS: Clear separation between reads and writes
- Scalability: Query and command handlers can be scaled independently
Target Framework
- .NET 10
Dependencies
Indiko.Blocks.Common.Abstractions
License
See LICENSE file in the repository root.
Related Packages
Indiko.Blocks.Mediation.Mediator- MediatR-based implementationIndiko.Blocks.Mediation.SimpleMediator- Lightweight implementationIndiko.Blocks.EventBus.Abstractions- Event-driven architectureIndiko.Common.Abstractions- Common models and interfaces
| 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
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Indiko.Blocks.Mediation.Abstractions:
| Package | Downloads |
|---|---|
|
Indiko.Blocks.Mediation.Mediator
Building Blocks Mediation Mediator |
|
|
Indiko.Blocks.Widget.Common.Abstractions
Building Blocks Widget Common Abstractions |
|
|
Indiko.Blocks.Mediation.SimpleMediator
Building Blocks Mediation Custom Mediator implemtation called SimpleMediator. Supports Request Handling, Notification and Pipeline Behaviors. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.1.1 | 48 | 12/2/2025 |
| 2.1.0 | 50 | 12/2/2025 |
| 2.0.0 | 279 | 9/17/2025 |
| 1.7.23 | 214 | 9/8/2025 |
| 1.7.22 | 211 | 9/8/2025 |
| 1.7.21 | 210 | 8/14/2025 |
| 1.7.20 | 226 | 6/23/2025 |
| 1.7.19 | 235 | 6/3/2025 |
| 1.7.18 | 205 | 5/29/2025 |
| 1.7.17 | 218 | 5/26/2025 |
| 1.7.15 | 164 | 4/12/2025 |
| 1.7.14 | 171 | 4/11/2025 |
| 1.7.13 | 185 | 3/29/2025 |
| 1.7.12 | 197 | 3/28/2025 |
| 1.7.11 | 209 | 3/28/2025 |
| 1.7.10 | 189 | 3/28/2025 |
| 1.7.9 | 182 | 3/28/2025 |
| 1.7.8 | 197 | 3/28/2025 |
| 1.7.5 | 207 | 3/17/2025 |
| 1.7.4 | 211 | 3/16/2025 |
| 1.7.3 | 235 | 3/16/2025 |
| 1.7.2 | 209 | 3/16/2025 |
| 1.7.1 | 227 | 3/11/2025 |
| 1.7.0 | 203 | 3/11/2025 |
| 1.6.8 | 226 | 3/11/2025 |
| 1.6.7 | 281 | 3/4/2025 |
| 1.6.6 | 196 | 2/26/2025 |
| 1.6.5 | 170 | 2/20/2025 |
| 1.6.4 | 172 | 2/20/2025 |
| 1.6.3 | 177 | 2/5/2025 |
| 1.6.2 | 159 | 1/24/2025 |
| 1.6.1 | 187 | 1/24/2025 |
| 1.6.0 | 153 | 1/16/2025 |
| 1.5.2 | 192 | 1/16/2025 |
| 1.5.1 | 189 | 11/3/2024 |
| 1.5.0 | 201 | 10/26/2024 |
| 1.3.2 | 172 | 10/24/2024 |
| 1.3.0 | 189 | 10/10/2024 |
| 1.2.5 | 191 | 10/9/2024 |
| 1.2.4 | 183 | 10/8/2024 |
| 1.2.1 | 185 | 10/3/2024 |
| 1.2.0 | 173 | 9/29/2024 |
| 1.1.1 | 207 | 9/23/2024 |
| 1.1.0 | 234 | 9/18/2024 |
| 1.0.33 | 222 | 9/15/2024 |
| 1.0.28 | 233 | 8/28/2024 |
| 1.0.27 | 223 | 8/24/2024 |
| 1.0.26 | 234 | 7/7/2024 |
| 1.0.25 | 224 | 7/6/2024 |
| 1.0.24 | 191 | 6/25/2024 |
| 1.0.23 | 176 | 6/1/2024 |
| 1.0.22 | 185 | 5/14/2024 |
| 1.0.21 | 177 | 5/14/2024 |
| 1.0.20 | 237 | 4/8/2024 |
| 1.0.19 | 229 | 4/3/2024 |
| 1.0.18 | 240 | 3/23/2024 |
| 1.0.17 | 241 | 3/19/2024 |
| 1.0.16 | 221 | 3/19/2024 |
| 1.0.15 | 215 | 3/11/2024 |
| 1.0.14 | 223 | 3/10/2024 |
| 1.0.13 | 206 | 3/6/2024 |
| 1.0.12 | 254 | 3/1/2024 |
| 1.0.11 | 236 | 3/1/2024 |
| 1.0.10 | 197 | 3/1/2024 |
| 1.0.9 | 244 | 3/1/2024 |
| 1.0.8 | 217 | 2/19/2024 |
| 1.0.7 | 244 | 2/17/2024 |
| 1.0.6 | 214 | 2/17/2024 |
| 1.0.5 | 236 | 2/17/2024 |
| 1.0.4 | 203 | 2/7/2024 |
| 1.0.3 | 233 | 2/6/2024 |
| 1.0.1 | 194 | 2/6/2024 |
| 1.0.0 | 287 | 1/9/2024 |
| 1.0.0-preview99 | 261 | 12/22/2023 |
| 1.0.0-preview98 | 192 | 12/21/2023 |
| 1.0.0-preview97 | 209 | 12/21/2023 |
| 1.0.0-preview96 | 195 | 12/20/2023 |
| 1.0.0-preview94 | 179 | 12/18/2023 |
| 1.0.0-preview93 | 399 | 12/13/2023 |
| 1.0.0-preview92 | 203 | 12/13/2023 |
| 1.0.0-preview91 | 236 | 12/12/2023 |
| 1.0.0-preview90 | 202 | 12/11/2023 |
| 1.0.0-preview89 | 208 | 12/11/2023 |
| 1.0.0-preview88 | 319 | 12/6/2023 |
| 1.0.0-preview87 | 216 | 12/6/2023 |
| 1.0.0-preview86 | 208 | 12/6/2023 |
| 1.0.0-preview85 | 235 | 12/6/2023 |
| 1.0.0-preview84 | 236 | 12/5/2023 |
| 1.0.0-preview83 | 236 | 12/5/2023 |
| 1.0.0-preview82 | 220 | 12/5/2023 |
| 1.0.0-preview81 | 231 | 12/4/2023 |
| 1.0.0-preview80 | 203 | 12/1/2023 |
| 1.0.0-preview77 | 210 | 12/1/2023 |
| 1.0.0-preview76 | 202 | 12/1/2023 |
| 1.0.0-preview75 | 221 | 12/1/2023 |
| 1.0.0-preview74 | 264 | 11/26/2023 |
| 1.0.0-preview73 | 257 | 11/7/2023 |
| 1.0.0-preview72 | 219 | 11/6/2023 |
| 1.0.0-preview71 | 254 | 11/3/2023 |
| 1.0.0-preview70 | 225 | 11/2/2023 |
| 1.0.0-preview69 | 204 | 11/2/2023 |
| 1.0.0-preview68 | 220 | 11/2/2023 |
| 1.0.0-preview67 | 206 | 11/2/2023 |
| 1.0.0-preview66 | 193 | 11/2/2023 |
| 1.0.0-preview65 | 240 | 11/2/2023 |
| 1.0.0-preview64 | 249 | 11/2/2023 |
| 1.0.0-preview63 | 232 | 11/2/2023 |
| 1.0.0-preview62 | 214 | 11/1/2023 |
| 1.0.0-preview61 | 224 | 11/1/2023 |
| 1.0.0-preview60 | 237 | 11/1/2023 |
| 1.0.0-preview59 | 226 | 11/1/2023 |
| 1.0.0-preview58 | 212 | 10/31/2023 |
| 1.0.0-preview57 | 217 | 10/31/2023 |
| 1.0.0-preview56 | 192 | 10/31/2023 |
| 1.0.0-preview55 | 201 | 10/31/2023 |
| 1.0.0-preview54 | 228 | 10/31/2023 |
| 1.0.0-preview53 | 199 | 10/31/2023 |
| 1.0.0-preview52 | 192 | 10/31/2023 |
| 1.0.0-preview51 | 235 | 10/31/2023 |
| 1.0.0-preview50 | 254 | 10/31/2023 |
| 1.0.0-preview48 | 317 | 10/31/2023 |
| 1.0.0-preview46 | 214 | 10/31/2023 |
| 1.0.0-preview45 | 201 | 10/31/2023 |
| 1.0.0-preview44 | 211 | 10/31/2023 |
| 1.0.0-preview43 | 226 | 10/31/2023 |
| 1.0.0-preview42 | 225 | 10/30/2023 |
| 1.0.0-preview41 | 211 | 10/30/2023 |
| 1.0.0-preview40 | 225 | 10/27/2023 |
| 1.0.0-preview39 | 180 | 10/27/2023 |
| 1.0.0-preview38 | 159 | 10/27/2023 |
| 1.0.0-preview37 | 167 | 10/27/2023 |
| 1.0.0-preview36 | 184 | 10/27/2023 |
| 1.0.0-preview35 | 161 | 10/27/2023 |
| 1.0.0-preview34 | 167 | 10/27/2023 |
| 1.0.0-preview33 | 187 | 10/26/2023 |
| 1.0.0-preview32 | 181 | 10/26/2023 |
| 1.0.0-preview31 | 196 | 10/26/2023 |
| 1.0.0-preview30 | 185 | 10/26/2023 |
| 1.0.0-preview29 | 223 | 10/26/2023 |
| 1.0.0-preview28 | 202 | 10/26/2023 |
| 1.0.0-preview27 | 206 | 10/26/2023 |
| 1.0.0-preview26 | 209 | 10/25/2023 |
| 1.0.0-preview25 | 191 | 10/23/2023 |
| 1.0.0-preview24 | 179 | 10/23/2023 |
| 1.0.0-preview23 | 184 | 10/23/2023 |
| 1.0.0-preview22 | 193 | 10/23/2023 |
| 1.0.0-preview21 | 172 | 10/23/2023 |
| 1.0.0-preview20 | 194 | 10/20/2023 |
| 1.0.0-preview19 | 289 | 10/19/2023 |
| 1.0.0-preview18 | 329 | 10/18/2023 |
| 1.0.0-preview16 | 236 | 10/11/2023 |
| 1.0.0-preview101 | 234 | 1/5/2024 |