Genocs.WebApi.CQRS Agent Reference

`Genocs.WebApi.CQRS` wires the CQRS abstractions from `Genocs.Common` (`ICommand`, `IQuery`, `IEv

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 to IEndpointsBuilder that auto-dispatches bound commands and queries via the in-memory dispatcher.
  • UseDispatcherEndpoints middleware helper β€” wires routing + auth + the dispatcher-flavoured endpoint DSL in one call.
  • UsePublicContracts β€” exposes a diagnostic JSON endpoint that lists all known ICommand and IEvent types in the process (useful for tooling and contract testing).
  • HttpContext extension methods β€” SendAsync<T> and QueryAsync<TResult> shortcuts.

Package ID: Genocs.WebApi.CQRS
NuGet config section: none (no config section; pure DI/middleware)


2. Quick Facts

PropertyValue
Target frameworksnet10.0, net9.0, net8.0
Config section keyβ€” (none)
Registration guardnone β€” AddInMemoryDispatcher is not idempotent
Dispatcher typeIn-process / in-memory (delegates to injected dispatchers)
Public contracts endpoint/_contracts (default, configurable)
RequiresGenocs.WebApi (which requires Genocs.Core, Genocs.Common)
Source filesrc/Genocs.WebApi.CQRS/Extensions.cs

3. Use When

  • You want to register HTTP endpoints where the framework automatically dispatches bound request bodies as ICommand or IQuery<T> objects β€” removing boilerplate dispatcher injection in every controller/handler.
  • You have ICommandDispatcher, IEventDispatcher, and IQueryDispatcher already registered (e.g. via Genocs.Core CQRS registration) and want a single IDispatcher faΓ§ade to inject.
  • You want to expose a /_contracts endpoint 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

  • AddInMemoryDispatcher is not idempotent. It calls AddSingleton<IDispatcher, InMemoryDispatcher>. Calling it twice registers two singletons; only the first will be resolved.
  • InMemoryDispatcher does NOT provide its own command/query dispatchers. It delegates entirely to ICommandDispatcher, IEventDispatcher, IQueryDispatcher β€” those must be registered separately (e.g. via Genocs.Core’s CQRS setup).
  • UseDispatcherEndpoints replaces UseEndpoints from Genocs.WebApi. Do not call both; they both call app.UseRouting() and app.UseEndpoints(...) internally.
  • UsePublicContracts reflects all loaded assemblies at app startup. Only types implementing ICommand or IEvent that are visible to AppDomain.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), UsePublicContracts throws InvalidOperationException on startup.
  • attributeRequired = true (default) means only types decorated with the attributeType passed to UsePublicContracts will be included. Pass attributeRequired: false to include all ICommand/IEvent types unconditionally.
  • DispatcherEndpointsBuilder wraps IEndpointsBuilder; it’s not a standalone builder β€” it requires an existing IEndpointsBuilder instance (obtained via UseDispatcherEndpoints or endpoints.Dispatch(...)).
  • The beforeDispatch / afterDispatch hooks 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

GoalAPI to use
Register the unified IDispatcherbuilder.AddInMemoryDispatcher()
Add CQRS-dispatching HTTP endpointsapp.UseDispatcherEndpoints(endpoints => endpoints.Post<MyCmd>("/cmd").Get<MyQuery, Result>("/q"))
Add CQRS endpoints in an existing endpoint chainendpoints.Dispatch(b => b.Post<MyCmd>("/cmd"))
Expose command/event contract list at /_contractsapp.UsePublicContracts<PublicContractAttribute>()
Include all commands/events (no attribute filter)app.UsePublicContracts(attributeRequired: false)
Dispatch command from HttpContextawait context.SendAsync(myCommand)
Dispatch query from HttpContextvar result = await context.QueryAsync(myQuery)
Intercept command before/after dispatchUse 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 in DispatcherEndpointsBuilder): resolves ICommandDispatcher from HttpContext.RequestServices at request time, then calls beforeDispatch β†’ SendAsync β†’ afterDispatch. If ICommandDispatcher is null (not registered), the dispatch is silently skipped β€” no exception is thrown.
  • Typed Get<TQuery, TResult>: if no afterDispatch is provided and the query result is null, responds with HTTP 404 automatically. If the result is non-null, serializes and writes it via ctx.Response.WriteJsonAsync(result).
  • PublicContractsMiddleware initialises contracts exactly once via Interlocked.Exchange β€” thread-safe, first-call wins. The static _serializedContracts string is computed once and reused for all subsequent requests.
  • PublicContractsMiddleware contract serialization uses System.Text.Json with camelCase property name policy and JsonStringEnumConverter (camelCase) β€” the output is always prettified JSON.
  • UsePublicContracts defaults: endpoint = "/_contracts", attributeType = typeof(PublicContractAttribute), attributeRequired = true.

9. Source-Accurate Capability Map

CapabilitySource Location
IDispatcher interfaceIDispatcher.cs
InMemoryDispatcherInMemoryDispatcher.cs
AddInMemoryDispatcherExtensions.cs
UseDispatcherEndpointsExtensions.cs
Dispatch (inline DSL)Extensions.cs
UsePublicContractsExtensions.cs
SendAsync / QueryAsync (HttpContext)Extensions.cs
IDispatcherEndpointsBuilderIDispatcherEndpointsBuilder.cs
DispatcherEndpointsBuilderBuilders/DispatcherEndpointsBuilder.cs
PublicContractsMiddlewareMiddlewares/PublicContractsMiddleware.cs

10. Dependencies

DependencyRole
Genocs.WebApiProvides IEndpointsBuilder, WebApiEndpointDefinitions, IGenocsBuilder
Genocs.CommonProvides ICommand, IQuery<T>, IEvent, ICommandDispatcher, IQueryDispatcher
Genocs.CoreProvides IGenocsBuilder, IEventDispatcher
Microsoft.AspNetCore.App (framework ref)ASP.NET Core pipeline primitives