The Situation
Let's be honest, many of us in the .NET world have relied on MediatR. It's a fantastic library that has shaped how we think about CQRS and in-process messaging. However, with the recent introduction of a licensing model, many developers are looking for free, open-source alternatives for its notification (publish/subscribe) capabilities.
I was in the same boat. I love the pub/sub pattern for decoupling components, but I needed a solution that was not only free but also incredibly fast and architecturally sound, especially for applications using services with specific lifetimes, like Entity Framework's DbContext.
That's why I created FlashEvents.
FlashEvents is a high-performance, in-memory event publishing library for .NET designed with two core principles in mind: simplicity and speed.
What Makes FlashEvents Different?
FlashEvents focuses exclusively on the "notification" or "event publishing" part of the pattern. Its key features are designed to solve common problems gracefully:
- 🚀 Blazing Fast Performance: It's optimized for low-latency and minimal memory allocations. (We'll get to the benchmarks later!)
- ⚡ Parallel Execution by Default: When you publish an event, all its handlers run concurrently using
Task.WhenAll. This maximizes throughput without any special configuration. - 🛡️ Scoped Handler Isolation: This is the killer feature. Each event handler is resolved and executed in its own
IServiceScope. This completely prevents issues with shared state. If one handler uses aDbContext, it gets its own instance. Another handler can use its ownDbContextor other scoped services without any conflicts. This is a huge win for reliability. - 🔧 Simple & Fluent API: Setting it up is a breeze with clean dependency injection extensions.
- 🔍 Automatic Handler Discovery: A single line of code can register all your event handlers from an assembly.
Let's See It in Action
Here’s how you can integrate FlashEvents into your project in just a few minutes.
1. Define Your Event
using FlashEvents.Abstractions;
public record OrderCreatedEvent(int OrderId, string CustomerEmail) : IEvent;
2. Create Event Handlers
using FlashEvents.Abstractions;
// Handler 1: Sends a welcome email
public class SendWelcomeEmailHandler : IEventHandler<OrderCreatedEvent>
{
private readonly IEmailService _emailService; // Can be transient or singleton
public SendWelcomeEmailHandler(IEmailService emailService) { /* ... */ }
public async Task Handle(OrderCreatedEvent @event, CancellationToken ct)
{
// ... send email ...
}
}
// Handler 2: Updates analytics using a scoped service
public class UpdateAnalyticsHandler : IEventHandler<OrderCreatedEvent>
{
private readonly IAnalyticsService _analyticsService; // Scoped service
public UpdateAnalyticsHandler(IAnalyticsService analyticsService) { /* ... */ }
public async Task Handle(OrderCreatedEvent @event, CancellationToken ct)
{
// ... update analytics ...
}
}
3. Configure Dependency Injection
using FlashEvents;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
// 1. Add FlashEvents services
builder.Services.AddEventPublisher();
// 2. Automatically discover and register all handlers
builder.Services.AddEventHandlersFromAssembly(Assembly.GetExecutingAssembly());
// 3. Register your other application services
builder.Services.AddTransient<IEmailService, EmailService>();
builder.Services.AddScoped<IanalyticsService, AnalyticsService>(); // Our scoped service
var app = builder.Build();
// ...
4. Publish an Event
using FlashEvents.Abstractions;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class OrdersController : ControllerBase
{
private readonly IEventPublisher _eventPublisher;
public OrdersController(IEventPublisher eventPublisher)
{
_eventPublisher = eventPublisher;
}
[HttpPost]
public async Task<IActionResult> CreateOrder()
{
// ... logic to create an order ...
var orderEvent = new OrderCreatedEvent(123, "test@example.com");
// Publish it. FlashEvents handles the rest.
await _eventPublisher.PublishAsync(orderEvent);
return Ok("Order created and events are being handled.");
}
}
But Is It Fast? The Benchmarks
Talk is cheap. Let's look at the numbers against MediatR v12.5.0.
System: i5-13400F, .NET 8
Scenario 1: Single Handler (vs. MediatR's Parallel Publisher)
| Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| FlashEvents_Publish | 205.1 ns | 1.79 ns | 1.68 ns | 1.00 | 520 B | 1.00 |
| MediatR_Publish | 141.19 ns | 1.114 ns | 0.988 ns | 1.55 | 664 B | 3.77 |
Scenario 2: Two Handlers (vs. MediatR's Parallel Publisher)
This test highlights the parallel execution for two handlers.
| Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| FlashEvents_Publish | 269.3 ns | 3.34 ns | 2.79 ns | 1.00 | 712 B | 1.00 |
| MediatR_Publish | 171.4 ns | 0.95 ns | 0.79 ns | 0.64 | 824 B | 1.16 |
Why Choose FlashEvents?
If you need the request/response or pipeline behaviors from MediatR, then MediatR is still the tool for the job.
But if you are primarily using its notification publisher feature, FlashEvents offers a compelling alternative:
- Free & Open Source: It's licensed under MIT. No fees, no strings attached.
- Architectural Safety: Built-in scoped execution for handlers is a huge advantage that prevents a common class of bugs related to dependency lifetimes.
- Simplicity: It does one thing and does it well. The API surface is minimal and easy to learn.
- Performance: It's extremely lightweight and holds its own against the industry standard, especially regarding memory allocations.
Give It a Try!
I built FlashEvents to solve a problem I was facing, and I hope it can help you too. It's available on NuGet, and the source code is on GitHub.
- NuGet Package:
FlashEvents - GitHub Repo:
Anton-Grebenkin/FlashEvents
Top comments (0)