03 Examples - vagnerjsmello/NexMediator GitHub Wiki
This section contains real and complete examples from the AuctionBoardGame
demo application.
They demonstrate how NexMediator handles CQRS, validation, caching, transactions, notifications and more β using real interfaces and pipeline behaviors.
builder.Services.AddMemoryCache();
builder.Services.AddScoped<ICache, MemoryRequestCache>();
builder.Services.AddScoped<ITransactionManager, EfCoreTransactionManager>();
builder.Services.AddScoped<ICorrelationContext, CorrelationContext>();
builder.Services.AddNexMediator(options =>
{
options.AddBehavior(typeof(LoggingBehavior<,>), 1);
options.AddBehavior(typeof(FluentValidationBehavior<,>), 2);
options.AddBehavior(typeof(CachingBehavior<,>), 3);
options.AddBehavior(typeof(TransactionBehavior<,>), 4);
});
This command creates a new auction and implements:
-
INexCommand<T>
: a command that changes state -
ITransactionalRequest<T>
: enables automatic transaction wrapping -
IInvalidateCacheableRequest
: invalidates cache after successful execution
The transaction is handled by EfCoreTransactionManager
, registered via:
services.AddScoped<ITransactionManager, EfCoreTransactionManager>();
The cache invalidation is done after a successful transaction commit, to ensure consistency.
public sealed record CreateAuctionCommand(...) :
INexCommand<Result<CreateAuctionResponse>>,
ITransactionalRequest<Result<CreateAuctionResponse>>,
IInvalidateCacheableRequest
{
public IReadOnlyCollection<string> PrefixesToInvalidate => new[] { "Auctions:" };
public IReadOnlyCollection<string> KeysToInvalidate => Array.Empty<string>();
}
public class CreateAuctionHandler : INexRequestHandler<CreateAuctionCommand, Result<CreateAuctionResponse>>
{
public async Task<Result<CreateAuctionResponse>> Handle(CreateAuctionCommand request, CancellationToken ct)
{
var boardGame = BoardGame.Create(request.Title, request.Publisher, request.ReleaseYear);
var auction = Auction.Create(boardGame, request.StartingPrice, request.EndDate);
await _repository.AddAsync(auction, ct);
await _nexMediator.Publish(new AuctionCreatedNotification(auction.Id), ct);
return Result<CreateAuctionResponse>.Success(auction.ToCreateAuctionResponse());
}
}
This command finalizes an existing auction and implements:
-
INexCommand<T>
: a command handler -
ITransactionalRequest<T>
: wraps execution in a transaction
public sealed record CloseAuctionCommand(Guid AuctionId) :
INexCommand<Result<CloseAuctionResponse>>,
ITransactionalRequest<Result<CloseAuctionResponse>>;
public class CloseAuctionHandler : INexRequestHandler<CloseAuctionCommand, Result<CloseAuctionResponse>>
{
public async Task<Result<CloseAuctionResponse>> Handle(CloseAuctionCommand request, CancellationToken ct)
{
var auction = await _repository.GetByIdAsync(request.AuctionId, ct);
if (auction is null) return Result<CloseAuctionResponse>.NotFound("Auction not found");
auction.Close();
await _repository.UpdateAsync(auction, ct);
return Result<CloseAuctionResponse>.Success(auction.ToCloseAuctionResponse(auction.GetWinningResult()));
}
}
This query supports pagination and caching. It implements:
-
INexQuery<T>
: read-only request -
ICacheableRequest<T>
: enables automatic response caching -
IPagedQuery
: marks it as paginated (for validation)
The cache key is unique per page+size, and the expiration is 2 minutes.
public record GetAllAuctionsQuery(int Page, int PageSize) :
INexQuery<Result<PaginatedResult<GetAllAuctionsResponse>>>,
ICacheableRequest<Result<PaginatedResult<GetAllAuctionsResponse>>>,
IPagedQuery
{
public string CacheKey => $"Auctions:Page={Page}:Size={PageSize}";
public TimeSpan? Expiration => TimeSpan.FromMinutes(2);
}
public class GetAllAuctionsHandler : INexRequestHandler<GetAllAuctionsQuery, Result<PaginatedResult<GetAllAuctionsResponse>>>
{
public async Task<Result<PaginatedResult<GetAllAuctionsResponse>>> Handle(GetAllAuctionsQuery query, CancellationToken ct)
{
var (items, total) = await _repository.GetPagedAsync(query.Page, query.PageSize, ct);
return Result<PaginatedResult<GetAllAuctionsResponse>>.Success(
items.ToPaginatedResult(query.Page, query.PageSize, total));
}
}
Simple read-only query for a specific auctionβs bids.
public record GetAuctionBidsQuery(Guid AuctionId) : INexQuery<Result<GetAuctionBidsResponse>>;
public class GetAuctionBidsHandler : INexRequestHandler<GetAuctionBidsQuery, Result<GetAuctionBidsResponse>>
{
public async Task<Result<GetAuctionBidsResponse>> Handle(GetAuctionBidsQuery query, CancellationToken ct)
{
var auction = await _repository.GetByIdAsync(query.AuctionId, ct);
return auction == null
? Result<GetAuctionBidsResponse>.NotFound("Auction not found")
: Result<GetAuctionBidsResponse>.Success(auction.ToGetAuctionBidsResponse());
}
}
Used to broadcast that a new auction was created. It's handled by cache invalidation logic.
public record AuctionCreatedNotification(Guid AuctionId) : INexNotification;
public class AuctionCreatedCacheInvalidationHandler : INexNotificationHandler<AuctionCreatedNotification>
{
public Task Handle(AuctionCreatedNotification notification, CancellationToken ct)
=> _cache.RemoveByPrefixAsync("Auctions:", ct);
}
Notifies other systems that a bid was placed. Here, it simulates sending an email.
public record BidPlacedNotification(Guid AuctionId, Guid BidderId, decimal Amount, DateTime PlacedAt) : INexNotification;
public class EmailNotificationHandler : INexNotificationHandler<BidPlacedNotification>
{
public Task Handle(BidPlacedNotification notification, CancellationToken ct)
{
Console.WriteLine($"[Email] Bidder {notification.BidderId} placed {notification.Amount:C} on auction {notification.AuctionId}");
return Task.CompletedTask;
}
}
group.MapPost("/", async ([FromBody] CreateAuctionCommand command, INexMediator nexMediator) =>
{
var result = await nexMediator.Send(command);
return result;
});
group.MapPost("/{id}/close", async (Guid id, INexMediator nexMediator) =>
{
var result = await nexMediator.Send(new CloseAuctionCommand(id));
return result;
});
group.MapGet("/", async ([AsParameters] GetAllAuctionsQuery query, INexMediator nexMediator) =>
{
return await nexMediator.Send(query);
});
group.MapGet("/{auctionId}/bids", async (Guid auctionId, INexMediator nexMediator) =>
{
return await nexMediator.Send(new GetAuctionBidsQuery(auctionId));
});
public static class PaginationRules
{
public static void AddPaginationRules<T>(this AbstractValidator<T> validator) where T : IPagedQuery
{
validator.RuleFor(x => x.Page).GreaterThan(0);
validator.RuleFor(x => x.PageSize).InclusiveBetween(1, 100);
}
}
Used in GetAllAuctionsValidator
:
public class GetAllAuctionsValidator : AbstractValidator<GetAllAuctionsQuery>
{
public GetAllAuctionsValidator() => this.AddPaginationRules();
}
Next β Behaviors and Middleware