skills/microservice/query/query-handler/SKILL.md
Use when implementing MediatR query handlers with pagination, filtering, and DTO mapping.
npx skillsauth add faysilalshareef/dotnet-ai-kit query-handlerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
IRequestHandler<TQuery, TOutput> via MediatR_unitOfWork.Orders)PageSize, CurrentPage, Total, and collection propertiesFindAsync then map to output DTOOrderNotFoundException)namespace {Company}.{Domain}.Query.Application.Contracts.Repositories;
public interface IUnitOfWork : IDisposable
{
IOrderRepository Orders { get; }
IProductRepository Products { get; }
IOrderItemRepository OrderItems { get; }
ICategoryRepository Categories { get; }
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}
namespace {Company}.{Domain}.Query.Application.Contracts.Repositories;
public interface IAsyncRepository<TDomain> where TDomain : class
{
Task AddAsync(TDomain entity);
Task AddRangeAsync(IEnumerable<TDomain> entities);
Task RemoveAsync(TDomain entity);
Task RemoveRangeAsync(IEnumerable<TDomain> entities);
Task<TDomain?> FindAsync(Guid id, bool includeRelated = false);
Task<IEnumerable<TDomain>> GetAllAsync();
Task<IEnumerable<TResult>> GetAllAsync<TResult>(
Expression<Func<TDomain, TResult>> target);
IQueryable<TDomain> GetQueryable();
}
namespace {Company}.{Domain}.Query.Application.Contracts.Repositories;
public interface IOrderRepository : IAsyncRepository<Order>
{
Task<GetOrdersOutput> GetOrdersAsync(
GetOrdersQuery query,
CancellationToken cancellationToken);
}
namespace {Company}.{Domain}.Query.Application.Features.Queries;
public record GetOrdersQuery(
int PageSize,
int CurrentPage,
DateTime? CreatedFrom,
DateTime? CreatedTo,
OrderStatus? Status,
string? CustomerName
) : IRequest<GetOrdersOutput>;
namespace {Company}.{Domain}.Query.Application.Features.Queries;
public class GetOrdersOutput
{
public int PageSize { get; set; }
public int CurrentPage { get; set; }
public int Total { get; set; }
public List<OrderOutput> Orders { get; set; } = [];
}
namespace {Company}.{Domain}.Query.Application.Features.Queries;
public class OrderOutput
{
public Guid Id { get; set; }
public string CustomerName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public decimal Total { get; set; }
public OrderStatus Status { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
}
namespace {Company}.{Domain}.Query.Application.Features.Queries;
public class GetOrdersHandler(IUnitOfWork unitOfWork)
: IRequestHandler<GetOrdersQuery, GetOrdersOutput>
{
private readonly IUnitOfWork _unitOfWork = unitOfWork;
public async Task<GetOrdersOutput> Handle(
GetOrdersQuery query,
CancellationToken cancellationToken)
{
return await _unitOfWork.Orders
.GetOrdersAsync(query, cancellationToken);
}
}
namespace {Company}.{Domain}.Query.Application.Features.Queries;
public record GetOrderByIdQuery(Guid Id) : IRequest<OrderOutput>;
public class GetOrderByIdHandler(IUnitOfWork unitOfWork)
: IRequestHandler<GetOrderByIdQuery, OrderOutput>
{
private readonly IUnitOfWork _unitOfWork = unitOfWork;
public async Task<OrderOutput> Handle(
GetOrderByIdQuery query,
CancellationToken cancellationToken)
{
var order = await _unitOfWork.Orders.FindAsync(query.Id)
?? throw new OrderNotFoundException();
return new OrderOutput
{
Id = order.Id,
CustomerName = order.CustomerName,
Email = order.Email,
Total = order.Total,
Status = order.Status,
IsActive = order.IsActive,
CreatedAt = order.CreatedAt
};
}
}
public class GetOrderDetailsHandler(
IUnitOfWork unitOfWork,
IQueriesService queriesService)
: IRequestHandler<GetOrderDetailsQuery, OrderDetailsOutput>
{
private readonly IUnitOfWork _unitOfWork = unitOfWork;
private readonly IQueriesService _queriesService = queriesService;
public async Task<OrderDetailsOutput> Handle(
GetOrderDetailsQuery query,
CancellationToken cancellationToken)
{
var order = await _unitOfWork.Orders.FindAsync(query.Id, true)
?? throw new OrderNotFoundException();
var extraInfo = await _queriesService
.GetAdditionalInfo(order.ExternalId);
return new OrderDetailsOutput { /* map fields */ };
}
}
| Query Type | Pattern | Returns |
|---|---|---|
| List with pagination | Delegate to repository method | GetOrdersOutput with Total, PageSize, CurrentPage |
| Single by ID | FindAsync + map to output | Output DTO or throw NotFoundException |
| Single by field | Repository-specific method | Output DTO or throw NotFoundException |
| With external data | Additional service injection | Combined output |
| Anti-Pattern | Correct Approach |
|---|---|
| ApplicationDbContext injected directly | Use IUnitOfWork with named repositories |
| Paginated<T> generic wrapper | Output class with PageSize, CurrentPage, Total, collection |
| AsNoTracking() in handlers | Tracking handled at repository/infrastructure level |
| Filtering logic in handlers | Repository methods encapsulate all query logic |
| Returning entities from queries | Map to output DTOs in handler |
| record output types | class output types with { get; set; } properties |
# Find query handlers
grep -r "IRequestHandler<.*Query" --include="*.cs" Application/Features/Queries/
# Find IUnitOfWork usage in queries
grep -r "_unitOfWork\." --include="*.cs" Application/Features/Queries/
# Find output classes
grep -r "public class.*Output" --include="*.cs" Application/Features/Queries/
# Find repository interfaces with query methods
grep -r "Task<Get.*Output>" --include="*.cs" Application/Contracts/
FindAsync then map to output DTO in handlerclass with PageSize, CurrentPage, Total, and collectionrecord type with filter parameters as nullableApplication/Features/Queries/{Area}/{QueryName}/ directorydata-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.