MagicCSharp.Events 0.0.13

dotnet add package MagicCSharp.Events --version 0.0.13
                    
NuGet\Install-Package MagicCSharp.Events -Version 0.0.13
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MagicCSharp.Events" Version="0.0.13" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MagicCSharp.Events" Version="0.0.13" />
                    
Directory.Packages.props
<PackageReference Include="MagicCSharp.Events" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MagicCSharp.Events --version 0.0.13
                    
#r "nuget: MagicCSharp.Events, 0.0.13"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MagicCSharp.Events@0.0.13
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MagicCSharp.Events&version=0.0.13
                    
Install as a Cake Addin
#tool nuget:?package=MagicCSharp.Events&version=0.0.13
                    
Install as a Cake Tool

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 tasks
  • AddDataNoDependencies = 0 - Create data with no dependencies
  • AddDataWithDependencies = 1000 - Create data that depends on other handlers
  • UpdateMetadata = 2000 - Update metadata after data is created
  • DeleteData = 2500 - Delete operations
  • NotifyUser = 3000 - Send notifications
  • RunLast = 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 IEventDispatcher interface 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 type
  • Events.Failed - Counter of failed events by type and handler
  • Events.Finished - Counter of completed events by type and handler
  • Events.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

MagicCSharp - Core infrastructure library (required) MagicCSharp.Data - Repository pattern and data access

License

MIT License - See LICENSE file for details.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
0.0.13 417 1/12/2026
0.0.12 150 1/12/2026
0.0.11 140 1/12/2026
0.0.9 143 1/12/2026
0.0.7 183 11/1/2025
0.0.6 162 11/1/2025
0.0.4 162 11/1/2025
0.0.2 164 11/1/2025