At some point in your .NET journey, you’ve probably worked with libraries like MediatR, MassTransit, or CAP for handling messaging and commands. Each has its strengths, but sometimes the wiring and configuration get in the way of just building features.
Recently, I discovered Wolverine, a command and message bus library from the JasperFx team. It brings together CQRS, messaging, scheduled jobs, and durable inbox/outbox all in one sleek package. And the best part? It’s fast, clean, and fun to work with.
In this article, I’ll walk you through a simple console application that demonstrates how to use Wolverine to handle commands, publish events, and schedule background messages, all without requiring a database or web API.
Why Wolverine?
Wolverine aims to simplify distributed application architecture by combining.
- Command handling (like MediatR).
- Asynchronous and scheduled messaging.
- Built-in middleware and behaviors.
- Optional durable inbox/outbox with Marten (PostgreSQL).
Instead of bolting together multiple libraries, Wolverine gives you a unified model with batteries included.
The Example: Console-Based User Registration
We’ll build a console app that,
- Registers a user via a command.
- Sends a welcome email (simulated).
- Schedules a follow-up reminder message.
Everything runs in-process using Wolverine's local message bus.
Step 1. Create the Project
Project Folder Structure
WolverineConsoleDemo/
├── Program.cs
├── WolverineConsoleDemo.csproj
├── Messages/
│ ├── RegisterUser.cs
│ ├── UserRegistered.cs
│ └── RemindAdmin.cs
├── Handlers/
│ ├── RegisterUserHandler.cs
│ ├── UserRegisteredHandler.cs
│ └── RemindAdminHandler.cs
dotnet new console -n WolverineConsoleDemo
cd WolverineConsoleDemo
dotnet add package WolverineFx
Step 2. Define Messages and Handlers
RegisterUser.cs
public record RegisterUser(string Email, string FullName);
UserRegistered.cs
public record UserRegistered(string Email);
RemindAdmin.cs
public record RemindAdmin(string Email);
Step 3. Implement Handlers
RegisterUserHandler.cs
using Wolverine;
using WolverineConsoleDemo.Messages;
namespace WolverineConsoleDemo.Handlers
{
public class RegisterUserHandler
{
/// <summary>
/// Handles the specified command.
/// </summary>
/// <param name="command">The command.</param>
/// <param name="context">The context.</param>
public static async Task Handle(RegisterUser command, IMessageContext context)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"Registered: {command.FullName}");
Console.ResetColor();
await context.PublishAsync(new UserRegistered(command.Email));
await context.ScheduleAsync(new RemindAdmin(command.Email), TimeSpan.FromSeconds(10));
}
}
}
UserRegisteredHandler.cs
using WolverineConsoleDemo.Messages;
namespace WolverineConsoleDemo.Handlers
{
public class UserRegisteredHandler
{
/// <summary>
/// Handles the specified event.
/// </summary>
/// <param name="event">The event.</param>
/// <returns></returns>
public static Task Handle(UserRegistered @event)
{
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine($"Welcome email sent to {@event.Email}");
Console.ResetColor();
return Task.CompletedTask;
}
}
}
RemindAdminHandler.cs
using WolverineConsoleDemo.Messages;
namespace WolverineConsoleDemo.Handlers
{
public class RemindAdminHandler
{
/// <summary>
/// Handles the specified MSG.
/// </summary>
/// <param name="msg">The MSG.</param>
/// <returns></returns>
public static Task Handle(RemindAdmin msg)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"Admin reminded for: {msg.Email}");
Console.ResetColor();
return Task.CompletedTask;
}
}
}
Step 4. Configure Wolverine in the Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Wolverine;
using WolverineConsoleDemo.Messages;
var builder = Host.CreateApplicationBuilder(args);
// 👇 suppress hosting lifetime logs
builder.Logging.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.None);
builder.Services.AddWolverine(opts =>
{
opts.PublishMessage<RegisterUser>().ToLocalQueue("users");
opts.PublishMessage<UserRegistered>().ToLocalQueue("emails");
opts.PublishMessage<RemindAdmin>().ToLocalQueue("admin");
});
using var host = builder.Build();
// Start the host before using Wolverine
await host.StartAsync();
var bus = host.Services.GetRequiredService<IMessageBus>();
await bus.InvokeAsync(new RegisterUser("[email protected]", "Test User"));
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
// Optional: gracefully shut down
await host.StopAsync();
Sample Console Output
This output demonstrates how Wolverine utilizes color-coded messages to distinguish each part of the registration process clearly.
![Console Output]()
Conclusion
Wolverine brings a fresh, powerful approach to building message-driven systems in .NET. With minimal setup and zero boilerplate, we’ve seen how easy it is to.
- Create and handle commands.
- Publish and respond to events.
- Schedule background tasks.
- Run everything in-process without external queues or services.
This lightweight console app demonstrates that you don’t need heavy infrastructure to adopt clean, decoupled architectures. Whether you’re prototyping a feature, building core business logic, or moving toward microservices, Wolverine gives you the right tools with a developer-friendly experience.
If you're tired of wiring up multiple libraries to do what Wolverine does out of the box, give it a try in your next project. You might be surprised how much simpler your code becomes.