MagicCSharp.Events
0.0.13
dotnet add package MagicCSharp.Events --version 0.0.13
NuGet\Install-Package MagicCSharp.Events -Version 0.0.13
<PackageReference Include="MagicCSharp.Events" Version="0.0.13" />
<PackageVersion Include="MagicCSharp.Events" Version="0.0.13" />
<PackageReference Include="MagicCSharp.Events" />
paket add MagicCSharp.Events --version 0.0.13
#r "nuget: MagicCSharp.Events, 0.0.13"
#:package MagicCSharp.Events@0.0.13
#addin nuget:?package=MagicCSharp.Events&version=0.0.13
#tool nuget:?package=MagicCSharp.Events&version=0.0.13
MagicCSharp.Events
Event-driven architecture made simple
Build decoupled, scalable applications with a clean event-driven architecture. MagicCSharp.Events handles event dispatching, serialization, and handler execution with priority ordering and comprehensive monitoring.
Why MagicCSharp.Events?
✅ Priority-Based Execution - Control handler execution order
✅ Automatic Registration - Event handlers register themselves
✅ Type-Safe Events - Full IntelliSense support
✅ OpenTelemetry Metrics - Built-in performance monitoring
✅ Flexible Dispatching - Synchronous and asynchronous dispatching
✅ Distributed Events - Kafka and AWS SQS support available
Installation
dotnet add package MagicCSharp.Events
dotnet add package MagicCSharp
Quick Start
1. Define an Event
public record UserCreatedEvent : MagicEvent
{
public long UserId { get; init; }
public string Email { get; init; } = string.Empty;
public string Name { get; init; } = string.Empty;
}
2. Create Event Handlers
public class SendWelcomeEmailHandler(IEmailService emailService)
: IEventHandler<UserCreatedEvent>
{
public MagicEventPriority Priority => MagicEventPriority.NotifyUser;
public async Task Handle(UserCreatedEvent @event)
{
await emailService.SendWelcomeEmail(@event.Email, @event.Name);
}
}
public class CreateUserProfileHandler(IProfileRepository profileRepository)
: IEventHandler<UserCreatedEvent>
{
public MagicEventPriority Priority => MagicEventPriority.AddDataNoDependencies;
public async Task Handle(UserCreatedEvent @event)
{
await profileRepository.Create(new Profile
{
UserId = @event.UserId,
Email = @event.Email
});
}
}
3. Register Events
// In your Startup.cs or Program.cs
services.RegisterMagicEvents();
// Register the LocalEventHandler as the IEventDispatcher
services.RegisterLocalMagicEvents();
// Optional: Enable OpenTelemetry metrics
// services.RegisterLocalMagicEvents(useOpenTelemetryMetrics: true);
4. Dispatch Events
public class UserService(IEventDispatcher eventDispatcher, IUserRepository userRepository)
{
public async Task<User> CreateUser(CreateUserRequest request)
{
var user = await userRepository.Create(request);
// Dispatch the event - all handlers will execute
eventDispatcher.Dispatch(new UserCreatedEvent
{
UserId = user.Id,
Email = user.Email,
Name = user.Name
});
return user;
}
}
Features
🎯 Priority-Based Handler Execution
Control the order in which handlers execute using priorities:
public class CreateRelatedDataHandler : IEventHandler<OrderCreatedEvent>
{
// Executes first - no dependencies
public MagicEventPriority Priority => MagicEventPriority.AddDataNoDependencies;
public async Task Handle(OrderCreatedEvent @event)
{
await CreateOrderItems(@event.OrderId);
}
}
public class UpdateInventoryHandler : IEventHandler<OrderCreatedEvent>
{
// Executes second - depends on order items existing
public MagicEventPriority Priority => MagicEventPriority.AddDataWithDependencies;
public async Task Handle(OrderCreatedEvent @event)
{
await UpdateInventoryLevels(@event.OrderId);
}
}
public class SendConfirmationHandler : IEventHandler<OrderCreatedEvent>
{
// Executes last - notify user after everything is done
public MagicEventPriority Priority => MagicEventPriority.NotifyUser;
public async Task Handle(OrderCreatedEvent @event)
{
await SendOrderConfirmation(@event.OrderId);
}
}
Built-in Priorities:
Cron = -1- For scheduled/cron tasksAddDataNoDependencies = 0- Create data with no dependenciesAddDataWithDependencies = 1000- Create data that depends on other handlersUpdateMetadata = 2000- Update metadata after data is createdDeleteData = 2500- Delete operationsNotifyUser = 3000- Send notificationsRunLast = 10000- Anything that should run last
Important: All handlers for a given event are always executed on the same server instance. This ensures that the priority-based execution order is maintained and allows you to chain operations where certain tasks must complete before others begin. This is particularly useful when handlers have dependencies on each other's side effects.
🏠 Always Use IEventDispatcher
Important: Always inject and use IEventDispatcher in your application code, never use specific implementations
directly. This allows you to switch between local and distributed event processing without changing your business logic.
// ✅ CORRECT - Always use IEventDispatcher
public class OrderService(IEventDispatcher eventDispatcher)
{
public void CreateOrder(CreateOrderRequest request)
{
var order = SaveOrder(request);
eventDispatcher.Dispatch(new OrderCreatedEvent { OrderId = order.Id });
}
}
// ❌ WRONG - Don't use specific implementations
public class OrderService(LocalEventDispatcher dispatcher) // Don't do this!
{
// ...
}
Local Development
Use RegisterLocalMagicEvents() to register LocalEventDispatcher as the implementation of IEventDispatcher. This
executes all handlers synchronously in the same process:
// In your Startup.cs or Program.cs
services.RegisterLocalMagicEvents();
// This registers:
// - IEventDispatcher → LocalEventDispatcher (executes handlers in-process)
// - IAsyncEventDispatcher → AsyncEventDispatcher (async version)
// - Event serialization, metrics, and handler discovery
What happens:
public class OrderService(IEventDispatcher eventDispatcher)
{
public void CreateOrder(CreateOrderRequest request)
{
var order = SaveOrder(request);
// Executes all handlers immediately in this process
// Blocks until all handlers complete
eventDispatcher.Dispatch(new OrderCreatedEvent { OrderId = order.Id });
// All handlers have finished executing at this point
}
}
Perfect for:
- Local development and testing
- Single-service applications
- When you need immediate execution
- Unit and integration testing without infrastructure
Distributed Events (Kafka/SQS)
For distributed systems, use RegisterMagicKafkaEvents() or RegisterMagicSQSEvents(). These register the distributed
event dispatcher as IEventDispatcher:
// In your Startup.cs or Program.cs
// Register Kafka
services.RegisterMagicKafkaEvents(kafkaConfig);
// OR
// Register SQS
services.RegisterMagicSQSEvents(sqsConfig);
// This registers:
// - IEventDispatcher → KafkaEventDispatcher (or SqsEventDispatcher)
// - IAsyncEventDispatcher → AsyncEventDispatcher (for consuming events)
// - Event serialization, metrics, and handler discovery
// - Background service to consume events from Kafka/SQS
What happens:
public class OrderService(IEventDispatcher eventDispatcher)
{
public void CreateOrder(CreateOrderRequest request)
{
var order = SaveOrder(request);
// Queues event to Kafka/SQS, returns immediately
eventDispatcher.Dispatch(new OrderCreatedEvent { OrderId = order.Id });
// Handler execution happens asynchronously on consumer services
}
}
Perfect for:
- Microservices architectures
- Cross-service communication
- Async, fire-and-forget event processing
- Resilient, distributed systems
Key Benefits:
- Zero Code Changes - Same
IEventDispatcherinterface for both local and distributed - Easy Testing - Test locally without Kafka/SQS infrastructure
- Flexible Deployment - Switch from local to distributed by changing registration only
📊 OpenTelemetry Metrics
Track event processing with built-in metrics:
services.RegisterLocalMagicEvents(useOpenTelemetryMetrics: true);
Metrics Collected:
Events- Counter of events received by typeEvents.Failed- Counter of failed events by type and handlerEvents.Finished- Counter of completed events by type and handlerEvents.ExecutionTime- Histogram of execution times by type and handler
View in your monitoring system:
Events{eventType="UserCreatedEvent"} = 1234
Events.Failed{eventType="OrderCreatedEvent", handlerName="SendEmailHandler"} = 5
Events.ExecutionTime{eventType="OrderCreatedEvent", handlerName="UpdateInventoryHandler"} = 125ms
✅ Type-Safe Event Serialization
Events are automatically serialized with type information:
{
"type": "UserCreatedEvent",
"body": {
"userId": "123",
"email": "user@example.com",
"name": "John Doe",
"eventId": "abc-123",
"occurredOn": "2024-01-15T10:30:00Z"
}
}
Features:
- Handles polymorphic deserialization
- Converts longs to strings (prevents JavaScript precision loss)
- Skips unknown event types gracefully
- Case-insensitive property names
🧪 Testing Events
Testing event handlers is straightforward:
[Fact]
public async Task SendWelcomeEmailHandler_SendsEmail()
{
// Arrange
var emailService = new Mock<IEmailService>();
var handler = new SendWelcomeEmailHandler(emailService.Object);
var @event = new UserCreatedEvent
{
UserId = 123,
Email = "test@example.com",
Name = "Test User"
};
// Act
await handler.Handle(@event);
// Assert
emailService.Verify(x =>
x.SendWelcomeEmail("test@example.com", "Test User"),
Times.Once);
}
Testing with IEventDispatcher:
[Fact]
public void CreateUser_DispatchesEvent()
{
// Arrange
var services = new ServiceCollection();
services.RegisterLocalMagicEvents();
services.AddTransient<IEventHandler<UserCreatedEvent>, SendWelcomeEmailHandler>();
// ... register other dependencies
var serviceProvider = services.BuildServiceProvider();
var dispatcher = serviceProvider.GetRequiredService<IEventDispatcher>();
// Act
dispatcher.Dispatch(new UserCreatedEvent { ... });
// Assert - verify handlers executed
}
🔧 Custom Event Handlers
Implement IEventHandler<T> to handle any event:
public class AuditLogHandler(IAuditRepository auditRepository)
: IEventHandler<UserCreatedEvent>,
IEventHandler<UserUpdatedEvent>,
IEventHandler<UserDeletedEvent>
{
public async Task Handle(UserCreatedEvent @event)
{
await auditRepository.Log("User created", @event.UserId);
}
public async Task Handle(UserUpdatedEvent @event)
{
await auditRepository.Log("User updated", @event.UserId);
}
public async Task Handle(UserDeletedEvent @event)
{
await auditRepository.Log("User deleted", @event.UserId);
}
}
Handler Lifetime: Event handlers are registered as Transient by default. They are created fresh for each event.
🎨 Advanced Patterns
Conditional Handlers:
public class PremiumUserWelcomeHandler : IEventHandler<UserCreatedEvent>
{
public async Task Handle(UserCreatedEvent @event)
{
if (!@event.IsPremium)
return; // Skip for non-premium users
await SendPremiumWelcomePackage(@event.UserId);
}
}
Batch Operations:
public class BatchNotificationHandler : IEventHandler<OrderCreatedEvent>
{
private readonly List<long> orderIds = new();
public async Task Handle(OrderCreatedEvent @event)
{
orderIds.Add(@event.OrderId);
if (orderIds.Count >= 100)
{
await SendBatchNotification(orderIds);
orderIds.Clear();
}
}
}
Complete Example
// 1. Define Event
public record OrderCreatedEvent : MagicEvent
{
public long OrderId { get; init; }
public long CustomerId { get; init; }
public decimal TotalAmount { get; init; }
}
// 2. Create Handlers
public class UpdateInventoryHandler(IInventoryService inventoryService)
: IEventHandler<OrderCreatedEvent>
{
public MagicEventPriority Priority => MagicEventPriority.AddDataWithDependencies;
public async Task Handle(OrderCreatedEvent @event)
{
await inventoryService.ReserveInventory(@event.OrderId);
}
}
public class SendConfirmationHandler(IEmailService emailService)
: IEventHandler<OrderCreatedEvent>
{
public MagicEventPriority Priority => MagicEventPriority.NotifyUser;
public async Task Handle(OrderCreatedEvent @event)
{
await emailService.SendOrderConfirmation(@event.OrderId);
}
}
// 3. Register
services.RegisterLocalMagicEvents();
// 4. Dispatch
public class OrderService(IEventDispatcher eventDispatcher)
{
public async Task CreateOrder(CreateOrderRequest request)
{
var order = await SaveOrder(request);
eventDispatcher.Dispatch(new OrderCreatedEvent
{
OrderId = order.Id,
CustomerId = order.CustomerId,
TotalAmount = order.TotalAmount
});
}
}
Distributed Event Processing
For distributed event processing with Kafka or SQS:
MagicCSharp.Events.Kafka - Kafka integration MagicCSharp.Events.SQS - AWS SQS integration
Related Packages
MagicCSharp - Core infrastructure library (required) MagicCSharp.Data - Repository pattern and data access
License
MIT License - See LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net9.0
- MagicCSharp (>= 0.0.13)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- System.Diagnostics.DiagnosticSource (>= 9.0.0)
- System.Text.Json (>= 9.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on MagicCSharp.Events:
| Package | Downloads |
|---|---|
|
MagicCSharp.Events.SQS
AWS SQS event dispatcher and consumer implementation for MagicCSharp.Events |
|
|
MagicCSharp.Events.Kafka
Kafka event dispatcher and consumer implementation for MagicCSharp.Events |
GitHub repositories
This package is not used by any popular GitHub repositories.