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
                    
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.1" />
                    
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.1" />
                    
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.1
                    
#r "nuget: Indiko.Blocks.Mediation.Abstractions, 2.1.1"
                    
#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.1
                    
#: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.1
                    
Install as a Cake Addin
#tool nuget:?package=Indiko.Blocks.Mediation.Abstractions&version=2.1.1
                    
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.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