A powerful Command Query Responsibility Segregation (CQRS) framework built on the mediator pattern. This library provides core abstractions and implementations for building scalable, maintainable applications using CQRS principles.
- Clean CQRS Implementation - Separate read and write operations with clear command/query patterns
- Mediator Pattern - Built on Foundatio.Mediator for decoupled messaging
- Multiple Data Stores - Support for Entity Framework Core and MongoDB out of the box
- ASP.NET Core Integration - Minimal API endpoints for exposing commands and queries via HTTP
- Caching Support - Built-in caching abstractions for query optimization
- Dynamic Queries - Powerful filtering, sorting, and pagination with LINQ support
- Multi-Targeting - Supports .NET 8, .NET 9, and .NET 10
# Core package
dotnet add package Foundatio.CommandQuery
# Entity Framework integration
dotnet add package Foundatio.CommandQuery.EntityFramework
# MongoDB integration
dotnet add package Foundatio.CommandQuery.MongoDB
# ASP.NET Core endpoints
dotnet add package Foundatio.CommandQuery.Endpoints
# Dispatcher for Blazor
dotnet add package Foundatio.CommandQuery.DispatcherInstall-Package Foundatio.CommandQuery
Install-Package Foundatio.CommandQuery.EntityFramework
Install-Package Foundatio.CommandQuery.MongoDB
Install-Package Foundatio.CommandQuery.Endpoints
Install-Package Foundatio.CommandQuery.Dispatcher// Entity model
public class Task : IHaveIdentifier<int>
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime Created { get; set; }
public string CreatedBy { get; set; }
}
// Read model
public class TaskReadModel : EntityReadModel<int>
{
public string Title { get; set; }
public string Description { get; set; }
}
// Create model
public class TaskCreateModel : EntityCreateModel<int>
{
public string Title { get; set; }
public string Description { get; set; }
}
// Update model
public class TaskUpdateModel : EntityUpdateModel
{
public string Title { get; set; }
public string Description { get; set; }
}using Foundatio.CommandQuery.EntityFramework;
public class TaskCommandHandler
: EntityCommandHandler<AppDbContext, Task, int, TaskReadModel, TaskCreateModel, TaskUpdateModel>
{
public TaskCommandHandler(AppDbContext context, IMapper mapper)
: base(context, mapper)
{
}
}
public class TaskQueryHandler
: EntityQueryHandler<AppDbContext, Task, int, TaskReadModel>
{
public TaskQueryHandler(AppDbContext context, IMapper mapper)
: base(context, mapper)
{
}
}var builder = WebApplication.CreateBuilder(args);
// Add mediator with assemblies containing handlers
builder.Services.AddMediator(options => options
.AddAssembly(typeof(Program).Assembly)
);
// Add your DbContext
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default"))
);
// Add endpoint routes
builder.Services.AddEndpointRoutes();public class TaskEndpoint
: EntityCommandEndpointBase<int, TaskListModel, TaskReadModel, TaskCreateModel, TaskUpdateModel>
{
public TaskEndpoint(ILoggerFactory loggerFactory)
: base(loggerFactory, "tasks", "api/tasks")
{
}
}
var app = builder.Build();
// Map endpoint routes
app.MapEndpointRoutes();
app.Run();This automatically creates the following endpoints:
GET /api/tasks/{id}- Get a task by IDGET /api/tasks- Query tasks with pagination and sortingPOST /api/tasks/query- Query tasks with advanced filteringGET /api/tasks/{id}/update- Get task update modelPOST /api/tasks- Create a new taskPUT /api/tasks/{id}- Update a taskDELETE /api/tasks/{id}- Delete a task
public class TaskService
{
private readonly IMediator _mediator;
public TaskService(IMediator mediator)
{
_mediator = mediator;
}
public async Task<Result<TaskReadModel>> CreateTaskAsync(TaskCreateModel model, ClaimsPrincipal user)
{
var command = new CreateEntity<TaskCreateModel, TaskReadModel>(user, model);
return await _mediator.InvokeAsync<Result<TaskReadModel>>(command);
}
public async Task<Result<TaskReadModel>> GetTaskAsync(int id, ClaimsPrincipal user)
{
var query = new GetEntity<int, TaskReadModel>(user, id);
return await _mediator.InvokeAsync<Result<TaskReadModel>>(query);
}
public async Task<Result<QueryResult<TaskReadModel>>> QueryTasksAsync(
QueryDefinition query,
ClaimsPrincipal user)
{
var command = new QueryEntities<TaskReadModel>(user, query);
return await _mediator.InvokeAsync<Result<QueryResult<TaskReadModel>>>(command);
}
}// Query with filtering, sorting, and pagination
var queryDefinition = new QueryDefinition
{
Filter = new QueryFilter
{
Logic = FilterLogic.And,
Filters = new List<QueryFilter>
{
new() { Field = "Status", Operator = FilterOperator.Equal, Value = "Active" },
new() { Field = "Priority", Operator = FilterOperator.GreaterThan, Value = 3 }
}
},
Sorts = new List<QuerySort>
{
new() { Name = "Priority", Descending = true },
new() { Name = "Created", Descending = false }
},
Page = 1,
PageSize = 20
};
var query = new QueryEntities<TaskReadModel>(user, queryDefinition);
var result = await _mediator.InvokeAsync<Result<QueryResult<TaskReadModel>>>(query);using Foundatio.CommandQuery.MongoDB;
public class TaskCommandHandler
: EntityCommandHandler<Task, int, TaskReadModel, TaskCreateModel, TaskUpdateModel>
{
public TaskCommandHandler(IMongoDatabase database, IMapper mapper)
: base(database, mapper)
{
}
}
public class TaskQueryHandler
: EntityQueryHandler<Task, int, TaskReadModel>
{
public TaskQueryHandler(IMongoDatabase database, IMapper mapper)
: base(database, mapper)
{
}
}public record ProcessTaskCommand : PrincipalCommand<CompleteModel>
{
public ProcessTaskCommand(ClaimsPrincipal? principal, string action)
: base(principal)
{
Action = action;
}
public string Action { get; }
}
public class ProcessTaskHandler : IRequestHandler<ProcessTaskCommand, CompleteModel>
{
public async ValueTask<CompleteModel> HandleAsync(
ProcessTaskCommand request,
CancellationToken cancellationToken = default)
{
// Custom processing logic
return CompleteModel.Success($"Processed: {request.Action}");
}
}Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.