Documentation
Every option
A complete reference for each option, with its default and the reason for it.
NexoOptions
The configuration builder AddNexo hands you.
| Member | Default | Description |
|---|---|---|
HandlerLifetime | Scoped | The lifetime of discovered handlers. Scoped matches the unit of work they write through. |
MediatorLifetime | Scoped | IMediator's lifetime. Singleton is only accepted with singleton handlers: a singleton mediator would resolve scoped handlers from the root scope, and registration fails rather than letting that happen. |
Services | — | The IServiceCollection, for registrations Nexo does not cover. |
services.AddNexo(nexo =>
{
// Handler discovery
nexo.AddHandlersFromAssemblyOf<Program>();
nexo.AddHandlersFromAssemblyOf<Program>(filter: t => !t.IsNested);
nexo.AddHandlersFromAssemblies(assemblyA, assemblyB);
nexo.AddHandlersFromAssemblies(filter: t => !t.IsNested, assemblyA, assemblyB);
nexo.AddHandler<OneSpecificHandler>();
nexo.AddGeneratedHandlers(); // generated at compile time for this assembly
// Pipelines (registering twice does not duplicate)
nexo.AddLoggingPipeline(logging => { /* ... */ });
nexo.AddTimeoutPipeline(); // for [MessageTimeout]
nexo.AddEnrichmentPipeline();
nexo.AddUnitOfWorkPipeline(uow => { /* ... */ });
nexo.AddPipeline<MyPipeline>();
nexo.AddPipeline<MyPipeline>(ServiceLifetime.Singleton);
nexo.AddPipeline(sp => new MyPipeline(sp.GetRequiredService<IFoo>()));
nexo.AddPipeline(new PipelineWithNoDependencies());
// Mediator
nexo.ConfigureMediator(m => m.EventDispatchMode = EventDispatchMode.Parallel);
// The ambient user
nexo.UseCurrentUser<HttpCurrentUserAccessor>();
nexo.UseCurrentUser(sp => "system");
// The entities' public identifier (default: UUID v7)
nexo.UseExternalIdGenerator(ExternalIdGenerators.SqlServer);
// Other databases, outbox and inbox (Nexo.EntityFrameworkCore)
nexo.UseDbContext<BillingDbContext>();
nexo.UseDbContext<ActivityDbContext>(session => session.UseTransactions = false);
nexo.UseOutbox(outbox => outbox.PollingInterval = TimeSpan.FromSeconds(1));
nexo.UseInbox();
});MediatorOptions
| Option | Default | Description |
|---|---|---|
EventDispatchMode | Sequential | How an event's handlers run. Sequential is the default because they share the scope, and therefore the DbContext, which is not thread-safe. Parallel aggregates failures through Task.WhenAll. |
UnitOfWorkPipelineOptions
| Option | Default | Description |
|---|---|---|
SaveChangesOnCommand | true | Saves after the command handler returns. It is what lets the handler simply stop. |
SaveChangesOnEvent | true | Saves after an event published from outside. Events published from inside a command join its unit of work, regardless. |
UseTransaction | true | Wraps handler, save and events in one transaction, through the provider's execution strategy. Ignored on a provider without transactions. |
DispatchDomainEvents | true | Collects the events from the changed entities — before the save, so a removed entity's are not lost — and dispatches them after it, in the same transaction. Integration events go to the outbox, if there is one. |
MaxDomainEventRounds | 4 | How many cascade rounds are allowed. Exceeding it aborts the transaction instead of looping. |
WarnOnPendingQueryChanges | true | Logs a warning when a query handler leaves pending changes — nearly always a write that leaked. |
LoggingPipelineOptions
| Option | Default | Description |
|---|---|---|
Level | Debug | The level of the start and success entries. |
FailureLevel | Error | The level when the handler throws something unexpected. |
ExpectedFailureLevel | Information | The level for failures that are answers rather than incidents: validation, not found, a business rule, a duplicate key, a concurrency conflict, a requested cancellation. |
IsExpectedFailure | the list above | Decides which exceptions count as expected. |
LogPayload | false | Off on purpose. Commands carry passwords, tokens and personal data; a default that leaks that is a liability, not a feature. |
LogResult | false | Serialises the result. Rarely worth the volume. |
Serializer | System.Text.Json | How the payload becomes text. Replace it with a serialiser that redacts fields. |
Every dispatch opens a log scope with NexoMessageKind, NexoMessageName and NexoMessageId, so any line written inside the handler comes out correlated already.
ValidationPipelineOptions
| Option | Default | Description |
|---|---|---|
ValidateCommands | true | Commands change state. |
ValidateQueries | true | It keeps filters and paging sane. |
ValidateEvents | false | An event describes something that already happened; rejecting it after the fact rarely helps. |
FailIfValidatorNotFound | false | Turn it on when the team decides every command must have a validator and you want the container to enforce it. |
DbSessionOptions
One set per context. services.Configure<DbSessionOptions>(…) sets the default for every database; the callback on AddNexo<TContext> or UseDbContext<TContext> tunes one of them, starting from that default.
| Option | Default | Description |
|---|---|---|
SaveBehavior | UnitOfWork | When changes reach the database. Immediate saves on every operation: convenient in a script, but it gives up atomicity. |
TrackReads | true | Reads by key come back tracked. An injected IQueryable never tracks, regardless. |
SoftDelete | true | IDelete marks ISoftDeletable entities instead of removing them. |
ApplyAudit | true | Turns on the AuditInterceptor's stamps. |
AssignMissingExternalIds | true | Assigns an identifier on insert when the field is empty, using the generator from UseExternalIdGenerator. |
UseTransactions | null | null detects from the provider: relational yes, in-memory and MongoDB no. Set true for a MongoDB replica set. Without transactions, on a non-relational context, Nexo also turns off the provider's automatic transaction. |
// The default for every database.
services.Configure<DbSessionOptions>(session => session.TrackReads = true);
services.AddNexo<CatalogDbContext>(nexo => nexo
// Inherits TrackReads = true, overrides the rest.
.UseDbContext<ActivityDbContext>(session =>
{
session.UseTransactions = false;
session.SoftDelete = false;
}));
// Checking what actually applied, per database:
var sessions = scope.ServiceProvider.GetRequiredService<DbSessionProvider>();
sessions.ForEntity<ActivityEntry>().Options.SoftDelete; // false
sessions.ForEntity<Product>().Options.SoftDelete; // trueOutboxOptions
Passed to nexo.UseOutbox(outbox => …). See Outbox.
| Option | Default | Description |
|---|---|---|
RunDispatcher | true | Registers the background service that delivers the events. Turn it off to call OutboxProcessor.ProcessAsync from a scheduler of your own. |
PollingInterval | 2 s | The gap between passes when nothing is pending. |
BatchSize | 50 | How many events one pass tries to deliver. |
MaxAttempts | 10 | After that the event stays in the table, with the last error, for a person to look at. |
RetryDelay / MaxRetryDelay | 5 s / 10 min | The exponential back-off between attempts. |
ClaimDuration | 1 min | How long one instance reserves an event it is delivering. |
SerializerOptions | JsonSerializerDefaults.General | How the event is written and read. |
A complete setup, everything explicit
// One AddDbContext per database. Nexo never registers the context for you.
builder.Services.AddDbContext<CatalogDbContext>((sp, options) => options
.UseSqlServer(
builder.Configuration.GetConnectionString("Catalog"),
sql => sql.EnableRetryOnFailure())
.UseNexo(sp));
builder.Services.AddDbContext<PurchasingDbContext>((sp, options) => options
.UseMySQL(
builder.Configuration.GetConnectionString("Purchasing")!,
mySql => mySql.EnableRetryOnFailure())
.UseNexo(sp));
builder.Services.AddDbContext<ActivityDbContext>((sp, options) => options
.UseMongoDB(builder.Configuration.GetConnectionString("Activity")!, "activity")
.UseNexo(sp));
builder.Services.AddNexo<CatalogDbContext>(
nexo =>
{
nexo.HandlerLifetime = ServiceLifetime.Scoped;
nexo.MediatorLifetime = ServiceLifetime.Scoped;
// The other databases, each with its own session options.
nexo.UseDbContext<PurchasingDbContext>();
nexo.UseDbContext<ActivityDbContext>(session =>
{
session.UseTransactions = false;
session.SoftDelete = false;
});
nexo.AddHandlersFromAssemblyOf<Program>();
nexo.AddValidatorsFromAssemblyOf<Program>();
nexo.UseCurrentUser<HttpCurrentUserAccessor>();
nexo.ConfigureMediator(mediator =>
mediator.EventDispatchMode = EventDispatchMode.Sequential);
nexo.AddPipeline<MetricsPipeline>(ServiceLifetime.Singleton);
},
// The session options for CatalogDbContext.
session =>
{
session.SaveBehavior = SaveBehavior.UnitOfWork;
session.TrackReads = true;
session.SoftDelete = true;
session.ApplyAudit = true;
});Configuring from appsettings
Every option goes through IOptions<T>, so it can come from configuration like anything else.
{
"Nexo": {
"UnitOfWork": {
"UseTransaction": true,
"MaxDomainEventRounds": 4
},
"Logging": {
"Level": "Information",
"LogPayload": false
}
}
}builder.Services.Configure<UnitOfWorkPipelineOptions>(
builder.Configuration.GetSection("Nexo:UnitOfWork"));
builder.Services.Configure<LoggingPipelineOptions>(
builder.Configuration.GetSection("Nexo:Logging"));