Dependency Injection
Nexum integrates with Microsoft.Extensions.DependencyInjection via the Nexum.Extensions.DependencyInjection package. The entry point is a single extension method: AddNexum().
AddNexum signatures
// Source generator auto-discovery (when Nexum.SourceGenerators is installed)
services.AddNexum();
// Explicit assembly scanning (no source generator)
services.AddNexum(assemblies: typeof(CreateOrderHandler).Assembly);
// With options
services.AddNexum(options =>
{
options.DefaultPublishStrategy = PublishStrategy.Sequential;
options.MaxDispatchDepth = 16;
});
// Options + assemblies together
services.AddNexum(
configure: opts => opts.MaxDispatchDepth = 32,
assemblies: typeof(CreateOrderHandler).Assembly,
typeof(SendOrderConfirmationEmail).Assembly);
Full declaration:
public static IServiceCollection AddNexum(
this IServiceCollection services,
Action<NexumOptions>? configure = null,
params Assembly[] assemblies);
NexumOptions
| Option | Default | Description |
|---|---|---|
DefaultPublishStrategy |
Sequential |
Strategy used when PublishAsync is called without an override. |
MaxDispatchDepth |
16 |
Maximum nested dispatch depth (re-entrancy guard). |
FireAndForgetChannelCapacity |
1024 |
Bounded channel size for the FireAndForget publish strategy. |
Handler lifetimes
By default, handlers are registered as Scoped — one instance per DI scope (e.g., per HTTP request). Override per handler with [HandlerLifetime]:
[CommandHandler]
[HandlerLifetime(NexumLifetime.Singleton)]
public sealed class WarmupHandler : ICommandHandler<WarmupCommand, Unit> { ... }
[CommandHandler]
[HandlerLifetime(NexumLifetime.Transient)]
public sealed class PerCallHandler : ICommandHandler<PerCallCommand, Unit> { ... }
NexumLifetime is Nexum's own enum (zero dependencies on Microsoft.Extensions.DependencyInjection.Abstractions at the abstraction layer). It maps to the standard MSDI ServiceLifetime inside the DI extension package.
Dispatcher lifetime
All three dispatchers (ICommandDispatcher, IQueryDispatcher, INotificationPublisher) are registered as Singleton. They are thread-safe, their internal caches are ConcurrentDictionary, and they resolve scoped handlers from a IServiceScopeFactory on each dispatch.
Manual registration
If you don't want to use either the Source Generator or assembly scanning, you can register handlers manually:
services.AddNexumCore(); // registers dispatchers only
services.AddScoped<ICommandHandler<CreateOrderCommand, Guid>, CreateOrderHandler>();
services.AddScoped<IQueryHandler<GetOrderQuery, OrderDto?>, GetOrderQueryHandler>();
This path is useful for tightly controlled composition roots (e.g., AOT scenarios where you want full visibility into what gets registered).