Skip to content

Commit 2566c9d

Browse files
committed
Merge branch 'main' into refactoring
2 parents 86cde50 + f4af953 commit 2566c9d

File tree

1 file changed

+58
-12
lines changed

1 file changed

+58
-12
lines changed

README.md

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ services.AddMediator();
3333

3434
## 🧩 Simple Handler Example
3535

36-
Just add any class ending with `Handler` or `Consumer`:
36+
Just add a class (instance or static) ending with `Handler` or `Consumer`. Methods must be named `Handle(Async)` or `Consume(Async)`. First parameter is required and is always the message. Supports multiple handler methods in a single class—for example, a `UserHandler` containing handlers for all CRUD messages.
3737

3838
```csharp
3939
public record Ping(string Text);
@@ -70,32 +70,39 @@ public class EmailHandler
7070

7171
## 🎪 Simple Middleware Example
7272

73-
Discovered by convention; static or instance with DI:
73+
Just add a class (instance or static) ending with `Middleware`. Supports `Before(Async)`, `After(Async)` and `Finally(Async)` lifecycle events. First parameter is required and is always the message. Use `object` for all message types or an interface for a subset of messages. `HandlerResult` can be returned from the `Before` lifecycle method to enable short-circuiting message handling. Other return types from `Before` will be available as parameters to `After` and `Finally`.
7474

7575
```csharp
7676
public static class ValidationMiddleware
7777
{
78-
public static HandlerResult Before(object msg)
79-
=> MiniValidator.TryValidate(msg, out var errs)
80-
? HandlerResult.Continue()
81-
: HandlerResult.ShortCircuit(Result.Invalid(errs));
78+
public static HandlerResult Before(object msg) {
79+
if (!TryValidate(msg, out var errors))
80+
{
81+
// short-circuit handler results when messages are invalid
82+
return HandlerResult.ShortCircuit(Result.Invalid(errors));
83+
}
84+
85+
return HandlerResult.Continue();
86+
}
8287
}
8388
```
8489

8590
## 📝 Logging Middleware Example
8691

8792
```csharp
88-
public class LoggingMiddleware
93+
public class LoggingMiddleware(ILogger<LoggingMiddleware> log)
8994
{
95+
// Stopwatch will be available as a parameter in `Finally` method
9096
public Stopwatch Before(object msg) => Stopwatch.StartNew();
9197

98+
// Finally causes before, handler and after to be run in a try catch and is guaranteed to run
9299
public void Finally(object msg, Stopwatch sw, Exception? ex)
93100
{
94101
sw.Stop();
95102
if (ex != null)
96-
Console.WriteLine($"Error in {msg.GetType().Name}: {ex.Message}");
103+
log.LogInformation($"Error in {msg.GetType().Name}: {ex.Message}");
97104
else
98-
Console.WriteLine($"Handled {msg.GetType().Name} in {sw.ElapsedMilliseconds}ms");
105+
log.LogInformation($"Handled {msg.GetType().Name} in {sw.ElapsedMilliseconds}ms");
99106
}
100107
}
101108
```
@@ -105,7 +112,7 @@ public class LoggingMiddleware
105112
Result\<T> is our built-in discriminated union for message-oriented workflows, capturing success, validation errors, conflicts, not found states, and more—without relying on exceptions.
106113

107114
```csharp
108-
public class GetUserHandler
115+
public class UserHandler
109116
{
110117
public async Task<Result<User>> HandleAsync(GetUser query) {
111118
var user = await _repo.Find(query.Id);
@@ -115,6 +122,19 @@ public class GetUserHandler
115122
// implicitly converted to Result<User>
116123
return user;
117124
}
125+
126+
public async Task<Result<User>> HandleAsync(CreateUser cmd)
127+
{
128+
var user = new User {
129+
Id = Guid.NewGuid(),
130+
Name = cmd.Name,
131+
Email = cmd.Email,
132+
CreatedAt = DateTime.UtcNow
133+
};
134+
135+
await _repo.AddAsync(user);
136+
return user;
137+
}
118138
}
119139
```
120140

@@ -160,7 +180,7 @@ await mediator.PublishAsync(new OrderShipped(orderId));
160180

161181
Foundatio.Mediator delivers exceptional performance, getting remarkably close to direct method calls while providing full mediator pattern benefits:
162182

163-
### Commands (Fire-and-Forget)
183+
### Commands
164184

165185
| Method | Mean | Error | StdDev | Gen0 | Allocated | vs Direct |
166186
|-------------------------------|-------------|-----------|-----------|--------|-----------|-----------|
@@ -180,7 +200,7 @@ Foundatio.Mediator delivers exceptional performance, getting remarkably close to
180200

181201
### 🎯 Key Performance Insights
182202

183-
- **🚀 Near-Optimal Performance**: Only **2.05x overhead** for commands and **1.78x overhead** for queries compared to direct method calls
203+
- **🚀 Near-Optimal Performance**: Only slight overhead vs direct method calls
184204
- **⚡ Foundatio vs MediatR**: **3.08x faster** for commands, **1.96x faster** for queries
185205
- **� Foundatio vs MassTransit**: **90x faster** for commands, **195x faster** for queries
186206
- **💾 Zero Allocation Commands**: Fire-and-forget operations have no GC pressure
@@ -219,6 +239,32 @@ Valid handler method names:
219239
- Known parameters: `CancellationToken` is automatically provided by the mediator
220240
- Service resolution: All other parameters are resolved from the DI container
221241

242+
### Ignoring Handlers
243+
244+
- Annotate handler classes or methods with `[FoundatioIgnore]` to exclude them from discovery
245+
246+
## 🎪 Middleware Conventions
247+
248+
- Classes should end with `Middleware`
249+
- Valid method names:
250+
- `Before(...)` / `BeforeAsync(...)`
251+
- `After(...)` / `AfterAsync(...)`
252+
- `Finally(...)` / `FinallyAsync(...)`
253+
- First parameter must be the message (can be `object`, an interface, or a concrete type)
254+
- Lifecycle methods are optional—you can implement any subset (`Before`, `After`, `Finally`)
255+
- `Before` can return:
256+
- a `HandlerResult` to short-circuit execution
257+
- a single state value
258+
- a tuple of state values
259+
- Values (single or tuple elements) returned from `Before` are matched by type and injected into `After`/`Finally` parameters
260+
- `After` runs only on successful handler completion
261+
- `Finally` always runs, regardless of success or failure
262+
- Methods may declare additional parameters: `CancellationToken`, DI-resolved services
263+
264+
### Ignoring Middleware
265+
266+
- Annotate middleware classes or methods with `[FoundatioIgnore]` to exclude them from discovery
267+
222268
## 🔧 API Reference
223269

224270
### IMediator Interface

0 commit comments

Comments
 (0)