Overview
The three responsibilities Elarion separates, and why auto-detection is the default.
Elarion separates an application into three responsibilities and uses compile-time generation to connect them. Understanding this split makes every other concept fall into place.
The three responsibilities
- Application module composition lives beside the feature code. A module decides which handlers, validators, JSON metadata, and module-owned services it exposes.
- Compile-time discovery replaces reflection-heavy startup scanning. Generators emit deterministic registration methods from attributes and conventions.
- Platform wiring stays in the host. The host owns
WebApplication, authentication, middleware, capability providers, database setup, telemetry exporters, and endpoint publication.
This keeps modules independent of the final host while still allowing real platform abstractions
where they matter. For example, modules may declare Minimal API endpoints with IEndpointRouteBuilder
directly, so ASP.NET Core endpoint conventions and source generation still work.
Why auto-detection is the default
Elarion prefers declaring application intent near the type over maintaining a second registration
list elsewhere. A handler already says "I handle this request"; a validator already says "I validate
this command"; a [Service] class already says "this is a module service". Repeating those facts in
Program.cs or hand-written AddXyz() methods creates a parallel model that drifts from the code it
describes.
This buys practical advantages:
- The module owns its application surface. Adding a handler, validator, JSON context, or service
under a module namespace is enough to publish it through generated
Add{Module}…()methods. - Registration drift becomes a compile-time problem. Missing trigger attributes, invalid service contracts, wrong hosted-service lifetimes, and unsupported generic services produce generator diagnostics instead of startup surprises.
- The host stops knowing feature internals. It wires platform capabilities and transports; it does not remember every application handler or module service.
- Generated code stays inspectable. The result is ordinary DI registration code, emitted deterministically rather than hidden behind runtime reflection.
- Refactoring follows structure. Moving a type between modules changes ownership through namespace containment; deleting a type deletes its registration on the next build.
- AOT and startup behavior are predictable. Compile-time discovery avoids broad runtime scans and keeps the dependency graph visible to the compiler and linker.
Explicit registration still has a place when explicitness is the point: database contexts, external clients, provider-specific decorators, authentication, telemetry, and concrete capability providers should be wired by the platform. The bias is auto-detect application patterns, explicitly wire platform capabilities.
Developers coming from Spring Boot will find the model familiar — components under a namespace boundary, annotations that declare their role, defaults for the common path — implemented with .NET source generation rather than runtime classpath scanning.
The trade-off
You accept conventions. Handler names, nested Command/Query and Response types, module
namespace containment, and pipeline attributes matter because generators use them. In exchange you
get less host boilerplate, inherent modularity, fewer runtime scans, and a clear separation between
application policy and host mechanics. The full comparison with idiomatic ASP.NET Core is in
How Elarion differs.
The concepts
The pages in this section cover each building block:
Handlers
Results & errors
Result<T> and transport-agnostic AppError.Modules
Services
[Service] registration and hosted services.