PANiXiDA.Core.Infrastructure.Messaging.Wolverine
4.1.2
See the version list below for details.
dotnet add package PANiXiDA.Core.Infrastructure.Messaging.Wolverine --version 4.1.2
NuGet\Install-Package PANiXiDA.Core.Infrastructure.Messaging.Wolverine -Version 4.1.2
<PackageReference Include="PANiXiDA.Core.Infrastructure.Messaging.Wolverine" Version="4.1.2" />
<PackageVersion Include="PANiXiDA.Core.Infrastructure.Messaging.Wolverine" Version="4.1.2" />
<PackageReference Include="PANiXiDA.Core.Infrastructure.Messaging.Wolverine" />
paket add PANiXiDA.Core.Infrastructure.Messaging.Wolverine --version 4.1.2
#r "nuget: PANiXiDA.Core.Infrastructure.Messaging.Wolverine, 4.1.2"
#:package PANiXiDA.Core.Infrastructure.Messaging.Wolverine@4.1.2
#addin nuget:?package=PANiXiDA.Core.Infrastructure.Messaging.Wolverine&version=4.1.2
#tool nuget:?package=PANiXiDA.Core.Infrastructure.Messaging.Wolverine&version=4.1.2
PANiXiDA.Core.Infrastructure.Messaging.Wolverine
PANiXiDA.Core.Infrastructure.Messaging.Wolverine is a .NET library that connects PANiXiDA.Core application messaging abstractions to WolverineFx.
It provides an in-process mediator, in-process domain event publishing by default, optional Kafka topic routing for selected event types, and durable inbox/outbox support backed by PostgreSQL.
Status
Features
IMediatorimplementation based on Wolverine in-process invocation.IEventBusimplementation based on Wolverine EF Core outbox.ISchedulerimplementation for durable command and event scheduling through the same outbox.- Default in-process event handling for domain events.
- Explicit Kafka producer and consumer registration per event type.
- Durable inbox and outbox policies for listeners, local queues, and external senders.
- PostgreSQL message storage with EF Core transaction integration.
- One shared Wolverine runtime and message-store schema across multiple module DbContexts.
- Request-scoped routing to keyed module
IUnitOfWorkand EF Core outbox services. - FluentValidation validator registration from Wolverine discovery assemblies.
- Wolverine application assembly resolution from the entry assembly for pre-generated handler code.
- Runtime compilation support for Wolverine
TypeLoadMode.Auto.
Quick Start
Requirements
- .NET 10 SDK
- PostgreSQL for Wolverine message storage
- Kafka only when external event topics are registered
Full Native AOT support is not currently provided.
Installation
Use the latest 4.1.x version:
<ItemGroup>
<PackageReference Include="PANiXiDA.Core.Infrastructure.Messaging.Wolverine" Version="4.1.*" />
</ItemGroup>
Minimal Setup
using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.DependencyInjection;
builder.Services.AddWolverineMediator<AppDbContext>();
builder.Host.UseWolverineMediator<AppDbContext>(
builder.Configuration.GetConnectionString("PostgreSqlConnectionString")!,
typeof(CreateUserHandler).Assembly);
AddWolverineMediator<TDbContext>() registers PANiXiDA IMediator, IEventBus, IScheduler, and the EF Core outbox dispatcher.
UseWolverineMediator<TDbContext>() configures Wolverine, PostgreSQL message storage, EF Core transactions, request middleware, FluentValidation validators from discovery assemblies, durable local queues, durable inbox, and durable outbox.
The package sets Wolverine's application assembly to the process entry assembly. This keeps generated handler code in the publishable application assembly when using Wolverine code generation commands such as dotnet run -- codegen write.
The package uses Wolverine TypeLoadMode.Auto and includes Wolverine runtime compilation support for handler code that has not been pre-generated.
Modular Setup
Use the non-generic overload when a host contains multiple modules with independent write DbContexts:
using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.DependencyInjection;
builder.Host.UseWolverineMediator(
builder.Configuration.GetConnectionString("PostgreSqlConnectionString")!,
modules =>
{
modules.AddModule<IdentityWriteDbContext>(
typeof(CreateUserCommand).Assembly,
typeof(IdentityIntegrationEventHandler).Assembly);
modules.AddModule<OrdersWriteDbContext>(
typeof(CreateOrderCommand).Assembly,
typeof(OrdersIntegrationEventHandler).Assembly);
});
This overload registers one IMediator, one IEventBus, one IScheduler, and one Wolverine runtime. Both DbContexts are enrolled in the same PostgreSQL message store and therefore use the same durable inbox/outbox tables in the wolverine schema.
Each AddModule<TDbContext>() also creates a scoped IOutboxDispatcher registration keyed by typeof(TDbContext). Outside the mediator pipeline, resolve it from the same DI scope as the module's DbContext:
using Microsoft.Extensions.DependencyInjection;
using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.OutboxDispatcher;
var outbox = scope.ServiceProvider.GetRequiredKeyedService<IOutboxDispatcher>(
typeof(OrdersWriteDbContext));
This selects the module's EF Core outbox directly. Within an explicit transaction, call outbox.SaveChangesAsync(cancellationToken) before commit and outbox.FlushAsync(cancellationToken) after commit.
Handlers for the same event type are separated into independent local queues and transactions. Durable message identity includes the destination so fan-out handlers have independent inbox records, retries, and failure handling.
The first assembly passed to AddModule<TDbContext>() contains the module's requests and is owned by exactly one DbContext. Additional assemblies are used only for handler and validator discovery. Assigning one request assembly to different DbContexts is rejected during configuration.
The module persistence registration must expose keyed IUnitOfWork services under the corresponding DbContext types. PANiXiDA.Core.Infrastructure.Persistence.Ef does this automatically for write DbContexts. During a mediator request, the package activates the owning module and routes the existing non-generic IUnitOfWork and IEventBus abstractions to that module's transaction and EF Core outbox.
Before a successful command commits, PersistOutgoingMessagesBehavior saves the active module's DbContext, including tracked message envelopes and business changes. It runs after domain event publication and before CommitTransactionBehavior; FlushOutgoingMessagesBehavior releases messages only after commit. Custom request pipelines must preserve this order. IUnitOfWork.CommitTransactionAsync() itself only completes the transaction.
Custom IOutboxDispatcher implementations must implement SaveChangesAsync(CancellationToken cancellationToken) when upgrading. Persist pending messages before commit, or explicitly return a completed task if they were already persisted in the current transaction. No default implementation is provided by the interface.
They must also implement SendAsync and the PublishAsync overload accepting Wolverine DeliveryOptions. Preserve those options and add messages to the same transactional outbox without committing or flushing it.
Calls to SaveChangesAsync and FlushAsync require an explicit cancellation token. Pass CancellationToken.None when cancellation is not needed.
For ordinary Wolverine messages, including events, module selection is not based on the message assembly. Wolverine detects the transaction from the concrete handler's DbContext dependency. This allows handlers for one shared event contract to commit or roll back independently in different module schemas. A transactional handler must depend on exactly one write DbContext.
IEventBus and IScheduler share IOutboxDispatcher, which uses the keyed module outbox inside the mediator request pipeline and the current Wolverine message context inside native handlers. The inbox record, handler changes, and messages published or scheduled by a native handler therefore share its selected DbContext transaction.
Without an active mediator module, the modular dispatcher's SaveChangesAsync throws InvalidOperationException: it cannot select a DbContext to save. Native handlers rely on Wolverine's transaction middleware to save changes and flush messages, so FlushAsync remains a no-op in that context. Outside the mediator pipeline, use the keyed module dispatcher to save and flush explicitly.
Do not synchronously invoke a command from another module while the first module transaction is active. Separate DbContexts use separate local database transactions, so such a call cannot be atomic. Publish an event through the outbox and let the receiving module handle it independently.
Scheduling
Inject IScheduler from PANiXiDA.Core.Application.Messaging.Scheduling into handlers:
await scheduler.ScheduleAsync(command, TimeSpan.FromMinutes(15), cancellationToken);
await scheduler.ScheduleAtAsync(occurredEvent, publishAt, cancellationToken);
Commands use SendAsync; events use PublishAsync. Scheduling delays delivery and does not return handler results.
The adapters do not check messages or delivery options for null, reject negative delays, or check cancellation before dispatch. Delay values are passed unchanged to Wolverine. Persistence still passes the cancellation token to EF Core.
Inside mediator requests, scheduled messages use the active module's EF Core outbox and commit or roll back with its business changes.
Native Wolverine handlers use their enlisted message context. The configured PostgreSQL storage and durable queues preserve scheduled messages across restarts.
Outside handlers in modular setups, construct WolverineScheduler with the module's keyed IOutboxDispatcher resolved using typeof(AppDbContext). Single-context setups can use the registered IScheduler. Save and commit through the same IDbContextOutbox<AppDbContext>/DbContext transaction; scheduling alone does not commit changes.
The adapter uses typed DI registrations and introduces no runtime reflection or code generation; full Native AOT support remains limited by Wolverine and EF Core.
Kafka Topics
Kafka is opt-in per event type. If no Kafka producer is registered for an event, publishing stays in-process.
Create typed option models in the consuming infrastructure project:
using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.Options;
public sealed class MainKafkaBrokerOption : KafkaBrokerOption
{
}
public sealed class UserCreatedKafkaProducerOption : KafkaProducerOption
{
}
public sealed class UserCreatedKafkaConsumerOption : KafkaConsumerOption
{
}
Register brokers, producers, and consumers in the Wolverine mediator configuration:
using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.DependencyInjection;
builder.Services.AddWolverineMediator<AppDbContext>();
builder.Host.UseWolverineMediator<AppDbContext>(
builder.Configuration.GetConnectionString("PostgreSqlConnectionString")!,
builder.Configuration,
options =>
{
options.AddKafkaBroker<MainKafkaBrokerOption>();
options.AddKafkaProducer<UserCreatedKafkaProducerOption, UserCreated>();
options.AddKafkaConsumer<UserCreatedKafkaConsumerOption, UserCreated>();
},
typeof(UserCreatedHandler).Assembly);
Configuration sections are resolved by option type name:
{
"ConnectionStrings": {
"PostgreSqlConnectionString": "Host=localhost;Port=5432;Database=app;Username=app;Password=app"
},
"MainKafkaBrokerOption": {
"BootstrapServers": "localhost:9092"
},
"UserCreatedKafkaProducerOption": {
"TopicName": "users.created"
},
"UserCreatedKafkaConsumerOption": {
"TopicName": "users.created",
"ConsumerGroupId": "users-service",
"AutoOffsetReset": "Earliest"
}
}
For named Kafka brokers, put the broker name into broker and topic options:
public sealed class ExternalKafkaBrokerOption : KafkaBrokerOption
{
}
public sealed class ExternalUserCreatedKafkaProducerOption : KafkaProducerOption
{
}
{
"ExternalKafkaBrokerOption": {
"BrokerName": "external",
"BootstrapServers": "external-kafka:9092"
},
"ExternalUserCreatedKafkaProducerOption": {
"BrokerName": "external",
"TopicName": "external.users.created"
}
}
options.AddKafkaBroker<ExternalKafkaBrokerOption>();
options.AddKafkaProducer<ExternalUserCreatedKafkaProducerOption, UserCreated>();
EF Core Storage
The package enrolls TDbContext in Wolverine PostgreSQL message storage. If the application keeps Wolverine envelope tables in EF Core migrations, map them in the DbContext model:
using Wolverine.EntityFrameworkCore;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.MapWolverineEnvelopeStorage("wolverine");
}
With the modular overload, the configured message-store schema is shared by all modules. Let Wolverine managed resources own that schema, or map it in exactly one dedicated messaging migration context. Do not map the shared envelope tables in every business module DbContext.
Dead Letter Expiration
Both single-context and modular registrations enable automatic expiration of database-backed dead letters after seven days. Wolverine assigns the expiration when a message enters the dead letter store: it uses the message's DeliverBy value when present, otherwise the current time plus seven days. Its background durability agent permanently deletes expired messages; cleanup is periodic, not immediate at the expiration time.
Applications can override the retention period or disable expiration by configuring Wolverine after UseWolverineMediator:
using Wolverine;
builder.Services.ConfigureWolverine(options =>
{
options.Durability.DeadLetterQueueExpirationEnabled = true;
options.Durability.DeadLetterQueueExpiration = TimeSpan.FromDays(14);
});
Set DeadLetterQueueExpirationEnabled to false to disable automatic cleanup. These settings apply to database-backed dead letters, not native broker dead letter queues. Existing rows without an expiration timestamp are not backfilled by this configuration change.
Behavior
Commands and queries are invoked in-process through Wolverine and PANiXiDA request contracts.
Domain events are published through IEventBus. By default, Wolverine dispatches them to local handlers. When a Kafka producer is registered for the event type, the same event is also routed to the configured Kafka topic through durable outbox.
Kafka consumers use durable inbox and map incoming topic messages to the configured event type with DefaultIncomingMessage<TEvent>().
Request Behaviors
The default request behavior pipeline is:
before: ValidationBehavior
before: BeginTransactionBehavior
after: PublishDomainEventsBehavior
after: PersistOutgoingMessagesBehavior
after: CommitTransactionBehavior
after: FlushOutgoingMessagesBehavior
finally: CleanupTransactionBehavior
The modular overload activates module routing before validation, keeps the application CleanupTransactionBehavior, and releases module routing after cleanup. The application-facing pipeline continues to depend only on the PANiXiDA IUnitOfWork and IEventBus abstractions.
Validators are discovered from the same assemblies passed to UseWolverineMediator<TDbContext>() for handler discovery.
The bundled source generator prepares request behavior metadata and scoped
FluentValidation registrations. Keep analyzer assets enabled in the host and reference
the assemblies passed to AddModule or UseWolverineMediator. Requests, behaviors,
validators, and validated types must be accessible to generated code, normally
public for referenced assemblies. Internal validators require the generator in
their declaring project. No additional registration calls, attributes, partial
declarations, or code-generation commands are required.
Missing validator registrations fail explicitly at startup without reflection scanning.
Validators within each discovery assembly are registered in deterministic type-name order.
Rebuild and republish the host together with changed discovery assemblies to regenerate
their validator registrations.
Validators emitted by another source generator must be compiled in a referenced project,
such as Application, so the host can discover them. Generating them alongside Wolverine
registrations in the same project produces diagnostic PANWOLVSG001.
Custom behaviors can be appended or inserted before or after any behavior in the same stage:
using PANiXiDA.Core.Application.Messaging.Mediator.Behaviors;
using PANiXiDA.Core.Infrastructure.Messaging.Wolverine.Behaviors;
builder.Host.UseWolverineMediator<AppDbContext>(
builder.Configuration.GetConnectionString("PostgreSqlConnectionString")!,
behaviors =>
{
behaviors.Before.InsertAfter(
typeof(AuthorizeRequestBehavior<,>),
typeof(ValidationBehavior<,>));
behaviors.After.InsertBefore(
typeof(AuditRequestResultBehavior<,>),
typeof(CommitTransactionBehavior<,>));
behaviors.Finally.InsertAfter(
typeof(ReleaseRequestLockBehavior<,>),
typeof(CleanupTransactionBehavior<,>));
},
typeof(CreateUserHandler).Assembly);
The same behavior configuration can be combined with Kafka topology registration:
builder.Host.UseWolverineMediator<AppDbContext>(
builder.Configuration.GetConnectionString("PostgreSqlConnectionString")!,
builder.Configuration,
kafka =>
{
kafka.AddKafkaBroker<MainKafkaBrokerOption>();
kafka.AddKafkaProducer<UserCreatedKafkaProducerOption, UserCreated>();
},
behaviors =>
{
behaviors.After.InsertBefore(
typeof(AuditRequestResultBehavior<,>),
typeof(FlushOutgoingMessagesBehavior<,>));
},
typeof(UserCreatedHandler).Assembly);
Development
dotnet restore
dotnet format
dotnet build --configuration Release
dotnet test --configuration Release
Continuous integration
Every pull request and push to main runs formatting, tests, and mandatory
SonarQube analysis. Publishing from main starts only after the SonarQube
Quality Gate succeeds.
License
This project is licensed under the Apache-2.0 license.
See the LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FluentValidation.DependencyInjectionExtensions (>= 12.1.1)
- Microsoft.CodeAnalysis.CSharp (>= 5.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- PANiXiDA.Core.Application (>= 4.1.1)
- PANiXiDA.Core.Domain (>= 3.0.1)
- WolverineFx (>= 6.40.0)
- WolverineFx.EntityFrameworkCore (>= 6.40.0)
- WolverineFx.Kafka (>= 6.40.0)
- WolverineFx.Postgresql (>= 6.40.0)
- WolverineFx.RuntimeCompilation (>= 6.40.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.0.3 | 193 | 11/5/2026 |
| 6.0.2 | 933 | 11/3/2026 |
| 6.0.1 | 65 | 11/3/2026 |
| 5.1.3 | 530 | 10/21/2026 |
| 5.1.2 | 123 | 10/20/2026 |
| 5.1.1 | 187 | 10/18/2026 |
| 5.0.3 | 130 | 10/11/2026 |
| 5.0.2 | 128 | 10/11/2026 |
| 5.0.1 | 144 | 10/9/2026 |
| 4.2.4 | 94 | 10/5/2026 |
| 4.2.3 | 100 | 10/5/2026 |
| 4.2.2 | 97 | 10/5/2026 |
| 4.2.1 | 195 | 10/4/2026 |
| 4.1.2 | 130 | 10/4/2026 |
| 4.1.1 | 103 | 10/3/2026 |
| 4.0.2 | 108 | 9/27/2026 |
| 4.0.1 | 106 | 9/27/2026 |
| 3.0.2 | 327 | 8/30/2026 |
| 3.0.1 | 114 | 8/29/2026 |
| 2.0.5 | 132 | 8/29/2026 |