Reference

Packages

flowchart LR
    SG["Send0xx.Dispatcher.SourceGeneration"] --> C["Send0xx.Dispatcher"]
    DI["Send0xx.Dispatcher.DependencyInjection"] --> C
    C --> A["Send0xx.Dispatcher.Abstractions"]
Package Contains
Send0xx.Dispatcher.Abstractions Messages, handlers, pipeline contracts, dispatcher interfaces, and Unit
Send0xx.Dispatcher Typed Microsoft DI registration extensions, and Dispatcher and telemetry options
Send0xx.Dispatcher.DependencyInjection The reflection-based implementation, registry, wrappers, handler scanning, pipelines, and telemetry runtime
Send0xx.Dispatcher.SourceGeneration The source-generation analyzer, referencing the shared runtime APIs for trimming and Native AOT
Important

Most applications should reference either Send0xx.Dispatcher.DependencyInjection or Send0xx.Dispatcher.SourceGeneration, not every package individually.

Package IDs use the Send0xx prefix. Core APIs remain in the Dispatcher namespace, while generator opt-in attributes and generated registration extensions use Dispatcher.SourceGeneration.

Current limitations

  • Notifications execute sequentially rather than concurrently.
  • Pipeline behaviors apply to queries and commands, not notifications.

Factory registrations

Registering a handler or behavior through a factory delegate is not recommended when it is also covered by scanning or a typed registration method. Duplicate registrations are otherwise detected and ignored, but Microsoft DI does not expose the type a factory returns, so a factory registration cannot be matched against another one. Both survive:

Registered twice Result
Notification handler Fires twice per publish
Pipeline behavior Runs twice per request
Query or command handler DuplicateHandlerException when the registry is created

Each factory below duplicates a registration made earlier in the same setup:

builder.Services
    .AddDispatcher()
    // Registers every query, command, and notification handler in the assembly,
    // including RecordOrderCreated and GetOrderQueryHandler. Behaviors are not scanned.
    .AddDispatcherHandlers(typeof(Program).Assembly);

builder.Services.AddPipelineBehavior(typeof(LoggingBehavior<,>));

// Not recommended. Scanning already registered RecordOrderCreated, but this
// descriptor reports no implementation type, so the duplicate goes undetected
// and the handler fires twice on every publish.
builder.Services.AddScoped<INotificationHandler<OrderCreated>>(
    _ => new RecordOrderCreated());

// Not recommended. The open generic behavior above already applies to
// GetOrderQuery, so this closes it a second time and it runs twice per request.
builder.Services.AddScoped<IPipelineBehavior<GetOrderQuery, Order?>>(
    _ => new LoggingBehavior<GetOrderQuery, Order?>());

// Not recommended. For a query or command handler this is a startup failure:
// creating the registry throws DuplicateHandlerException.
builder.Services.AddScoped<IQueryHandler<GetOrderQuery, Order?>>(
    provider => new GetOrderQueryHandler(provider.GetRequiredService<OrderStore>()));

Register by type instead, or as an instance. Both expose an implementation type, so a repeat of something already registered is detected and ignored:

builder.Services
    .AddDispatcher()
    // The same scan as above.
    .AddDispatcherHandlers(typeof(Program).Assembly);

// Repeating a scanned handler by type is detected and ignored.
builder.Services.AddNotificationHandler<OrderCreated, RecordOrderCreated>();
builder.Services.AddPipelineBehavior(typeof(LoggingBehavior<,>));

// Constructor dependencies come from the container, so no factory is needed.
builder.Services.AddScoped<OrderStore>();