Getting Started
This guide shows how to register the broker, declare an event and a handler, and publish.
Install
dotnet add package M.EventBrokerSlim
Step 1. Register the broker
AddEventBroker() registers the broker. Call it once, regardless of how many events or handlers you add.
services.AddEventBroker();
It also accepts optional configuration. See In-memory event broker: Registering the event broker.
Step 2. Define an event
An event is any C# type. No interface required. A record works well: it’s immutable, so it’s safe to hand the same instance to multiple handlers running concurrently.
public record ArticlePublished(Guid ArticleId, string Slug);
Step 3. Define a handler
The recommended approach is a delegate pipeline. A pipeline is a function (or a chain of functions) that receives the event and any DI services it needs, all injected as parameters. No class to declare, no interface to implement.
using M.EventBrokerSlim.DependencyInjection;
IPipeline pipeline = PipelineBuilder.Create()
.NewPipeline()
.Execute(async (ArticlePublished e, IEmailService email, CancellationToken ct) =>
{
await email.NotifySubscribersAsync(e.ArticleId, ct);
})
.Build()
.Pipelines[0];
Build() produces the pipeline; Pipelines[0] takes the single one defined here.
The delegate receives the published ArticlePublished instance directly. Any service registered in DI can be added as a parameter alongside it.
Pipelines can also chain multiple delegates to layer cross-cutting concerns. See the Pipelines guide.
Step 4. Register the handler
Register the pipeline for its event type. Do this once per event type you handle:
services.AddEventHandlerPipeline<ArticlePublished>(pipeline);
AddEventHandlerPipeline() links the pipeline to ArticlePublished events.
Step 5. Publish
Inject IEventBroker wherever you publish events:
public class ArticleService
{
private readonly IEventBroker _broker;
public ArticleService(IEventBroker broker) => _broker = broker;
public async Task PublishArticleAsync(Article article, CancellationToken ct)
{
// ... your publishing logic ...
await _broker.Publish(new ArticlePublished(article.Id, article.Slug), ct);
}
}
Publish() writes the event to an in-memory channel and returns immediately.
What happens next
When Publish() is called:
- The event is written to an internal
Channel<object>. - A single consumer loop picks it up and resolves all registered handlers for that event type.
- Each handler runs on the ThreadPool in its own DI scope. The scope is disposed when the handler completes.
Alternative: class-based handlers
If you prefer a class, implement IEventHandler<TEvent>:
public class ArticlePublishedHandler : IEventHandler<ArticlePublished>
{
private readonly IEmailService _email;
public ArticlePublishedHandler(IEmailService email) => _email = email;
public async Task Handle(ArticlePublished e, IRetryPolicy retry, CancellationToken ct)
{
await _email.NotifySubscribersAsync(e.ArticleId, ct);
}
public Task OnError(Exception ex, ArticlePublished e, IRetryPolicy retry, CancellationToken ct)
{
// Called when Handle throws. Use retry.RetryAfter(...) to retry.
return Task.CompletedTask;
}
}
Register it:
services.AddTransientEventHandler<ArticlePublished, ArticlePublishedHandler>();
Class-based and pipeline handlers can coexist. See In-memory broker for mixing handler styles and registering multiple handlers per event.
Next steps
- In-memory broker. Retries without blocking, dynamic handlers, and multiple broker instances.
- Persistent Events. Add durability with a single configuration change.