Skip to main content
Version: Next

Event Handling Examples

This page walks through the event handling examples included with RCommon. Each example is a self-contained console application showing a different event handling provider.

Example Projects

Examples/EventHandling/
Examples.EventHandling.InMemoryEventBus/
Examples.EventHandling.MediatR/

Step 1: Define an Event

An event is a plain class that implements ISyncEvent. It is immutable after construction:

using RCommon.Models.Events;

public class TestEvent : ISyncEvent
{
public TestEvent() { }

public TestEvent(DateTime dateTime, Guid guid)
{
DateTime = dateTime;
Guid = guid;
}

public DateTime DateTime { get; }
public Guid Guid { get; }
}

ISyncEvent is the marker interface that RCommon's routing infrastructure uses to identify events. No base class is required.

Step 2: Define a Subscriber

A subscriber implements ISubscriber<TEvent> and contains the handling logic:

using RCommon.EventHandling.Subscribers;

public class TestEventHandler : ISubscriber<TestEvent>
{
public async Task HandleAsync(TestEvent notification,
CancellationToken cancellationToken = default)
{
Console.WriteLine("Handled event: {0}", notification.ToString());
await Task.CompletedTask;
}
}

Subscribers are registered with a specific builder, which scopes their routing.

Example 1: In-Memory Event Bus

Installation

NuGet Package
dotnet add package RCommon.EventHandling

Configuration

services.AddRCommon()
.WithEventHandling<InMemoryEventBusBuilder>(eventHandling =>
{
eventHandling.AddProducer<PublishWithEventBusEventProducer>();
eventHandling.AddSubscriber<TestEvent, TestEventHandler>();
});

WithEventHandling<InMemoryEventBusBuilder> registers an in-process bus. Events are dispatched synchronously within the same process with no external broker dependency.

Publishing

Resolve all registered IEventProducer instances and call ProduceEventAsync:

var eventProducers = host.Services.GetServices<IEventProducer>();
var testEvent = new TestEvent(DateTime.Now, Guid.NewGuid());

foreach (var producer in eventProducers)
{
await producer.ProduceEventAsync(testEvent);
}

Full Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using RCommon;
using RCommon.EventHandling;
using RCommon.EventHandling.Producers;

var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddRCommon()
.WithEventHandling<InMemoryEventBusBuilder>(eventHandling =>
{
eventHandling.AddProducer<PublishWithEventBusEventProducer>();
eventHandling.AddSubscriber<TestEvent, TestEventHandler>();
});
}).Build();

Console.WriteLine("Example Starting");

var eventProducers = host.Services.GetServices<IEventProducer>();
var testEvent = new TestEvent(DateTime.Now, Guid.NewGuid());

foreach (var producer in eventProducers)
{
await producer.ProduceEventAsync(testEvent);
}

Console.WriteLine("Example Complete");

Expected Output

Example Starting
Handled event: Examples.EventHandling.InMemoryEventBus.TestEvent
Example Complete

Example 2: MediatR Event Bus

Installation

NuGet Package
dotnet add package RCommon.MediatR

Configuration

services.AddRCommon()
.WithEventHandling<MediatREventHandlingBuilder>(eventHandling =>
{
eventHandling.AddProducer<PublishWithMediatREventProducer>();
eventHandling.AddSubscriber<TestEvent, TestEventHandler>();
});

WithEventHandling<MediatREventHandlingBuilder> routes events through MediatR's IPublisher. This integrates with existing MediatR pipeline behaviors.

Full Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using RCommon;
using RCommon.EventHandling.Producers;
using RCommon.MediatR;
using RCommon.MediatR.Producers;

var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddRCommon()
.WithEventHandling<MediatREventHandlingBuilder>(eventHandling =>
{
eventHandling.AddProducer<PublishWithMediatREventProducer>();
eventHandling.AddSubscriber<TestEvent, TestEventHandler>();
});
}).Build();

Console.WriteLine("Example Starting");

var eventProducers = host.Services.GetServices<IEventProducer>();
var testEvent = new TestEvent(DateTime.Now, Guid.NewGuid());

foreach (var producer in eventProducers)
{
await producer.ProduceEventAsync(testEvent);
}

Console.WriteLine("Example Complete");

Multiple Subscribers for the Same Event

Register the same event type with multiple handlers. All handlers are invoked when the event is published:

services.AddRCommon()
.WithEventHandling<InMemoryEventBusBuilder>(eventHandling =>
{
eventHandling.AddProducer<PublishWithEventBusEventProducer>();
eventHandling.AddSubscriber<OrderPlacedEvent, SendConfirmationEmailHandler>();
eventHandling.AddSubscriber<OrderPlacedEvent, UpdateInventoryHandler>();
eventHandling.AddSubscriber<OrderPlacedEvent, NotifyWarehouseHandler>();
});

Event Handling in an ASP.NET Core Application

Wire event handling alongside the rest of your RCommon configuration:

// Program.cs — ASP.NET Core Web API
builder.Services.AddRCommon()
.WithMediator<MediatRBuilder>(mediator =>
{
mediator.AddRequest<PlaceOrderCommand, BaseCommandResponse,
PlaceOrderCommandHandler>();
mediator.AddUnitOfWorkToRequestPipeline();
})
.WithEventHandling<InMemoryEventBusBuilder>(eventHandling =>
{
eventHandling.AddProducer<PublishWithEventBusEventProducer>();
eventHandling.AddSubscriber<OrderPlacedEvent, SendConfirmationEmailHandler>();
eventHandling.AddSubscriber<OrderPlacedEvent, UpdateDashboardHandler>();
})
.WithPersistence<EFCorePerisistenceBuilder>(ef =>
{
ef.AddDbContext<AppDbContext>("AppDb", options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString("AppDb"));
});
ef.SetDefaultDataStore(ds => ds.DefaultDataStoreName = "AppDb");
});

In a command handler, inject and use IEnumerable<IEventProducer>:

public class PlaceOrderCommandHandler
: IAppRequestHandler<PlaceOrderCommand, BaseCommandResponse>
{
private readonly IGraphRepository<Order> _orderRepository;
private readonly IEnumerable<IEventProducer> _eventProducers;

public PlaceOrderCommandHandler(
IGraphRepository<Order> orderRepository,
IEnumerable<IEventProducer> eventProducers)
{
_orderRepository = orderRepository;
_eventProducers = eventProducers;
}

public async Task<BaseCommandResponse> HandleAsync(
PlaceOrderCommand request,
CancellationToken cancellationToken)
{
var order = new Order(request.CustomerId, request.Items);
await _orderRepository.AddAsync(order);

var @event = new OrderPlacedEvent(order.Id, order.CustomerId);

foreach (var producer in _eventProducers)
{
await producer.ProduceEventAsync(@event);
}

return new BaseCommandResponse { Success = true, Id = order.Id };
}
}

Testing an Event Handler

Event handlers are plain classes and test without a running host:

[Test]
public async Task Handler_Writes_To_Console_On_Event()
{
var handler = new TestEventHandler();
var @event = new TestEvent(DateTime.UtcNow, Guid.NewGuid());

// Should not throw
await handler.HandleAsync(@event, CancellationToken.None);
}

For handlers with dependencies, use mocks:

[Test]
public async Task SendConfirmationEmailHandler_Calls_Email_Service()
{
var emailMock = new Mock<IEmailService>();
var handler = new SendConfirmationEmailHandler(emailMock.Object);

await handler.HandleAsync(
new OrderPlacedEvent(Guid.NewGuid(), "customer-1"),
CancellationToken.None);

emailMock.Verify(
x => x.SendAsync(It.Is<EmailRequest>(r => r.To == "customer-1")),
Times.Once);
}

Choosing Between In-Memory and MediatR

AspectInMemoryEventBusBuilderMediatREventHandlingBuilder
External dependencyNone beyond RCommonMediatR NuGet package
Pipeline behaviorsNot supportedSupported via MediatR behaviors
Multi-publisher isolationYesYes
Distributed messagingNoNo
Best forSimple in-process eventsProjects already using MediatR

For distributed messaging across services, see the Messaging Examples page.

API Reference

TypePackagePurpose
ISyncEventRCommon.ModelsBase interface for all events
IEventProducerRCommon.EventHandlingPublishes events
ISubscriber<TEvent>RCommon.EventHandlingHandles events
InMemoryEventBusBuilderRCommon.EventHandlingRegisters the in-process event bus
PublishWithEventBusEventProducerRCommon.EventHandlingIn-memory producer
MediatREventHandlingBuilderRCommon.MediatRRegisters MediatR-backed event handling
PublishWithMediatREventProducerRCommon.MediatRMediatR-backed producer
RCommonRCommon