The Event System

Decouple your UI components using Server-Side Events and Distributed Handlers.

The Monolith Problem

In a typical MVC app, controllers often become "God Objects" that know too much about the UI.

// ❌ Introduction to tight coupling
public IActionResult CompleteTask(int id)
{
    // Business Logic
    _service.Complete(id);

    // UX Logic - explicitly knowing every part of the page that needs an update
    var builder = SwapResponse();
    builder.AlsoUpdate("task-list", "_List", _service.GetTasks()); // Update List
    builder.AlsoUpdate("sidebar-stats", "_Stats", _service.GetStats()); // Update Sidebar
    builder.AlsoUpdate("header-alert", "_Alert", "Done!"); // Show Alert
    
    return builder.Build(); // The controller is now coupled to 3 separate UI widgets.
}

The Solution: Event-Driven Responses

Swap.Htmx allows you to invert this dependency. The Controller triggers an Event, and independent Handlers decide how to update the UI.


Pattern: Distributed Handlers

1. Define the Event

Simple C# classes representing what happened.

public record TaskCompletedEvent(int TaskId);

2. Updates in Isolation

Create small, focused handlers for each UI component.

// Handler 1: The List Component removes the item
[SwapHandler]
public class TaskListHandler : ISwapEventHandler<TaskCompletedEvent>
{
    public Task HandleAsync(TaskCompletedEvent evt, SwapResponseBuilder builder, CancellationToken ct)
    {
        // Tell HTMX to remove the element with this ID
        builder.AlsoUpdate(SwapElements.TaskRow(evt.TaskId), "", null, SwapMode.Delete);
        return Task.CompletedTask;
    }
}

// Handler 2: The Sidebar Component updates its counter
[SwapHandler]
public class SidebarHandler : ISwapEventHandler<TaskCompletedEvent>
{
    public Task HandleAsync(TaskCompletedEvent evt, SwapResponseBuilder builder, CancellationToken ct)
    {
        var stats = _repo.GetStats();
        builder.AlsoUpdate(SwapElements.SidebarStats, SwapViews.Sidebar._Stats, stats);
        return Task.CompletedTask;
    }
}

3. The Clean Controller

The controller simply announces what happened.

[HttpPost]
public IActionResult Complete(int id)
{
    _service.Complete(id);
    return SwapEvent(new TaskCompletedEvent(id)).Build();
}

Benefits

  1. Single Responsibility: Controllers focus on user intent, Handlers focus on UI updates.
  2. Scalability: Add a new UI widget? Just add a new Handler. No need to touch the Controller.
  3. Testing: Unit test your handlers in isolation to verify they update the correct target IDs.

Source Generators

Use our Source Generators to avoid magic strings for Event Names and Element IDs.

// Auto-generated constants
builder.AlsoUpdate(SwapElements.SidebarStats, ...);