Softalleys.Utilities.Queries
1.0.2
There is a newer version of this package available.
See the version list below for details.
See the version list below for details.
dotnet add package Softalleys.Utilities.Queries --version 1.0.2
NuGet\Install-Package Softalleys.Utilities.Queries -Version 1.0.2
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Softalleys.Utilities.Queries" Version="1.0.2" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Softalleys.Utilities.Queries" Version="1.0.2" />
<PackageReference Include="Softalleys.Utilities.Queries" />
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Softalleys.Utilities.Queries --version 1.0.2
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: Softalleys.Utilities.Queries, 1.0.2"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Softalleys.Utilities.Queries@1.0.2
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Softalleys.Utilities.Queries&version=1.0.2
#tool nuget:?package=Softalleys.Utilities.Queries&version=1.0.2
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
Softalleys.Utilities.Queries
Lightweight CQRS query dispatcher with proper DI lifetimes (Scoped by default, opt-in Singleton) and streaming support for .NET 8 and .NET 9.
Features
- Simple contracts:
IQuery<T>
,IQueryHandler<TQuery,TResponse>
,IQueryStreamHandler<TQuery,TResponse>
- Dispatcher:
IQueryDispatcher
(singleton) to route queries to handlers - DI-friendly lifetimes:
- Handlers are Scoped by default
- Opt-in Singleton via
IQuerySingletonHandler<TQuery,TResponse>
andIQueryStreamSingletonHandler<TQuery,TResponse>
- Assembly scanning DI extension:
services.AddSoftalleysQueries(params Assembly[] assemblies)
- Streaming queries return
IAsyncEnumerable<T>
and keep the scope alive during enumeration
Installation
Install via NuGet once published:
dotnet add package Softalleys.Utilities.Queries
Concepts
IQuery<TResponse>
: marker for a query that producesTResponse
.IQueryHandler<TQuery,TResponse>
: handles a single-result query.IQueryStreamHandler<TQuery,TResponse>
: handles a streaming query and returnsIAsyncEnumerable<TResponse>
viaStreamAsync
. Uses the sameIQuery<TResponse>
as the single-result handler.IQueryDispatcher
: dispatches queries to their registered handlers.- Optional lifetimes:
- Scoped (default):
IQueryHandler<,>
,IQueryStreamHandler<,>
- Singleton (opt-in): implement the marker
IQuerySingletonHandler<,>
orIQueryStreamSingletonHandler<,>
in addition to the base interface.
- Scoped (default):
Constraints and guidance:
- Register exactly one handler per query type. If more than one is registered, the last registration may win when resolving a single service.
- Streaming handlers should be careful to respect cancellation tokens.
Dependency Injection setup
Register services and scan assemblies that contain handlers:
using Softalleys.Utilities.Queries;
// Program.cs
builder.Services.AddSoftalleysQueries(typeof(SomeHandlerInThisAssembly).Assembly);
// Or scan multiple assemblies
builder.Services.AddSoftalleysQueries(
typeof(HandlerA).Assembly,
typeof(HandlerB).Assembly
);
// Later resolve IQueryDispatcher wherever you need it
public class MyController(IQueryDispatcher dispatcher) { /* ... */ }
The dispatcher creates a new DI scope per dispatch to respect Scoped dependencies. For stream queries, the scope is held for the entire enumeration.
Usage examples
Single-result query
public record GetUserById(Guid Id) : IQuery<UserDto>;
public class GetUserByIdHandler(MyDbContext db) : IQueryHandler<GetUserById, UserDto>
{
public async Task<UserDto> HandleAsync(GetUserById query, CancellationToken ct = default)
=> await db.Users
.Where(u => u.Id == query.Id)
.Select(u => new UserDto(u.Id, u.Name))
.SingleAsync(ct);
}
// Dispatch
var dto = await dispatcher.DispatchAsync(new GetUserById(id), ct);
Streaming query (same IQuery<T> as single)
public record GetNumbers(int Count) : IQuery<int>;
public class GetNumbersHandler : IQueryStreamHandler<GetNumbers, int>
{
public async IAsyncEnumerable<int> StreamAsync(GetNumbers query, [EnumeratorCancellation] CancellationToken ct = default)
{
for (var i = 0; i < query.Count; i++)
{
ct.ThrowIfCancellationRequested();
yield return i;
await Task.Yield();
}
}
}
// Dispatch
await foreach (var n in dispatcher.DispatchStreamAsync<int>(new GetNumbers(5), ct))
{
Console.WriteLine(n);
}
Opt-in Singleton handler
public record GetVersion() : IQuery<string>;
public class GetVersionHandler : IQuerySingletonHandler<GetVersion, string>
{
public Task<string> HandleAsync(GetVersion query, CancellationToken ct = default)
=> Task.FromResult("1.0.1");
}
Error handling
- If no handler is registered for a given query, the dispatcher throws
InvalidOperationException
. - It’s recommended to keep query handlers side-effect free. Use commands (not included here) for writes.
Testing tips
- Handlers are plain classes and easy to unit test in isolation.
- The dispatcher can be integration-tested by composing a minimal
ServiceCollection
, callingAddSoftalleysQueries
, and asserting behavior.
Objectives (design summary)
IQuery<T>
marker interfaceIQueryHandler<TQuery,TResponse>
andIQueryStreamHandler<TQuery,TResponse>
IQueryDispatcher
(singleton)- Scoped default lifetimes; opt-in singleton via marker interfaces
- DI assembly scanning via
AddSoftalleysQueries
- Streaming support with scope preserved over enumeration
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net8.0
-
net9.0
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.