Skip to content

Commit c170d70

Browse files
committed
Update readme
1 parent 5bedda0 commit c170d70

File tree

3 files changed

+23
-189
lines changed

3 files changed

+23
-189
lines changed

README.md

Lines changed: 17 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -5,181 +5,33 @@
55
[![feedz.io](https://img.shields.io/endpoint?url=https%3A%2F%2Ff.feedz.io%2Ffoundatio%2Ffoundatio%2Fshield%2FFoundatio.Mediator%2Flatest)](https://f.feedz.io/foundatio/foundatio/packages/Foundatio.Mediator/latest/download)
66
[![Discord](https://img.shields.io/discord/715744504891703319)](https://discord.gg/6HxgFCx)
77

8-
Build completely message-oriented, loosely coupled .NET apps that are easy to test — with near-direct-call performance and zero boilerplate. Powered by source generators and interceptors.
8+
Foundatio Mediator is a high-performance mediator library for .NET that uses source generators and C# interceptors to achieve near-direct-call performance with zero runtime reflection. Build completely message-oriented, loosely coupled apps that are easy to test — with zero boilerplate.
99

10-
## Why Choose Foundatio Mediator?
10+
## Features
1111

12-
- 🚀 **Near-direct call performance** — zero runtime reflection, minimal overhead ([benchmarks](https://mediator.foundatio.dev/guide/performance.html))
13-
-**Convention-based** — no interfaces or base classes required
14-
- 🌐 **Auto-generated endpoints** — Minimal API endpoints from handlers, skip the mapping boilerplate
12+
- 🚀 **Near-direct call performance** — source generators and interceptors eliminate runtime reflection
13+
-**Convention-based discovery** — handlers discovered by naming conventions, no interfaces or base classes required
14+
- 🧩 **Plain handler classes** — sync or async, static or instance methods, any signature, multiple handlers per class
15+
- 🌐 **Auto-generated API endpoints** — Minimal API endpoints generated from handlers with route, method, and parameter binding inference
16+
- 📡 **Streaming handlers**`IAsyncEnumerable<T>` support with SSE endpoint generation
1517
- 🎯 **Built-in Result\<T>** — rich status handling without exceptions, auto-mapped to HTTP status codes
16-
- 🎪 **Middleware pipeline** — Before/After/Finally/Execute hooks with state passing
17-
- 🔄 **Cascading messages** — tuple returns auto-publish events
18-
- 🧩 **Plain handler classes**static or instance methods, any signature
19-
- 🔧 **Full DI support**Microsoft.Extensions.DependencyInjection integration
20-
- 🔒 **Compile-time safety**early validation and diagnostics
21-
- 🧪 **Easy testing** — plain objects, no framework coupling
22-
- 🐛 **Superior debugging** — short, simple call stacks
18+
- 🎪 **Middleware pipeline** — Before/After/Finally/Execute hooks with state passing and short-circuiting
19+
- 🔄 **Cascading messages** — tuple returns automatically publish follow-on events
20+
- 🔧 **Full DI support**constructor and method parameter injection via Microsoft.Extensions.DependencyInjection
21+
- 🔐 **Authorization**built-in attribute-based authorization with policy support
22+
- 🔒 **Compile-time safety**analyzer diagnostics catch misconfigurations before runtime
23+
- 🧪 **Easy testing** — plain objects with no framework coupling
24+
- 🐛 **Superior debugging** — short, readable call stacks
2325

24-
### Why Convention-Based?
25-
26-
Traditional mediator libraries force you into rigid interface contracts like `IRequestHandler<TRequest, TResponse>`. This means:
27-
28-
- Lots of boilerplate
29-
- Fixed method signatures
30-
- Always async (even for simple operations)
31-
- One handler class per message type
32-
33-
**Foundatio Mediator's conventions give you freedom:**
34-
35-
```csharp
36-
public class OrderHandler
37-
{
38-
// Sync handler - no async overhead
39-
public decimal Handle(CalculateTotal query) => query.Items.Sum(i => i.Price);
40-
41-
// Async with any DI parameters you need
42-
public async Task<Order> HandleAsync(GetOrder query, IOrderRepo repo, CancellationToken ct)
43-
=> await repo.FindAsync(query.Id, ct);
44-
45-
// Cascading: first element returned, rest auto-published as events
46-
public (Order order, OrderCreated evt) Handle(CreateOrder cmd) { /* ... */ }
47-
}
48-
49-
// Static handlers for maximum performance
50-
public static class MathHandler
51-
{
52-
public static int Handle(Add query) => query.A + query.B;
53-
}
54-
```
55-
56-
> **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).
57-
58-
## 🚀 Quick Start
26+
## 🚀 Get Started
5927

6028
```bash
6129
dotnet add package Foundatio.Mediator
6230
```
6331

64-
```csharp
65-
// Program.cs
66-
var builder = WebApplication.CreateBuilder(args);
67-
builder.Services.AddMediator();
68-
var app = builder.Build();
69-
app.MapMyAppEndpoints(); // generated — see "Auto-Generate API Endpoints" below
70-
app.Run();
71-
```
72-
73-
That's it for setup. Now define messages and handlers:
32+
**👉 [Getting Started Guide](https://mediator.foundatio.dev/guide/getting-started.html)** — step-by-step setup with code samples for ASP.NET Core and console apps.
7433

75-
```csharp
76-
// Messages (records, classes, anything)
77-
public record GetUser(int Id);
78-
public record CreateUser(string Name, string Email);
79-
public record UserCreated(int UserId, string Email);
80-
81-
// Handlers - just plain classes ending with "Handler" or "Consumer"
82-
public class UserHandler
83-
{
84-
public async Task<Result<User>> HandleAsync(GetUser query, IUserRepository repo)
85-
{
86-
var user = await repo.FindAsync(query.Id);
87-
return user ?? Result.NotFound($"User {query.Id} not found");
88-
}
89-
90-
public async Task<(User user, UserCreated evt)> HandleAsync(CreateUser cmd, IUserRepository repo)
91-
{
92-
var user = new User { Name = cmd.Name, Email = cmd.Email };
93-
await repo.AddAsync(user);
94-
95-
// Return tuple: first element is response, rest are auto-published
96-
return (user, new UserCreated(user.Id, user.Email));
97-
}
98-
}
99-
100-
// Event handlers
101-
public class EmailHandler
102-
{
103-
public async Task HandleAsync(UserCreated evt, IEmailService email)
104-
{
105-
await email.SendWelcomeAsync(evt.Email);
106-
}
107-
}
108-
109-
// Middleware - classes ending with "Middleware"
110-
public class LoggingMiddleware(ILogger<LoggingMiddleware> logger)
111-
{
112-
public Stopwatch Before(object message) => Stopwatch.StartNew();
113-
114-
// Objects or tuples returned from the Before method are available as parameters
115-
public void Finally(object message, Stopwatch sw, Exception? ex)
116-
{
117-
logger.LogInformation("Handled {MessageType} in {Ms}ms",
118-
message.GetType().Name, sw.ElapsedMilliseconds);
119-
}
120-
}
121-
```
122-
123-
### 3. Use the Mediator
124-
125-
```csharp
126-
// Query with response
127-
var result = await mediator.InvokeAsync<Result<User>>(new GetUser(123));
128-
if (result.IsSuccess)
129-
Console.WriteLine($"Found user: {result.Value.Name}");
130-
131-
// Command with automatic event publishing
132-
var user = await mediator.InvokeAsync<User>(new CreateUser("John", "john@example.com"));
133-
// UserCreated event automatically published to EmailHandler
134-
135-
// Publish events to multiple handlers
136-
await mediator.PublishAsync(new UserCreated(user.Id, user.Email));
137-
```
138-
139-
### 4. Auto-Generate API Endpoints
140-
141-
Skip the boilerplate of manually mapping endpoints to message handlers:
142-
143-
```csharp
144-
// Enable endpoint generation (in any .cs file)
145-
[assembly: MediatorConfiguration(EndpointDiscovery = EndpointDiscovery.All)]
146-
```
147-
148-
```csharp
149-
[HandlerCategory("Products", RoutePrefix = "products")]
150-
public class ProductHandler
151-
{
152-
public Task<Result<Product>> HandleAsync(CreateProduct command) { /* ... */ }
153-
public Result<Product> Handle(GetProduct query) { /* ... */ }
154-
}
155-
```
156-
157-
This generates:
158-
159-
- `POST /api/products``ProductHandler.HandleAsync(CreateProduct)`
160-
- `GET /api/products/{productId}``ProductHandler.Handle(GetProduct)`
161-
162-
HTTP methods, routes, and parameter binding are inferred from message names and properties. `Result<T>` maps to the correct HTTP status codes automatically. Pass `logEndpoints: true` to see all mapped routes at startup:
163-
164-
```csharp
165-
app.MapProductsEndpoints(logEndpoints: true);
166-
```
167-
168-
See [Endpoints Guide](https://mediator.foundatio.dev/guide/endpoints.html) for route customization, OpenAPI metadata, authorization, and more.
169-
170-
## 📚 Learn More
171-
172-
**👉 [Complete Documentation](https://mediator.foundatio.dev)**
173-
174-
Key topics:
175-
176-
- [Getting Started](https://mediator.foundatio.dev/guide/getting-started.html) - Step-by-step setup
177-
- [Handler Conventions](https://mediator.foundatio.dev/guide/handler-conventions.html) - Discovery rules and patterns
178-
- [Middleware](https://mediator.foundatio.dev/guide/middleware.html) - Pipeline hooks and state management
179-
- [Result Types](https://mediator.foundatio.dev/guide/result-types.html) - Rich status handling
180-
- [Endpoints](https://mediator.foundatio.dev/guide/endpoints.html) - Auto-generated Minimal API endpoints
181-
- [Performance](https://mediator.foundatio.dev/guide/performance.html) - Benchmarks vs other libraries
182-
- [Configuration](https://mediator.foundatio.dev/guide/configuration.html) - Assembly attribute and runtime options
34+
**📖 [Complete Documentation](https://mediator.foundatio.dev)**
18335

18436
## 📂 Sample Applications
18537

@@ -194,24 +46,6 @@ Explore complete working examples:
19446
- Auto-generated API endpoints
19547
- Shared middleware across modules
19648

197-
## 🔍 Viewing Generated Code
198-
199-
For debugging purposes, you can inspect the source code generated by Foundatio Mediator. Add this to your `.csproj`:
200-
201-
```xml
202-
<PropertyGroup>
203-
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
204-
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
205-
</PropertyGroup>
206-
207-
<ItemGroup>
208-
<Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
209-
<Content Include="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
210-
</ItemGroup>
211-
```
212-
213-
After building, check the `Generated` folder for handler wrappers, DI registrations, and interceptor code. See [Troubleshooting](https://mediator.foundatio.dev/guide/troubleshooting.html) for more details.
214-
21549
## 📦 CI Packages (Feedz)
21650

21751
Want the latest CI build before it hits NuGet? Add the Feedz source (read‑only public) and install the pre-release version:

samples/CleanArchitectureSample/src/Api/Handlers/ClientEventStreamHandler.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public class ClientEventStreamHandler(IMediator mediator)
2525
[HandlerEndpoint(
2626
Route = "/events/stream",
2727
Streaming = EndpointStreaming.ServerSentEvents,
28-
SseEventType = "event",
2928
Summary = "Subscribe to real-time domain events via Server-Sent Events")]
3029
[HandlerAllowAnonymous]
3130
public async IAsyncEnumerable<ClientEvent> Handle(

src/Foundatio.Mediator/HandlerAnalyzer.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,9 @@ public static List<HandlerInfo> GetHandlers(GeneratorSyntaxContext context)
212212
bool hasConstructorParameters = !handlerMethod.IsStatic &&
213213
classSymbol.InstanceConstructors.Any(c => c.Parameters.Length > 0);
214214

215-
// Extract XML documentation summary
216-
var xmlDocSummary = ExtractXmlDocSummary(handlerMethod);
215+
// Extract XML documentation summary (method first, then class if single handler)
216+
var xmlDocSummary = ExtractXmlDocSummary(handlerMethod)
217+
?? (handlerMethods.Count == 1 ? ExtractXmlDocSummary(classSymbol) : null);
217218

218219
// Extract authorization metadata from [HandlerAuthorize], [HandlerAllowAnonymous], [AllowAnonymous]
219220
var authorizationInfo = ExtractAuthorizationInfo(classSymbol, handlerMethod, context.SemanticModel.Compilation);
@@ -317,12 +318,12 @@ private static string[] ExtractTypeArrayArgument(AttributeData? attr, string arg
317318
}
318319

319320
/// <summary>
320-
/// Extracts the XML documentation summary from a method symbol using syntax trivia.
321+
/// Extracts the XML documentation summary from a symbol (method, class, etc.) using syntax trivia.
321322
/// Requires GenerateDocumentationFile to be enabled for the trivia to be parsed as documentation.
322323
/// </summary>
323-
private static string? ExtractXmlDocSummary(IMethodSymbol method)
324+
private static string? ExtractXmlDocSummary(ISymbol symbol)
324325
{
325-
var syntaxRef = method.DeclaringSyntaxReferences.FirstOrDefault();
326+
var syntaxRef = symbol.DeclaringSyntaxReferences.FirstOrDefault();
326327
if (syntaxRef == null)
327328
return null;
328329

0 commit comments

Comments
 (0)