Indiko.Blocks.Mediation.Abstractions 2.1.2

dotnet add package Indiko.Blocks.Mediation.Abstractions --version 2.1.2
                    
NuGet\Install-Package Indiko.Blocks.Mediation.Abstractions -Version 2.1.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Indiko.Blocks.Mediation.Abstractions" Version="2.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Indiko.Blocks.Mediation.Abstractions" Version="2.1.2" />
                    
Directory.Packages.props
<PackageReference Include="Indiko.Blocks.Mediation.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Indiko.Blocks.Mediation.Abstractions --version 2.1.2
                    
#r "nuget: Indiko.Blocks.Mediation.Abstractions, 2.1.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Indiko.Blocks.Mediation.Abstractions@2.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Indiko.Blocks.Mediation.Abstractions&version=2.1.2
                    
Install as a Cake Addin
#tool nuget:?package=Indiko.Blocks.Mediation.Abstractions&version=2.1.2
                    
Install as a Cake Tool

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

  1. Single Responsibility: One handler per request type
  2. Immutable Requests: Use read-only properties
  3. Thin Controllers: Keep controllers thin, move logic to handlers
  4. Query Optimization: Use projections, avoid N+1 queries
  5. Command Validation: Validate commands before processing
  6. Error Handling: Use pipeline behaviors for consistent error handling
  7. Naming Conventions:
    • Commands: CreateUserCommand, UpdateOrderCommand
    • Queries: GetUserByIdQuery, GetOrdersPagedQuery
    • Handlers: CreateUserCommandHandler, GetUserByIdQueryHandler

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.

  • Indiko.Blocks.Mediation.Mediator - MediatR-based implementation
  • Indiko.Blocks.Mediation.SimpleMediator - Lightweight implementation
  • Indiko.Blocks.EventBus.Abstractions - Event-driven architecture
  • Indiko.Common.Abstractions - Common models and interfaces
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.2 289 12/18/2025
2.1.1 689 12/2/2025
2.1.0 688 12/2/2025
2.0.0 289 9/17/2025
1.7.23 227 9/8/2025
1.7.22 221 9/8/2025
1.7.21 221 8/14/2025
1.7.20 239 6/23/2025
1.7.19 244 6/3/2025
1.7.18 218 5/29/2025
1.7.17 225 5/26/2025
1.7.15 172 4/12/2025
1.7.14 181 4/11/2025
1.7.13 197 3/29/2025
1.7.12 202 3/28/2025
1.7.11 219 3/28/2025
1.7.10 200 3/28/2025
1.7.9 193 3/28/2025
1.7.8 207 3/28/2025
1.7.5 216 3/17/2025
1.7.4 218 3/16/2025
1.7.3 244 3/16/2025
1.7.2 216 3/16/2025
1.7.1 236 3/11/2025
1.7.0 215 3/11/2025
1.6.8 236 3/11/2025
1.6.7 291 3/4/2025
1.6.6 209 2/26/2025
1.6.5 181 2/20/2025
1.6.4 181 2/20/2025
1.6.3 192 2/5/2025
1.6.2 169 1/24/2025
1.6.1 201 1/24/2025
1.6.0 160 1/16/2025
1.5.2 200 1/16/2025
1.5.1 193 11/3/2024
1.5.0 211 10/26/2024
1.3.2 180 10/24/2024
1.3.0 197 10/10/2024
1.2.5 198 10/9/2024
1.2.4 194 10/8/2024
1.2.1 195 10/3/2024
1.2.0 177 9/29/2024
1.1.1 218 9/23/2024
1.1.0 242 9/18/2024
1.0.33 230 9/15/2024
1.0.28 238 8/28/2024
1.0.27 228 8/24/2024
1.0.26 239 7/7/2024
1.0.25 232 7/6/2024
1.0.24 196 6/25/2024
1.0.23 187 6/1/2024
1.0.22 198 5/14/2024
1.0.21 184 5/14/2024
1.0.20 246 4/8/2024
1.0.19 240 4/3/2024
1.0.18 250 3/23/2024
1.0.17 249 3/19/2024
1.0.16 233 3/19/2024
1.0.15 227 3/11/2024
1.0.14 232 3/10/2024
1.0.13 217 3/6/2024
1.0.12 262 3/1/2024
1.0.11 248 3/1/2024
1.0.10 204 3/1/2024
1.0.9 252 3/1/2024
1.0.8 229 2/19/2024
1.0.7 253 2/17/2024
1.0.6 221 2/17/2024
1.0.5 240 2/17/2024
1.0.4 207 2/7/2024
1.0.3 240 2/6/2024
1.0.1 203 2/6/2024
1.0.0 295 1/9/2024
1.0.0-preview99 271 12/22/2023
1.0.0-preview98 200 12/21/2023
1.0.0-preview97 218 12/21/2023
1.0.0-preview96 207 12/20/2023
1.0.0-preview94 188 12/18/2023
1.0.0-preview93 411 12/13/2023
1.0.0-preview92 211 12/13/2023
1.0.0-preview91 247 12/12/2023
1.0.0-preview90 212 12/11/2023
1.0.0-preview89 216 12/11/2023
1.0.0-preview88 325 12/6/2023
1.0.0-preview87 227 12/6/2023
1.0.0-preview86 220 12/6/2023
1.0.0-preview85 245 12/6/2023
1.0.0-preview84 246 12/5/2023
1.0.0-preview83 243 12/5/2023
1.0.0-preview82 227 12/5/2023
1.0.0-preview81 238 12/4/2023
1.0.0-preview80 213 12/1/2023
1.0.0-preview77 218 12/1/2023
1.0.0-preview76 209 12/1/2023
1.0.0-preview75 230 12/1/2023
1.0.0-preview74 272 11/26/2023
1.0.0-preview73 267 11/7/2023
1.0.0-preview72 225 11/6/2023
1.0.0-preview71 263 11/3/2023
1.0.0-preview70 233 11/2/2023
1.0.0-preview69 210 11/2/2023
1.0.0-preview68 226 11/2/2023
1.0.0-preview67 216 11/2/2023
1.0.0-preview66 196 11/2/2023
1.0.0-preview65 253 11/2/2023
1.0.0-preview64 256 11/2/2023
1.0.0-preview63 241 11/2/2023
1.0.0-preview62 222 11/1/2023
1.0.0-preview61 230 11/1/2023
1.0.0-preview60 241 11/1/2023
1.0.0-preview59 235 11/1/2023
1.0.0-preview58 223 10/31/2023
1.0.0-preview57 229 10/31/2023
1.0.0-preview56 198 10/31/2023
1.0.0-preview55 209 10/31/2023
1.0.0-preview54 235 10/31/2023
1.0.0-preview53 209 10/31/2023
1.0.0-preview52 199 10/31/2023
1.0.0-preview51 241 10/31/2023
1.0.0-preview50 260 10/31/2023
1.0.0-preview48 327 10/31/2023
1.0.0-preview46 219 10/31/2023
1.0.0-preview45 207 10/31/2023
1.0.0-preview44 218 10/31/2023
1.0.0-preview43 230 10/31/2023
1.0.0-preview42 231 10/30/2023
1.0.0-preview41 222 10/30/2023
1.0.0-preview40 230 10/27/2023
1.0.0-preview39 185 10/27/2023
1.0.0-preview38 168 10/27/2023
1.0.0-preview37 182 10/27/2023
1.0.0-preview36 193 10/27/2023
1.0.0-preview35 166 10/27/2023
1.0.0-preview34 177 10/27/2023
1.0.0-preview33 196 10/26/2023
1.0.0-preview32 191 10/26/2023
1.0.0-preview31 207 10/26/2023
1.0.0-preview30 193 10/26/2023
1.0.0-preview29 230 10/26/2023
1.0.0-preview28 215 10/26/2023
1.0.0-preview27 213 10/26/2023
1.0.0-preview26 216 10/25/2023
1.0.0-preview25 201 10/23/2023
1.0.0-preview24 188 10/23/2023
1.0.0-preview23 193 10/23/2023
1.0.0-preview22 204 10/23/2023
1.0.0-preview21 182 10/23/2023
1.0.0-preview20 201 10/20/2023
1.0.0-preview19 300 10/19/2023
1.0.0-preview18 339 10/18/2023
1.0.0-preview16 242 10/11/2023
1.0.0-preview101 245 1/5/2024