Handler Registration
How Mocha discovers message bus handlers at compile time using a Roslyn source generator, and how to customize module naming, mix auto and manual registration, and resolve analyzer diagnostics.
builder.Services .AddMessageBus() .AddOrderService(); // source-generated from your assembly name
Handler registration
That registers the message bus, discovers your handlers and sagas at compile time, and wires up the registration. .AddOrderService() is a source-generated extension method - it knows your handler types at compile time and produces direct registration calls with no reflection.
How the source generator works
At build time, the Mocha.Analyzers package runs a Roslyn incremental source generator that scans your assembly for classes implementing message bus handler interfaces. For each handler it finds, it emits a registration call in a generated extension method on IMessageBusHostBuilder.
The generator discovers these types:
| Interface | Pattern |
|---|---|
IEventHandler<T> | Pub/sub events |
IEventRequestHandler<TReq, TRes> | Request/reply |
IEventRequestHandler<TReq> | Send (fire-and-forget) |
IBatchEventHandler<T> | Batch processing |
IConsumer<T> | Low-level consumer |
Saga<TState> | Saga orchestration |
If you have used the Mediator source generator, this works the same way. The mediator generates Add{ModuleName}() on IMediatorHostBuilder; the message bus generates Add{ModuleName}() on IMessageBusHostBuilder.
Recommendation: Always use the source generator for handler registration. The generated code uses optimized, reflection-free registration paths. The source-generated output is designed for long-term stability across versions. Manual registration methods are available for edge cases but their internal behavior may change between releases.
Module naming
The source generator names the extension method based on your assembly:
- If you apply
[assembly: MessagingModule("OrderService")], the method isAddOrderService() - Otherwise, it uses the last segment of the assembly name:
MyCompany.OrderService.ApiproducesAddApi()
To set an explicit module name, add the attribute to any file in your project:
using Mocha;
[assembly: MessagingModule("OrderService")]
This generates:
builder.Services .AddMessageBus() .AddOrderService() // from [assembly: MessagingModule("OrderService")] .AddRabbitMQ();
Convention: Use a short, meaningful name that identifies the service or bounded context -
OrderService,Billing,Inventory. This name appears in the generated code and in yourProgram.cs, so keep it readable.
What the generator produces
For a project named OrderService with an event handler, a request handler, and a saga, the source generator produces an extension method AddOrderService() on IMessageBusHostBuilder. This method registers all discovered handlers and sagas with optimized, reflection-free factory delegates.
Handlers are grouped by kind and ordered alphabetically within each group. The registration order is: batch handlers, consumers, request handlers, event handlers, sagas.
Note: The generated code is an implementation detail and may change between versions. Do not depend on the shape of the generated output.
Manual handler registration
When you need to register handlers outside the source generator's reach - from a plugin assembly, a dynamically loaded module, or in integration tests - use the explicit registration methods:
builder.Services .AddMessageBus() .AddOrderService() // source-generated handlers .AddEventHandler<ExternalNotificationHandler>() // from another assembly .AddRequestHandler<PluginPaymentHandler>() // from a plugin .AddRabbitMQ();
You can mix source-generated and manual registration freely. If both the source generator and manual code register the same handler type, the configurations are composed - the source generator sets up the base registration and your manual call layers additional configuration (such as consumer middleware) on top.
Prefer the source generator. Manual registration methods use runtime reflection to create handler consumers. The source generator produces direct, reflection-free factory calls. We guarantee backwards compatibility for the source-generated registration path; the manual registration API is stable at the surface level but its internal behavior may evolve.
Troubleshooting
The source-generated method does not appear
If IntelliSense does not show Add{ModuleName}():
- Confirm the
Mocha.Analyzerspackage is referenced withOutputItemType="Analyzer"in your.csproj - Rebuild the project - source generators run during compilation
- Check the build output for analyzer diagnostics prefixed with
MO - Verify you have at least one concrete handler class in the assembly
Handler is not being called
If the source-generated method is available but a specific handler does not run:
- Check for MO0013 (abstract handler) - only concrete classes are registered
- Check for MO0012 (open generic) - close the generic type
- Verify the handler implements the correct interface for the messaging pattern you are using
- Ensure the handler is in the same project that references
Mocha.Analyzers
Priority when a handler implements multiple interfaces
If a class implements more than one messaging interface (e.g., both IBatchEventHandler<T> and IEventHandler<T>), the source generator registers it using the highest-priority interface only:
IBatchEventHandler > IConsumer > IEventRequestHandler<T,R> > IEventRequestHandler<T> > IEventHandler
Next steps
- Handlers and Consumers - handler interfaces, DI scoping, and exception behavior
- Routing and Endpoints - how the bus routes messages to registered handlers
- Sagas - saga state machines and long-running workflows
- Mediator - the mediator uses the same source generation approach for in-process CQRS