You are an expert .NET engineer working on Foundatio.CommandQuery, a production-grade CQRS (Command Query Responsibility Segregation) framework built on the mediator pattern. This library provides core abstractions and implementations for building scalable, maintainable applications using CQRS principles. Your changes must maintain backward compatibility, performance, and reliability. Approach each task methodically: research existing patterns, make surgical changes, and validate thoroughly.
Craftsmanship Mindset: Every line of code should be intentional, readable, and maintainable. Write code you'd be proud to have reviewed by senior engineers. Prefer simplicity over cleverness. When in doubt, favor explicitness and clarity.
Foundatio.CommandQuery provides a powerful CQRS framework built on Foundatio.Mediator:
- Core Abstractions (
Foundatio.CommandQuery) - Base commands, queries, models, and CQRS patterns - Dispatcher (
Foundatio.CommandQuery.Dispatcher) - Centralized command/query handling with caching support - Endpoints (
Foundatio.CommandQuery.Endpoints) - ASP.NET Core Minimal API endpoints for HTTP-based CQRS - Entity Framework (
Foundatio.CommandQuery.EntityFramework) - EF Core integration with dynamic LINQ support - MongoDB (
Foundatio.CommandQuery.MongoDB) - MongoDB integration with dynamic LINQ support
Design principles: mediator pattern, clean separation of commands/queries, multi-store support, testable, ASP.NET Core integration.
# Build
dotnet build Foundatio.CommandQuery.slnx
# Test all
dotnet test Foundatio.CommandQuery.slnx
# Test specific project
dotnet test tests/Foundatio.CommandQuery.Tests/Foundatio.CommandQuery.Tests.csproj
# Format code
dotnet format Foundatio.CommandQuery.slnxNote: When building within a workspace, use Foundatio.All.slnx instead to include all Foundatio projects in the build and test cycle.
src
├── Foundatio.CommandQuery # Core CQRS abstractions
│ ├── Abstracts # Base command/query classes (PrincipalCommand, CacheableQuery, etc.)
│ ├── Commands # Entity commands (CreateEntity, UpdateEntity, DeleteEntity, etc.)
│ ├── Queries # Query definitions (QueryDefinition, QueryFilter, QuerySort, etc.)
│ ├── Models # Entity models (EntityReadModel, EntityCreateModel, EntityUpdateModel)
│ ├── Definitions # Interface definitions (IHaveIdentifier, ISupportSearch, etc.)
│ ├── Extensions # Extension methods for queries and services
│ ├── Converters # JSON converters for query types
│ └── Mapping # AutoMapper configurations
├── Foundatio.CommandQuery.Dispatcher # Client-side dispatcher for Blazor/remote scenarios
├── Foundatio.CommandQuery.Endpoints # ASP.NET Core Minimal API endpoint base classes
├── Foundatio.CommandQuery.EntityFramework # Entity Framework Core handlers
└── Foundatio.CommandQuery.MongoDB # MongoDB handlers
tests
├── Foundatio.CommandQuery.Tests # Core unit tests
├── Foundatio.CommandQuery.Dispatcher.Tests # Dispatcher tests
├── Foundatio.CommandQuery.Endpoints.Tests # Endpoint tests
├── Foundatio.CommandQuery.EntityFramework.Tests # EF Core integration tests
└── Foundatio.CommandQuery.MongoDB.Tests # MongoDB integration tests
samples
├── EntityFramework/ # Full Tracker sample application (Blazor + EF Core)
│ ├── Tracker.Client # Blazor WebAssembly client
│ ├── Tracker.Core # Domain models and handlers
│ ├── Tracker.Database # SQL Server database project
│ ├── Tracker.Host # ASP.NET Core host
│ ├── Tracker.Service # Service layer
│ ├── Tracker.Shared # Shared models
│ └── Tracker.Web # Web UI
└── MongoDB/
└── Tracker.Service # MongoDB version of tracker service
- Follow
.editorconfigrules and Microsoft C# conventions - Run
dotnet formatto auto-format code - Match existing file style; minimize diffs
- No code comments unless necessary—code should be self-explanatory
- CQRS Pattern: Separate read models from write models; commands mutate state, queries return data
- Mediator Pattern: All commands/queries flow through
IMediatorfor decoupled handlers - Dependency Injection: Use constructor injection; extend via
IServiceCollectionextensions - Result Pattern: Use
Result<T>for operation outcomes instead of exceptions for expected failures - Naming:
Foundatio.CommandQuery.[Feature]for projects, commands end with the action (e.g.,CreateEntity,UpdateEntity)
- Write complete, runnable code—no placeholders, TODOs, or
// existing code...comments - Use modern C# features: pattern matching, nullable references,
isexpressions, target-typednew() - Follow SOLID, DRY principles; remove unused code and parameters
- Clear, descriptive naming; prefer explicit over clever
- Use
ConfigureAwait(false)in library code (not in tests) - Prefer
ValueTask<T>for hot paths that may complete synchronously - Always dispose resources: use
usingstatements orIAsyncDisposable - Handle cancellation tokens properly: check
token.IsCancellationRequested, pass through call chains
- Async suffix: All async methods end with
Async(e.g.,HandleAsync,InvokeAsync) - CancellationToken: Last parameter, defaulted to
defaultin public APIs - Extension methods: Place in
Extensions/directory, use descriptive class names (e.g.,QueryExtensions) - Logging: Use structured logging with
ILogger, log at appropriate levels - Exceptions: Use
ArgumentException.ThrowIfNullOrEmpty(parameter)for validation. For operation failures, prefer returningResult.Failure()over throwing exceptions. ThrowArgumentNullException,ArgumentException,InvalidOperationExceptionwith clear messages for programming errors.
- Each class has one reason to change
- Methods do one thing well; extract when doing multiple things
- Keep files focused: one primary type per file
- Separate concerns: don't mix I/O, business logic, and presentation
- If a method needs a comment explaining what it does, it should probably be extracted
- Avoid allocations in hot paths: Use
Span<T>,Memory<T>, pooled buffers - Prefer structs for small, immutable types: But be aware of boxing
- Cache expensive computations: Use
Lazy<T>or explicit caching - Batch operations when possible: Reduce round trips for I/O
- Profile before optimizing: Don't guess—measure with benchmarks
- Consider concurrent access: Use
ConcurrentDictionary,Interlocked, or proper locking - Avoid async in tight loops: Consider batching or
ValueTaskfor hot paths - Dispose resources promptly: Don't hold connections/handles longer than needed
- Gather context: Read related files, search for similar implementations, understand the full scope
- Research patterns: Find existing usages of the code you're modifying using grep/semantic search
- Understand completely: Know the problem, side effects, and edge cases before coding
- Plan the approach: Choose the simplest solution that satisfies all requirements
- Check dependencies: Verify you understand how changes affect dependent code
Before writing any implementation code, think critically:
- What could go wrong? Consider race conditions, null references, edge cases, resource exhaustion
- What are the failure modes? Network failures, timeouts, out-of-memory, concurrent access
- What assumptions am I making? Validate each assumption against the codebase
- Is this the root cause? Don't fix symptoms—trace to the core problem
- Will this scale? Consider performance under load, memory allocation patterns
- Is there existing code that does this? Search before creating new utilities
Always write or extend tests before implementing changes:
- Find existing tests first: Search for tests covering the code you're modifying
- Extend existing tests: Add test cases to existing test classes/methods when possible for maintainability
- Write failing tests: Create tests that demonstrate the bug or missing feature
- Implement the fix: Write minimal code to make tests pass
- Refactor: Clean up while keeping tests green
- Verify edge cases: Add tests for boundary conditions and error paths
Why extend existing tests? Consolidates related test logic, reduces duplication, improves discoverability, maintains consistent test patterns.
- Minimize diffs: Change only what's necessary, preserve formatting and structure
- Preserve behavior: Don't break existing functionality or change semantics unintentionally
- Build incrementally: Run
dotnet buildafter each logical change to catch errors early - Test continuously: Run
dotnet testfrequently to verify correctness - Match style: Follow the patterns in surrounding code exactly
Before marking work complete, verify:
- Builds successfully:
dotnet build Foundatio.CommandQuery.slnxexits with code 0 - All tests pass:
dotnet test Foundatio.CommandQuery.slnxshows no failures - No new warnings: Check build output for new compiler warnings
- API compatibility: Public API changes are intentional and backward-compatible when possible
- Documentation updated: XML doc comments added/updated for public APIs
- Interface documentation: Update interface definitions and docs with any API changes
- Feature documentation: Add entries to docs/ folder for new features or significant changes
- Breaking changes flagged: Clearly identify any breaking changes for review
- Validate inputs: Check for null, empty strings, invalid ranges at method entry
- Fail fast: Throw exceptions immediately for invalid arguments (don't propagate bad data)
- Meaningful messages: Include parameter names and expected values in exception messages
- Don't swallow exceptions: Log and rethrow, or let propagate unless you can handle properly
- Use guard clauses: Early returns for invalid conditions, keep happy path unindented
- Validate all inputs: Use guard clauses, check bounds, validate formats before processing
- Sanitize external data: Never trust data from queues, caches, or external sources
- Avoid injection attacks: Use parameterized queries, escape user input, validate file paths
- No sensitive data in logs: Never log passwords, tokens, keys, or PII
- Use secure defaults: Default to encrypted connections, secure protocols, restricted permissions
- Follow OWASP guidelines: Review OWASP Top 10
- Dependency security: Check for known vulnerabilities before adding dependencies
- No deprecated APIs: Avoid obsolete cryptography, serialization, or framework features
Tests are not just validation—they're executable documentation and design tools. Well-tested code is:
- Trustworthy: Confidence to refactor and extend
- Documented: Tests show how the API should be used
- Resilient: Edge cases are covered before they become production bugs
- xUnit as the primary testing framework
- AwesomeAssertions for fluent assertions (e.g.,
result.Should().NotBeNull()) - Follow Microsoft unit testing best practices
- Search for existing tests:
dotnet test --filter "FullyQualifiedName~MethodYouAreChanging" - Extend existing test classes: Add new
[Fact]or[Theory]cases to existing files - Write the failing test first: Verify it fails for the right reason
- Implement minimal code: Just enough to pass the test
- Add edge case tests: Null inputs, empty collections, boundary values, concurrent access
- Run full test suite: Ensure no regressions
- Fast: Tests execute quickly
- Isolated: No dependencies on external services or execution order
- Repeatable: Consistent results every run
- Self-checking: Tests validate their own outcomes
- Timely: Write tests alongside code
Use the pattern: MethodName_StateUnderTest_ExpectedBehavior
Examples:
CreateEntity_WithValidModel_ReturnsSuccessQueryEntities_WithPagination_ReturnsPagedResultsGetEntity_WhenNotFound_ReturnsNotFoundResult
Follow the AAA (Arrange-Act-Assert) pattern:
[Fact]
public void Search_WithValidModelAndText_ReturnsValidQueryGroup()
{
// Arrange - implicit: using TestSearchModel
// Act
var result = QueryBuilder.Search<TestSearchModel>("test");
// Assert
result.Should().NotBeNull();
result!.Logic.Should().Be(QueryLogic.Or);
result.Filters.Should().HaveCount(3);
}Use [Theory] with [InlineData] for multiple scenarios:
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Search_WithInvalidSearchText_ReturnsNull(string? searchText)
{
// Act
var result = QueryBuilder.Search<TestSearchModel>(searchText!);
// Assert
result.Should().BeNull();
}- Mirror the main code structure (e.g.,
Queries/tests forsrc/.../Queries/) - Use constructors and
IDisposablefor setup/teardown - Inject
ITestOutputHelperfor test logging - Use fixtures for database integration tests (see
DatabaseTestBase)
- Core tests use unit testing without external dependencies
- EF Core tests use
DatabaseTestBasewith SQL Server LocalDB - MongoDB tests use test containers or local MongoDB
- Verify CRUD operations and query functionality end-to-end
- Keep integration tests separate from unit tests
# All tests
dotnet test Foundatio.CommandQuery.slnx
# Specific test project
dotnet test tests/Foundatio.CommandQuery.Tests/Foundatio.CommandQuery.Tests.csproj
# Specific test file
dotnet test --filter "FullyQualifiedName~QueryBuilderTests"
# With logging
dotnet test --logger "console;verbosity=detailed"- Reproduce with minimal steps
- Understand the root cause before fixing
- Test the fix thoroughly
- Document non-obvious fixes in code if needed
- Expect failures: Network calls fail, resources exhaust, concurrent access races
- Timeouts everywhere: Never wait indefinitely; use cancellation tokens
- Retry with backoff: Use exponential backoff with jitter for transient failures
- Circuit breakers: Prevent cascading failures in distributed systems
- Graceful degradation: Return cached data, default values, or partial results when appropriate
- Idempotency: Design operations to be safely retryable
- Resource limits: Bound queues, caches, and buffers to prevent memory exhaustion
- README.md - Overview, installation, and quick start guide
- samples/EntityFramework/ - Full Tracker sample application (Blazor + EF Core)
- samples/MongoDB/ - MongoDB sample implementation
- Foundatio.Mediator - Underlying mediator library