MagicCSharp.Events 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package MagicCSharp.Events --version 1.0.0
                    
NuGet\Install-Package MagicCSharp.Events -Version 1.0.0
                    
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="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MagicCSharp.Events" Version="1.0.0" />
                    
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 1.0.0
                    
#r "nuget: MagicCSharp.Events, 1.0.0"
                    
#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@1.0.0
                    
#: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=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=MagicCSharp.Events&version=1.0.0
                    
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.

Registration order

Two calls, and the first is not optional:

services.AddMagicEvents();        // handler discovery, serializer, IAsyncEventDispatcher
services.AddLocalMagicEvents();    // then a transport — or AddMagicKafkaEvents / AddMagicSqsEvents

AddMagicEvents is what finds your IEventHandler<T> implementations and registers IAsyncEventDispatcher. The transport call only registers IEventDispatcher, so calling it alone leaves the application failing at resolution the first time anything dispatches. MagicCSharp.App does both for you.

Priority must be static. The dispatcher reads it without constructing the handler, so an instance property compiles and is silently ignored — your handler runs at the default priority. The interface declares it static virtual.

public class SendOrderConfirmationHandler : IEventHandler<OrderCreated>
{
    public static MagicEventPriority Priority => MagicEventPriority.NotifyUser;

    public Task Handle(OrderCreated evt) => ...;
}

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 static 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 static 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.AddMagicEvents();
// Register the LocalEventHandler as the IEventDispatcher
services.AddLocalMagicEvents();

// Optional: Enable OpenTelemetry metrics
// services.AddMagicEvents(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 static 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 static 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 static 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 AddLocalMagicEvents() 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.AddLocalMagicEvents();

// 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 AddMagicKafkaEvents() or AddMagicSqsEvents(). These register the distributed event dispatcher as IEventDispatcher:

// In your Startup.cs or Program.cs

// Register Kafka
services.AddMagicKafkaEvents(kafkaConfig);
// OR
// Register SQS
services.AddMagicSqsEvents(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.AddMagicEvents(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.AddLocalMagicEvents();
    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 static MagicEventPriority Priority => MagicEventPriority.AddDataWithDependencies;

    public async Task Handle(OrderCreatedEvent @event)
    {
        await inventoryService.ReserveInventory(@event.OrderId);
    }
}

public class SendConfirmationHandler(IEmailService emailService)
    : IEventHandler<OrderCreatedEvent>
{
    public static MagicEventPriority Priority => MagicEventPriority.NotifyUser;

    public async Task Handle(OrderCreatedEvent @event)
    {
        await emailService.SendOrderConfirmation(@event.OrderId);
    }
}

// 3. Register
services.AddLocalMagicEvents();

// 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 (4)

Showing the top 4 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

MagicCSharp.App

Everything a MagicCSharp web service needs, wired in two calls. Brings in the core, ASP.NET, events and scheduling packages and registers use cases, TimeProvider, Snowflake IDs, request-ID tracking, in-process events, scheduling defaults, problem-details error handling and a startup preflight. Use the individual packages instead when you want to choose each piece.

MagicCSharp.Testing

Test doubles for MagicCSharp: a deterministic key generator driven by the test's TimeProvider, a synchronous event dispatcher, and an in-memory distributed lock. No test framework or database dependencies — see MagicCSharp.Testing.Database for those.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 393 9/24/2026
1.0.1 155 9/24/2026
1.0.0 144 9/23/2026
0.0.13 466 1/12/2026
0.0.12 166 1/12/2026
0.0.11 163 1/12/2026
0.0.9 163 1/12/2026
0.0.7 204 11/1/2025
0.0.6 183 11/1/2025
0.0.4 182 11/1/2025
0.0.2 181 11/1/2025