You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
> **Prefer explicit interfaces?** Use `IHandler` marker interface or `[Handler]` attributes instead. See [Handler Conventions](https://mediator.foundatio.dev/guide/handler-conventions.html#explicit-handler-declaration).
Copy file name to clipboardExpand all lines: docs/guide/handler-conventions.md
+24Lines changed: 24 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,30 @@
2
2
3
3
Foundatio Mediator uses simple naming conventions to automatically discover handlers at compile time. This eliminates the need for interfaces, base classes, or manual registration while providing excellent compile-time validation.
4
4
5
+
## Why Conventions?
6
+
7
+
Some developers initially feel that convention-based discovery is "too magical." But consider: **all abstractions are magic until you learn them.**
8
+
9
+
- ASP.NET discovers `*Controller` classes automatically
10
+
- Entity Framework treats `Id` properties as primary keys
11
+
- xUnit runs methods decorated with `[Fact]`
12
+
- C# records generate equality, `ToString()`, and more from a single line
13
+
14
+
The real question isn't "is it magic?" but **"does the magic help or hurt?"**
15
+
16
+
Convention-based handlers provide tangible benefits over traditional interface-based approaches:
17
+
18
+
| Benefit | Convention-Based | Interface-Based |
19
+
| ------- | ---------------- | --------------- |
20
+
|**Multiple handlers per class**| ✅ Natural grouping | ❌ One interface per handler |
21
+
|**Flexible method signatures**| ✅ Any params via DI | ❌ Fixed `Handle(TRequest, CancellationToken)`|
22
+
|**Sync or async**| ✅ Return `void`, `T`, `Task<T>`, etc. | ❌ Must return `Task<T>`|
23
+
|**Static handlers**| ✅ Zero allocation | ❌ Always instance-based |
|**Boilerplate**| ✅ Just a class + methods | ❌ Interface inheritance per handler |
26
+
27
+
**Prefer explicit declaration?** Use `IHandler` or `[Handler]` attributes. See [Explicit Handler Declaration](#explicit-handler-declaration).
28
+
5
29
Alternatively, you can mark handlers explicitly using the `IHandler` marker interface or the `[Handler]` attribute. See [Explicit Handler Declaration](#explicit-handler-declaration) for details.
publicasyncTask<User>HandleAsync(GetUserq, IRepor) =>...; // Async with DI
68
+
public (Order, OrderCreated) Handle(CreateOrderc) =>...; // Cascading events
69
+
```
70
+
55
71
### 🔧 Seamless Dependency Injection
56
72
57
73
Full support for Microsoft.Extensions.DependencyInjection with both constructor and method injection:
@@ -134,7 +150,8 @@ public class LoggingMiddleware
134
150
-**Simple CRUD** applications with minimal business logic
135
151
-**Performance-critical** inner loops where even 10ns matters
136
152
-**Legacy codebases** that can't adopt modern .NET features
137
-
-**Teams resistant** to convention-based approaches
153
+
154
+
> **Note:** If you prefer explicit interfaces over conventions, Foundatio Mediator fully supports that too! Use `IHandler` marker interface or `[Handler]` attributes, and optionally disable conventional discovery. See [Handler Conventions](./handler-conventions#explicit-handler-declaration) for details.
The difference between "magic" and "convention" is simply **familiarity**. Once you learn a convention, it becomes invisible—you stop thinking about it.
61
+
62
+
**Why conventions are actually _better_ for mediator patterns:**
63
+
64
+
| Traditional Interfaces | Convention-Based |
65
+
| ---------------------- | ---------------- |
66
+
| Forces `IRequestHandler<TRequest, TResponse>` inheritance | Plain classes with any name ending in `Handler`|
67
+
| One handler method per class (or awkward multiple interface inheritance) | Multiple handler methods per class, naturally grouped |
68
+
| Fixed method signature: `Handle(TRequest, CancellationToken)`| Flexible signatures: sync/async, any parameters via DI |
69
+
| Must return `Task<TResponse>` even for sync operations | Return `void`, `Task`, `T`, `Task<T>`, `Result<T>`, tuples |
70
+
| Handler classes must be registered in DI | Auto-discovered at compile time |
71
+
| Runtime dispatch via reflection | Compile-time interceptors for near-direct call performance |
72
+
73
+
**With conventions, you get unprecedented flexibility:**
Built-in `Result<T>` types eliminate exception-driven control flow:
@@ -221,9 +298,10 @@ Compare this to complex reflection-based call stacks in other libraries.
221
298
222
299
-**Legacy .NET Framework** projects (requires modern .NET)
223
300
-**Simple CRUD applications** without complex business logic
224
-
-**Teams preferring explicit interfaces** over conventions
225
301
-**Existing MediatR codebases** where migration cost isn't justified
226
302
303
+
> **Note:** If your team prefers explicit interfaces over conventions, Foundatio Mediator supports that too! Use `IHandler` or `[Handler]` attributes and optionally disable conventional discovery entirely. See [Explicit Handler Declaration](./handler-conventions#explicit-handler-declaration).
0 commit comments