Skip to content

Commit 27bfd62

Browse files
committed
Update readme
1 parent 27408c1 commit 27bfd62

File tree

1 file changed

+89
-218
lines changed

1 file changed

+89
-218
lines changed

README.md

Lines changed: 89 additions & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -1,142 +1,137 @@
11
# Foundatio.Mediator
22

3-
**The fastest convention-based C# mediator library with source generators**
43

54
[![NuGet](https://img.shields.io/nuget/v/Foundatio.Mediator.svg)](https://www.nuget.org/packages/Foundatio.Mediator/)
6-
[![Performance](https://img.shields.io/badge/performance-2x%20close%20to%20direct%20calls-brightgreen)](#performance-benchmarks)
7-
[![Memory](https://img.shields.io/badge/memory-zero%20allocation-blue)](#performance-benchmarks)
85

9-
Foundatio.Mediator is a high-performance, convention-based mediator library that leverages C# source generators and cutting-edge interceptors to achieve near-direct method call performance. No interfaces, no base classes, no reflection at runtime—just clean, simple code that flies.
6+
Blazingly fast, convention-based C# mediator powered by source generators and interceptors.
107

11-
## ✨ Why Choose Foundatio.Mediator?
8+
## High-Level Features
129

13-
- **🚀 Blazing Fast** - Nearly as fast as direct method calls, 3x faster than MediatR
14-
- **🎯 Convention-Based** - No interfaces or base classes required
15-
- **⚡ Source Generated** - Compile-time code generation for optimal performance
16-
- **🔧 Full DI Integration** - Works seamlessly with Microsoft.Extensions.DependencyInjection
17-
- **🎪 Middleware Pipeline** - Elegant middleware support
18-
- **� Cascading Messages** - Tuple returns automatically trigger follow-up events
19-
- **�📦 Auto Registration** - Handlers discovered and registered automatically
20-
- **🔒 Compile-Time Safety** - Rich diagnostics catch errors before runtime
21-
- **🔧 C# Interceptors** - Direct method calls using cutting-edge C# interceptor technology
22-
- **🎯 Built-in Result Type** - Comprehensive discriminated union for handling all operation outcomes
10+
- 🚀 Near-direct call performance, zero runtime reflection
11+
- ⚡ Convention-based handler discovery (no interfaces/base classes)
12+
- 🔧 Full DI support via Microsoft.Extensions.DependencyInjection
13+
- 🧩 Plain handler classes or static methods—just drop them in
14+
- 🎪 Middleware pipeline with Before/After/Finally hooks
15+
- 🎯 Built-in Result and Result\<T> types for rich status handling
16+
- 🔄 Automatic cascading messages via tuple returns
17+
- 🔒 Compile-time diagnostics and validation
18+
- 📦 Auto-registration with no boilerplate
2319

24-
## 🚀 Quick Start
20+
## 1. Simple Handler
2521

26-
### 1. Install the Package
22+
Just add any class ending with `Handler` or `Consumer`:
2723

28-
```bash
29-
dotnet add package Foundatio.Mediator
24+
```csharp
25+
public record Ping(string Text);
26+
27+
public static class PingHandler
28+
{
29+
public static string Handle(Ping msg) => $"Pong: {msg.Text}";
30+
}
3031
```
3132

32-
### 2. Register the Mediator
33+
Call it:
3334

3435
```csharp
35-
services.AddMediator();
36+
var reply = mediator.Invoke<string>(new Ping("Hello"));
3637
```
3738

38-
### 3. Create Clean, Simple Handlers with Built-in Result Types
39+
## 2. Dependency Injection in Handlers
40+
41+
Supports constructor and method injection:
3942

4043
```csharp
41-
// Messages (any class/record)
42-
public record CreateUserCommand
44+
public class EmailHandler
4345
{
44-
[Required, StringLength(50, MinimumLength = 2)]
45-
public string Name { get; set; } = string.Empty;
46-
47-
[Required, EmailAddress]
48-
public string Email { get; set; } = string.Empty;
46+
private readonly IEmailService _svc;
47+
public EmailHandler(IEmailService svc) => _svc = svc;
4948

50-
[Range(18, 120)]
51-
public int Age { get; set; }
49+
public Task HandleAsync(SendEmail cmd, ILogger<EmailHandler> log, CancellationToken ct)
50+
{
51+
log.LogInformation("Sending to {To}", cmd.To);
52+
return _svc.SendAsync(cmd.To, cmd.Subject, cmd.Body, ct);
53+
}
5254
}
55+
```
5356

54-
public record User(int Id, string Name, string Email, int Age, DateTime CreatedAt);
57+
## 3. Simple Middleware
5558

56-
// Handlers return Result<T> for comprehensive status handling
57-
public class CreateUserCommandHandler
59+
Discovered by convention; static or instance with DI:
60+
61+
```csharp
62+
public static class ValidationMiddleware
5863
{
59-
public async Task<Result<User>> HandleAsync(CreateUserCommand command, CancellationToken cancellationToken = default)
60-
{
61-
// Business logic validation
62-
if (command.Email == "existing@example.com")
63-
return Result.Conflict("A user with this email already exists");
64+
public static HandlerResult Before(object msg)
65+
=> MiniValidator.TryValidate(msg, out var errs)
66+
? HandlerResult.Continue()
67+
: HandlerResult.ShortCircuit(Result.Invalid(errs));
68+
}
69+
```
6470

65-
// Create the user
66-
var user = new User(
67-
Id: Random.Shared.Next(1000, 9999),
68-
Name: command.Name,
69-
Email: command.Email,
70-
Age: command.Age,
71-
CreatedAt: DateTime.UtcNow
72-
);
71+
## 4. Logging Middleware Example
7372

74-
return user; // Implicit conversion to Result<User>
73+
```csharp
74+
public class LoggingMiddleware
75+
{
76+
public Stopwatch Before(object msg) => Stopwatch.StartNew();
77+
78+
public void Finally(object msg, Stopwatch sw, Exception? ex)
79+
{
80+
sw.Stop();
81+
if (ex != null)
82+
Console.WriteLine($"Error in {msg.GetType().Name}: {ex.Message}");
83+
else
84+
Console.WriteLine($"Handled {msg.GetType().Name} in {sw.ElapsedMilliseconds}ms");
7585
}
7686
}
7787
```
7888

79-
### 4. Use the Mediator with Result Pattern
89+
## 5. Using Result<T>
8090

8191
```csharp
82-
var mediator = serviceProvider.GetRequiredService<IMediator>();
83-
84-
// Create a user with comprehensive result handling
85-
var result = await mediator.InvokeAsync<Result<User>>(new CreateUserCommand
86-
{
87-
Name = "John Doe",
88-
Email = "john@example.com",
89-
Age = 30
90-
});
91-
92-
// Handle different outcomes with pattern matching
93-
var response = result.Status switch
92+
public class GetUserHandler
9493
{
95-
ResultStatus.Ok => $"User created successfully: {result.Value.Name}",
96-
ResultStatus.Invalid => $"Validation failed: {string.Join(", ", result.Errors.Select(e => e.ErrorMessage))}",
97-
ResultStatus.Conflict => $"Conflict: {result.ErrorMessage}",
98-
_ => "Unexpected result"
99-
};
100-
101-
Console.WriteLine(response);
94+
public Task<Result<User>> HandleAsync(GetUser cmd)
95+
=> _repo.Find(cmd.Id) is { } user
96+
? Task.FromResult(Result.Ok(user))
97+
: Task.FromResult(Result.NotFound($"User {cmd.Id} not found"));
98+
}
10299
```
103100

104-
## 🎯 Built-in Result Type - Essential for Message-Oriented Architecture
101+
## 6. Invocation API
105102

106-
Foundatio.Mediator includes a comprehensive `Result` and `Result<T>` type that acts as a discriminated union, allowing handlers to return different operation outcomes without exceptions. This is crucial for message-oriented architectures where you need to handle various scenarios gracefully.
103+
```csharp
104+
// With response
105+
var user = await mediator.InvokeAsync<User>(new GetUser(id));
107106

108-
### Why Result Types Matter
107+
// Without response
108+
await mediator.InvokeAsync(new Ping("Hi"));
109+
```
109110

110-
In message-oriented systems, operations can have many outcomes beyond just success/failure:
111+
## 7. Tuple Returns & Cascading Messages
111112

112-
- **Success** with data
113-
- **Validation errors** with detailed field-level messages
114-
- **Business rule violations** (conflicts, unauthorized access)
115-
- **Not found** scenarios
116-
- **Created** responses with location information
113+
Handlers can return tuples; one matches the response, the rest are published:
117114

118-
The Result type captures all these scenarios in a type-safe way without throwing exceptions.
115+
```csharp
116+
public async Task<(User user, UserCreated evt)> HandleAsync(CreateUser cmd)
117+
{
118+
var user = await _repo.Add(cmd);
119+
return (user, new UserCreated(user.Id));
120+
}
119121

120-
### Result Creation Methods
122+
// Usage
123+
var user = await mediator.InvokeAsync<User>(new CreateUser(...));
124+
// UserCreated is auto-published
125+
```
126+
127+
## 8. Publish API
121128

122129
```csharp
123-
// Success results
124-
var user = new User(1, "John", "john@example.com", 30, DateTime.UtcNow);
125-
return user; // Implicit conversion to Result<User>
126-
return Result.Ok(user); // Explicit success
127-
return Result.Created(user, "/api/users/1"); // Created with location
128-
129-
// Error results
130-
return Result.NotFound("User not found");
131-
return Result.Invalid(validationErrors);
132-
return Result.Conflict("Email already exists");
133-
return Result.Unauthorized("Login required");
134-
135-
// Generic results (non-generic Result class)
136-
return Result.Ok(); // Success with no return value
137-
return Result.NoContent(); // Success with no content
130+
await mediator.PublishAsync(new OrderShipped(orderId));
138131
```
139132

133+
All handlers run in parallel; if any fail, PublishAsync throws.
134+
140135
### Example: Complete CRUD with Result Types
141136

142137
```csharp
@@ -273,107 +268,6 @@ public class ComplexOrderHandler
273268
}
274269
```
275270

276-
## 🎪 Beautiful Middleware Pipeline
277-
278-
Create elegant middleware that runs before, after, and finally around your handlers. Middleware works seamlessly with the Result type for comprehensive error handling:
279-
280-
```csharp
281-
public class LoggingMiddleware
282-
{
283-
private readonly ILogger<LoggingMiddleware> _logger;
284-
285-
public LoggingMiddleware(ILogger<LoggingMiddleware> logger)
286-
{
287-
_logger = logger;
288-
}
289-
290-
public Stopwatch Before(object message)
291-
{
292-
_logger.LogInformation("Processing {MessageType}", message.GetType().Name);
293-
return Stopwatch.StartNew();
294-
}
295-
296-
public void Finally(object message, Stopwatch stopwatch, Exception? exception)
297-
{
298-
stopwatch.Stop();
299-
if (exception != null)
300-
{
301-
_logger.LogError(exception, "Error processing {MessageType}", message.GetType().Name);
302-
}
303-
else
304-
{
305-
_logger.LogInformation("Completed {MessageType} in {ElapsedMs}ms",
306-
message.GetType().Name, stopwatch.ElapsedMilliseconds);
307-
}
308-
}
309-
}
310-
311-
public static class ValidationMiddleware
312-
{
313-
public static HandlerResult Before(object message)
314-
{
315-
if (MiniValidator.TryValidate(message, out var errors))
316-
return HandlerResult.Continue();
317-
318-
var validationErrors = errors.SelectMany(kvp =>
319-
kvp.Value.Select(errorMessage => new ValidationError(kvp.Key, errorMessage))).ToList();
320-
321-
return HandlerResult.ShortCircuit(Result.Invalid(validationErrors));
322-
}
323-
}
324-
```
325-
326-
With this middleware, your handlers automatically get validation without any boilerplate:
327-
328-
```csharp
329-
// This command will be automatically validated by ValidationMiddleware
330-
var result = await mediator.InvokeAsync<Result<User>>(new CreateUserCommand
331-
{
332-
Name = "", // Invalid - too short
333-
Email = "not-an-email", // Invalid - bad format
334-
Age = 10 // Invalid - too young
335-
});
336-
337-
if (result.Status == ResultStatus.Invalid)
338-
{
339-
foreach (var error in result.Errors)
340-
Console.WriteLine($"{error.PropertyName}: {error.ErrorMessage}");
341-
}
342-
```
343-
344-
## 💉 Dependency Injection Made Simple
345-
346-
Handlers support both constructor and method-level dependency injection:
347-
348-
```csharp
349-
public class SendWelcomeEmailHandler
350-
{
351-
private readonly IEmailService _emailService;
352-
private readonly IGreetingService _greetingService;
353-
private readonly ILogger<SendWelcomeEmailHandler> _logger;
354-
355-
public SendWelcomeEmailHandler(
356-
IEmailService emailService,
357-
IGreetingService greetingService,
358-
ILogger<SendWelcomeEmailHandler> logger)
359-
{
360-
_emailService = emailService;
361-
_greetingService = greetingService;
362-
_logger = logger;
363-
}
364-
365-
public async Task HandleAsync(
366-
SendWelcomeEmailCommand command,
367-
CancellationToken cancellationToken = default) // Provided by mediator
368-
{
369-
_logger.LogInformation("Sending welcome email to {Email}", command.Email);
370-
371-
var greeting = _greetingService.CreateGreeting(command.Name);
372-
await _emailService.SendEmailAsync(command.Email, "Welcome!", greeting);
373-
}
374-
}
375-
```
376-
377271
## 📊 Performance Benchmarks
378272

379273
Foundatio.Mediator delivers exceptional performance, getting remarkably close to direct method calls while providing full mediator pattern benefits:
@@ -454,30 +348,9 @@ public interface IMediator
454348

455349
// Publishing (multiple handlers)
456350
Task PublishAsync(object message, CancellationToken cancellationToken = default);
457-
void Publish(object message, CancellationToken cancellationToken = default);
458351
}
459352
```
460353

461-
## 🎬 Sample Applications
462-
463-
### ConsoleSample - Comprehensive Demonstration
464-
465-
The `samples/ConsoleSample` project provides a complete demonstration of all Foundatio.Mediator features:
466-
467-
- **Simple Commands & Queries** - Basic fire-and-forget and request/response patterns
468-
- **Dependency Injection** - Handler methods with injected services and logging
469-
- **Publish/Subscribe** - Multiple handlers for the same event
470-
- **Mixed Sync/Async** - Both synchronous and asynchronous handler examples
471-
- **Middleware Pipeline** - Global and message-specific middleware examples
472-
- **Service Integration** - Email, SMS, and audit service examples
473-
474-
To run the comprehensive sample:
475-
476-
```bash
477-
cd samples/ConsoleSample
478-
dotnet run
479-
```
480-
481354
## ⚙️ How It Works
482355

483356
The source generator:
@@ -516,8 +389,6 @@ foreach (var handler in handlers)
516389

517390
- **Interceptors First** - Same-assembly calls use interceptors for maximum performance
518391
- **DI Fallback** - Cross-assembly handlers and publish operations use DI registration
519-
- **Automatic Selection** - The generator chooses the optimal strategy per call site
520-
- **Keyed Services** - Handlers registered by fully qualified message type name
521392
- **Zero Runtime Overhead** - Interceptors bypass all runtime lookup completely
522393

523394
**Benefits:**

0 commit comments

Comments
 (0)