Genocs.WebApi.CQRS Agent Reference
`Genocs.WebApi.CQRS` wires the CQRS abstractions from `Genocs.Common` (`ICommand`, `IQuery
Genocs.WebApi.CQRS β Agent Reference Documentation
Format: AI-optimized agent reference. Structured for rapid decision-making. All capability claims are linked to actual source files. Do not infer capabilities not listed here.
1. Purpose
Genocs.WebApi.CQRS wires the CQRS abstractions from Genocs.Common (ICommand, IQuery<T>, IEvent)
into the Genocs.WebApi endpoint-builder DSL. It provides:
IDispatcher/InMemoryDispatcherβ a single faΓ§ade that delegates to the three dispatchers (ICommandDispatcher,IEventDispatcher,IQueryDispatcher).IDispatcherEndpointsBuilder/DispatcherEndpointsBuilderβ a typed alternative toIEndpointsBuilderthat auto-dispatches bound commands and queries via the in-memory dispatcher.UseDispatcherEndpointsmiddleware helper β wires routing + auth + the dispatcher-flavoured endpoint DSL in one call.UsePublicContractsβ exposes a diagnostic JSON endpoint that lists all knownICommandandIEventtypes in the process (useful for tooling and contract testing).HttpContextextension methods βSendAsync<T>andQueryAsync<TResult>shortcuts.
Package ID: Genocs.WebApi.CQRS
NuGet config section: none (no config section; pure DI/middleware)
2. Quick Facts
| Property | Value |
|---|---|
| Target frameworks | net10.0, net9.0, net8.0 |
| Config section key | β (none) |
| Registration guard | none β AddInMemoryDispatcher is not idempotent |
| Dispatcher type | In-process / in-memory (delegates to injected dispatchers) |
| Public contracts endpoint | /_contracts (default, configurable) |
| Requires | Genocs.WebApi (which requires Genocs.Core, Genocs.Common) |
| Source file | src/Genocs.WebApi.CQRS/Extensions.cs |
3. Use When
- You want to register HTTP endpoints where the framework automatically dispatches bound request bodies as
ICommandorIQuery<T>objects β removing boilerplate dispatcher injection in every controller/handler. - You have
ICommandDispatcher,IEventDispatcher, andIQueryDispatcheralready registered (e.g. viaGenocs.CoreCQRS registration) and want a singleIDispatcherfaΓ§ade to inject. - You want to expose a
/_contractsendpoint that enumerates all commands and events in the assembly β useful for agentic tooling, contract testing, and documentation generation. - You want to mix typed CQRS endpoints (
Post<TCommand>,Get<TQuery, TResult>) with raw handler endpoints (Post(path, ctx => ...)) in the same endpoint builder chain.
4. Do Not Assume
AddInMemoryDispatcheris not idempotent. It callsAddSingleton<IDispatcher, InMemoryDispatcher>. Calling it twice registers two singletons; only the first will be resolved.InMemoryDispatcherdoes NOT provide its own command/query dispatchers. It delegates entirely toICommandDispatcher,IEventDispatcher,IQueryDispatcherβ those must be registered separately (e.g. viaGenocs.Core’s CQRS setup).UseDispatcherEndpointsreplacesUseEndpointsfromGenocs.WebApi. Do not call both; they both callapp.UseRouting()andapp.UseEndpoints(...)internally.UsePublicContractsreflects all loaded assemblies at app startup. Only types implementingICommandorIEventthat are visible toAppDomain.CurrentDomain.GetAssemblies()at that moment are indexed. Types loaded lazily after startup are NOT included.- Duplicate command/event names throw. If two types share the same
.Name(regardless of namespace),UsePublicContractsthrowsInvalidOperationExceptionon startup. attributeRequired = true(default) means only types decorated with theattributeTypepassed toUsePublicContractswill be included. PassattributeRequired: falseto include allICommand/IEventtypes unconditionally.DispatcherEndpointsBuilderwrapsIEndpointsBuilder; it’s not a standalone builder β it requires an existingIEndpointsBuilderinstance (obtained viaUseDispatcherEndpointsorendpoints.Dispatch(...)).- The
beforeDispatch/afterDispatchhooks in typed endpoints are synchronous with the dispatch pipeline. Exceptions thrown in hooks will propagate up.
5. High-Value Entry Points
Extensions.cs β AddInMemoryDispatcher(IGenocsBuilder)
Extensions.cs β UseDispatcherEndpoints(IApplicationBuilder, Action<IDispatcherEndpointsBuilder>, ...)
Extensions.cs β Dispatch(IEndpointsBuilder, Func<IDispatcherEndpointsBuilder, ...>)
Extensions.cs β UsePublicContracts<T>(IApplicationBuilder, endpoint)
Extensions.cs β UsePublicContracts(IApplicationBuilder, bool attributeRequired, endpoint)
Extensions.cs β SendAsync<T>(HttpContext, T command)
Extensions.cs β QueryAsync<TResult>(HttpContext, IQuery<TResult>)
IDispatcher.cs β IDispatcher β unified faΓ§ade interface
InMemoryDispatcher.cs β InMemoryDispatcher β singleton implementation
IDispatcherEndpointsBuilder.cs β IDispatcherEndpointsBuilder
Builders/DispatcherEndpointsBuilder.cs β DispatcherEndpointsBuilder
Middlewares/PublicContractsMiddleware.cs β PublicContractsMiddleware
6. Decision Matrix
| Goal | API to use |
|---|---|
| Register the unified IDispatcher | builder.AddInMemoryDispatcher() |
| Add CQRS-dispatching HTTP endpoints | app.UseDispatcherEndpoints(endpoints => endpoints.Post<MyCmd>("/cmd").Get<MyQuery, Result>("/q")) |
| Add CQRS endpoints in an existing endpoint chain | endpoints.Dispatch(b => b.Post<MyCmd>("/cmd")) |
Expose command/event contract list at /_contracts | app.UsePublicContracts<PublicContractAttribute>() |
| Include all commands/events (no attribute filter) | app.UsePublicContracts(attributeRequired: false) |
| Dispatch command from HttpContext | await context.SendAsync(myCommand) |
| Dispatch query from HttpContext | var result = await context.QueryAsync(myQuery) |
| Intercept command before/after dispatch | Use beforeDispatch / afterDispatch parameters in typed endpoint methods |
7. Minimal Integration Recipe
7.1 Program.cs β Registration
IGenocsBuilder gnxBuilder = builder
.AddGenocs()
.AddWebApi()
.AddInMemoryDispatcher(); // registers IDispatcher singleton
gnxBuilder.Build();
Note: ICommandDispatcher, IQueryDispatcher, IEventDispatcher must be registered independently
(e.g. via Genocs.Core’s AddCommandHandlers() / AddQueryHandlers() or your own registrations).
7.2 Program.cs β Middleware (CQRS endpoint builder)
app.UseDispatcherEndpoints(endpoints => endpoints
.Post<CreateOrderCommand>("/orders", auth: true)
.Get<GetOrderQuery, OrderDto>("/orders/{id}", auth: true)
.Delete<CancelOrderCommand>("/orders/{id}", auth: true));
7.3 Mixing with raw endpoints
app.UseDispatcherEndpoints(endpoints => endpoints
.Get("/health", ctx => { ctx.Response.StatusCode = 200; return Task.CompletedTask; })
.Post<CreateOrderCommand>("/orders"));
7.4 Public contracts
// Only exposes types marked [PublicContract]
app.UsePublicContracts<PublicContractAttribute>();
// Exposes all ICommand / IEvent types
app.UsePublicContracts(attributeRequired: false, endpoint: "/_contracts");
7.5 Inject IDispatcher directly
public class MyService(IDispatcher dispatcher)
{
public Task Handle(MyCommand cmd) => dispatcher.SendAsync(cmd);
}
8. Behavior Notes
BuildCommandContext<T>(private helper inDispatcherEndpointsBuilder): resolvesICommandDispatcherfromHttpContext.RequestServicesat request time, then callsbeforeDispatchβSendAsyncβafterDispatch. IfICommandDispatcheris null (not registered), the dispatch is silently skipped β no exception is thrown.- Typed
Get<TQuery, TResult>: if noafterDispatchis provided and the query result is null, responds with HTTP 404 automatically. If the result is non-null, serializes and writes it viactx.Response.WriteJsonAsync(result). PublicContractsMiddlewareinitialises contracts exactly once viaInterlocked.Exchangeβ thread-safe, first-call wins. The static_serializedContractsstring is computed once and reused for all subsequent requests.PublicContractsMiddlewarecontract serialization usesSystem.Text.Jsonwith camelCase property name policy andJsonStringEnumConverter(camelCase) β the output is always prettified JSON.UsePublicContractsdefaults: endpoint ="/_contracts",attributeType = typeof(PublicContractAttribute),attributeRequired = true.
9. Source-Accurate Capability Map
| Capability | Source Location |
|---|---|
IDispatcher interface | IDispatcher.cs |
InMemoryDispatcher | InMemoryDispatcher.cs |
AddInMemoryDispatcher | Extensions.cs |
UseDispatcherEndpoints | Extensions.cs |
Dispatch (inline DSL) | Extensions.cs |
UsePublicContracts | Extensions.cs |
SendAsync / QueryAsync (HttpContext) | Extensions.cs |
IDispatcherEndpointsBuilder | IDispatcherEndpointsBuilder.cs |
DispatcherEndpointsBuilder | Builders/DispatcherEndpointsBuilder.cs |
PublicContractsMiddleware | Middlewares/PublicContractsMiddleware.cs |
10. Dependencies
| Dependency | Role |
|---|---|
Genocs.WebApi | Provides IEndpointsBuilder, WebApiEndpointDefinitions, IGenocsBuilder |
Genocs.Common | Provides ICommand, IQuery<T>, IEvent, ICommandDispatcher, IQueryDispatcher |
Genocs.Core | Provides IGenocsBuilder, IEventDispatcher |
Microsoft.AspNetCore.App (framework ref) | ASP.NET Core pipeline primitives |
11. Related Docs
- NuGet package readme: src/Genocs.WebApi.CQRS/README_NUGET.md
- Repository guide: README.md
- Package documentation: docs/Genocs.WebApi.CQRS-Agent-Documentation.md
- Related: Genocs.WebApi-Agent-Documentation.md β base endpoint DSL that
DispatcherEndpointsBuilderwraps - Related: Genocs.Core-Agent-Documentation.md β CQRS abstractions source (
ICommandDispatcher,IEventDispatcher)