Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ServiceComposer/ServiceComposer.AspNetCore/llms.txt

Use this file to discover all available pages before exploring further.

Composition handlers run independently and are deliberately designed to be unaware of each other. However, some scenarios require coordination — the most common being the composition of lists. When building a page that shows a list of products, one handler might supply identifiers while another needs to enrich each item with additional data. Events provide a lightweight, in-process signalling mechanism that keeps handlers decoupled while still allowing them to react to each other’s output.
Composing lists of composed elements (master-detail type outputs) is one of the primary motivations for the events API. Events are synchronous and in-memory — they are not serializable and do not cross process boundaries.

Defining an event

Events are plain .NET types — classes or records. There are no base classes or interfaces to implement:
public record AnEvent(string SomeValue);

Publishing events

A handler publishes an event by first obtaining the ICompositionContext from the request, then calling RaiseEvent:
public class EventPublishingHandler : ICompositionRequestsHandler
{
    [HttpGet("/route-based-handler/{some-id}")]
    public async Task Handle(HttpRequest request)
    {
        var context = request.GetCompositionContext();
        await context.RaiseEvent(new AnEvent(SomeValue: "This is the value"));
    }
}
RaiseEvent is awaitable. All subscribers registered for the event type are invoked synchronously (in-process) before RaiseEvent returns.

Subscribing to events

ServiceComposer provides two subscription APIs with different scopes.
Implement ICompositionEventsHandler<TEvent> to subscribe to an event across all routes. The assembly scanner discovers these classes at startup and registers them in the DI container as transient components — so they fully support constructor injection.
public class GenericEventHandler : ICompositionEventsHandler<AnEvent>
{
    public Task Handle(AnEvent @event, HttpRequest request)
    {
        // handle the event
        return Task.CompletedTask;
    }
}
This handler is invoked every time AnEvent is raised, regardless of which route is being handled.

Interface contracts

The three interfaces involved in event-based coordination are:
public interface ICompositionEventsHandler<in TEvent>
{
    Task Handle(TEvent @event, HttpRequest request);
}

public interface ICompositionEventsSubscriber
{
    void Subscribe(ICompositionEventsPublisher publisher);
}

public interface ICompositionEventsPublisher
{
    void Subscribe<TEvent>(CompositionEventHandler<TEvent> handler);
}

When to use which

ICompositionEventsHandler<TEvent>

Use when: the same event is handled identically across all routes. Discovery is automatic; the handler benefits from DI and can have scoped or transient dependencies injected via the constructor.

ICompositionEventsSubscriber

Use when: the same event requires different behaviour depending on the current route (e.g. GET vs POST), or when you only want to react to the event on a subset of routes. The [Http*] attribute provides fine-grained control.
If the same event is published during both a GET and a POST to different endpoints and your logic must differ, prefer ICompositionEventsSubscriber with separate [HttpGet] and [HttpPost] decorated classes rather than branching inside a generic handler.

How it all fits together

The following example shows the full flow: a publishing handler raises an event, and a route-scoped subscriber reacts to it, both targeting the same route:
// 1. The event type
public record AnEvent(string SomeValue);

// 2. A handler that publishes the event
public class EventPublishingHandler : ICompositionRequestsHandler
{
    [HttpGet("/route-based-handler/{some-id}")]
    public async Task Handle(HttpRequest request)
    {
        var context = request.GetCompositionContext();
        await context.RaiseEvent(new AnEvent(SomeValue: "This is the value"));
    }
}

// 3. A route-scoped subscriber that reacts to it
public class RouteBasedEventHandler : ICompositionEventsSubscriber
{
    [HttpGet("/route-based-handler/{some-id}")]
    public void Subscribe(ICompositionEventsPublisher publisher)
    {
        publisher.Subscribe<AnEvent>((@event, request) =>
        {
            // handle the event
            return Task.CompletedTask;
        });
    }
}
Both classes are discovered automatically via assembly scanning — no manual registration is needed.

Build docs developers (and LLMs) love