Basic Configuration
Introduction
Darker is the query-side counterpart to Brighter, implementing the Query Object pattern for CQRS (Command Query Responsibility Segregation) architectures. While Brighter handles commands and events that change state, Darker provides a pipeline for executing queries that read state. Together, they form a complete CQRS solution for .NET applications.
You use Darker when you want to separate the parameters of a query from the execution of that query, typically when you need to add cross-cutting concerns such as logging, retry policies, or circuit breakers to your query handling. For more information on CQRS patterns and how Brighter and Darker work together, see CQRS with Brighter and Darker.
Prerequisites
.NET Version Requirements
Darker supports .NET 8.0 and later versions. The sample code in this documentation is compatible with both .NET 8.0 and .NET 9.0.
NuGet Packages
You will need the following NuGet packages to use Darker:
Paramore.Darker - Core library providing the
IQueryProcessorand query handler infrastructureParamore.Darker.AspNetCore - ASP.NET Core integration providing the
AddDarkerextension method for dependency injectionParamore.Darker.QueryLogging - Optional package for JSON-based query logging
Paramore.Darker.Policies - Optional package for integrating Polly resilience policies (retry, circuit breaker, fallback)
Install the packages using the .NET CLI:
dotnet add package Paramore.Darker
dotnet add package Paramore.Darker.AspNetCore
dotnet add package Paramore.Darker.QueryLogging
dotnet add package Paramore.Darker.PoliciesOr using Package Manager Console:
Quick Start with ASP.NET Core
Basic Setup
To configure Darker in an ASP.NET Core application, use the AddDarker extension method in your Program.cs or Startup.cs file. Darker integrates with the ASP.NET Core dependency injection container, making the IQueryProcessor available for injection into your controllers and endpoints.
The AddHandlersFromAssemblies method scans the specified assembly for query handlers and registers them automatically. This is the recommended approach for registering handlers.
Minimal API Example
Here's a complete example of using Darker with ASP.NET Core Minimal APIs:
In this example, IQueryProcessor is injected directly into the endpoint handlers. The query processor dispatches the query to the appropriate query handler.
MVC Controller Example
If you're using MVC controllers, inject IQueryProcessor into your controller's constructor:
Working examples can be found in the Darker samples: Darker/samples/SampleMinimalApi/
Configuration Options
Query Processor Lifetime
By default, the IQueryProcessor is registered with a Transient lifetime, meaning a new instance is created each time it's requested. However, if you're using Entity Framework Core, you need to register the Query Processor with a Scoped lifetime to match the EF Core DbContext lifetime.
Default Configuration (Transient):
Scoped Configuration (Required for EF Core):
When using Entity Framework Core, the DbContext is registered as scoped by default. To ensure Darker works correctly with EF Core, you must configure the Query Processor to use the same scoped lifetime:
If you don't configure the scoped lifetime when using EF Core, you may encounter exceptions related to accessing a disposed DbContext.
Handler Registration Strategies
Darker provides two ways to register query handlers: automatic assembly scanning (recommended) and manual registration.
Assembly Scanning (Recommended):
The AddHandlersFromAssemblies method scans one or more assemblies and automatically registers all query handlers it finds:
This approach follows convention over configuration and is the easiest way to register handlers.
Manual Registration:
For more control over handler registration, you can use QueryHandlerRegistry to register handlers explicitly:
Manual registration is useful when you need fine-grained control over which handlers are registered or when you're not using ASP.NET Core's dependency injection.
Using IQueryProcessor
The IQueryProcessor is the central interface for executing queries in Darker. Once configured, you inject it into your controllers, endpoints, or services to dispatch queries to their handlers.
ExecuteAsync Pattern (Recommended)
The asynchronous ExecuteAsync method is recommended for most scenarios, especially when your query involves I/O operations like database access:
Always pass the CancellationToken to ExecuteAsync when available. This allows query execution to be cancelled if the request is aborted, improving application responsiveness and resource usage.
Execute Pattern (Synchronous)
For synchronous query execution (rare cases where async is not needed), use the Execute method:
Use the synchronous Execute method only when:
Your query handler performs purely in-memory operations
You're working in a synchronous context that cannot be made async
You have a specific performance requirement for synchronous execution
For most modern applications, prefer ExecuteAsync.
Configuration with Decorators
Darker supports decorators (middleware) that wrap query handlers to provide cross-cutting concerns like logging and resilience policies. You can add decorators during configuration.
Adding Query Logging
The AddJsonQueryLogging extension adds a decorator that logs query execution details in JSON format:
This will log:
The query type and parameters
Execution time
Query results (summary)
Query logging is useful for debugging and monitoring query execution in your application. For more details on using the logging decorator in handlers, see Query Pipeline.
Adding Policies
Darker integrates with Polly to provide resilience and transient fault handling through policies like retry, circuit breaker, and fallback.
Using Default Policies:
The AddDefaultPolicies method adds a default set of policies:
Using Custom Policy Registry:
For more control, you can create a custom policy registry with specific retry strategies, circuit breakers, and timeout policies:
Once policies are configured, you can apply them to query handlers using attributes. For details on using policy decorators in handlers, see Query Pipeline.
Common Configuration Patterns
Pattern: Basic Web API Setup
This is the most common setup for an ASP.NET Core Web API using Darker:
Pattern: With EF Core DbContext
When using Entity Framework Core, you must configure the Query Processor with scoped lifetime to match the DbContext:
Without the scoped configuration, you'll encounter exceptions about disposed DbContext instances.
Pattern: Multiple Handler Assemblies
When your query handlers are spread across multiple assemblies, register all of them:
This pattern is useful in modular monoliths or when organizing queries by domain.
Troubleshooting
Common Issues
Handler not found errors
If you receive an exception that a handler cannot be found for a query:
Verify that the handler class implements
QueryHandler<TQuery, TResult>orQueryHandlerAsync<TQuery, TResult>Ensure the handler's assembly is registered with
AddHandlersFromAssembliesCheck that the query and handler types match exactly (including generic type parameters)
Verify the handler class is public and not abstract
Lifetime scope issues with EF Core
If you see exceptions about a disposed DbContext:
Ensure you've configured
QueryProcessorLifetime = ServiceLifetime.Scopedin the Darker optionsVerify your DbContext is registered with scoped lifetime (default for EF Core)
Check that you're not trying to use the query result after the scope has been disposed
Assembly scanning not finding handlers
If handlers aren't being registered automatically:
Verify you're passing the correct assembly to
AddHandlersFromAssembliesEnsure handlers are in the same assembly or you've registered all relevant assemblies
Check that handler classes are public and not nested within other classes
Verify handlers follow the naming conventions (end with "Handler" or "QueryHandler")
Policy not found errors
If you see exceptions about missing policies:
Ensure you've called
AddDefaultPolicies()orAddPolicies(registry)Verify the policy name in your attribute matches the name in the policy registry
Check that the policy registry is correctly configured before being passed to
AddPolicies
Further Reading
Queries and Query Objects - Learn how to design query objects
Implementing a Query Handler - Detailed guide to implementing query handlers
Query Pipeline - Understanding decorators, policies, and the query pipeline
CQRS with Brighter and Darker - Architectural patterns for CQRS
Last updated
Was this helpful?
