Softalleys.Utilities.Queries
1.0.1
See the version list below for details.
dotnet add package Softalleys.Utilities.Queries --version 1.0.1
NuGet\Install-Package Softalleys.Utilities.Queries -Version 1.0.1
<PackageReference Include="Softalleys.Utilities.Queries" Version="1.0.1" />
<PackageVersion Include="Softalleys.Utilities.Queries" Version="1.0.1" />
<PackageReference Include="Softalleys.Utilities.Queries" />
paket add Softalleys.Utilities.Queries --version 1.0.1
#r "nuget: Softalleys.Utilities.Queries, 1.0.1"
#:package Softalleys.Utilities.Queries@1.0.1
#addin nuget:?package=Softalleys.Utilities.Queries&version=1.0.1
#tool nuget:?package=Softalleys.Utilities.Queries&version=1.0.1
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.0");
}
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.
Packaging and publishing
This project is multi-targeted for .NET 8 and .NET 9 and includes conditional dependencies. It also ships a .nuspec
file if you prefer packing from nuspec. Typical flows:
Option A: pack via the project file
dotnet pack .\Softalleys.Utilities.Queries\Softalleys.Utilities.Queries.csproj -c Release
Option B: pack via the nuspec (requires NuGet CLI)
nuget pack .\Softalleys.Utilities.Queries\Softalleys.Utilities.Queries.nuspec -Properties Configuration=Release
Then push:
dotnet nuget push .\Softalleys.Utilities.Queries\bin\Release\Softalleys.Utilities.Queries.*.nupkg --source https://api.nuget.org/v3/index.json --api-key <YOUR_KEY>
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. |
-
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.