Knara.SourceGenerators.DesignPatterns.Mediator
1.1.0
dotnet add package Knara.SourceGenerators.DesignPatterns.Mediator --version 1.1.0
NuGet\Install-Package Knara.SourceGenerators.DesignPatterns.Mediator -Version 1.1.0
<PackageReference Include="Knara.SourceGenerators.DesignPatterns.Mediator" Version="1.1.0" />
<PackageVersion Include="Knara.SourceGenerators.DesignPatterns.Mediator" Version="1.1.0" />
<PackageReference Include="Knara.SourceGenerators.DesignPatterns.Mediator" />
paket add Knara.SourceGenerators.DesignPatterns.Mediator --version 1.1.0
#r "nuget: Knara.SourceGenerators.DesignPatterns.Mediator, 1.1.0"
#:package Knara.SourceGenerators.DesignPatterns.Mediator@1.1.0
#addin nuget:?package=Knara.SourceGenerators.DesignPatterns.Mediator&version=1.1.0
#tool nuget:?package=Knara.SourceGenerators.DesignPatterns.Mediator&version=1.1.0
Declarative Mediator Generator
A C# source generator that retrofits existing services with the mediator pattern using declarative attributes. Designed for gradual CQRS adoption in legacy codebases. Generates mediator infrastructure at compile-time using Roslyn analysis.
What It Does
This generator creates mediator boilerplate for two distinct scenarios:
1. CQRS-Style (New Code)
Clean separation of commands and queries with dedicated request/handler classes:
[Query(Name = "GetUserQuery", ResponseType = typeof(User))]
public record GetUserRequest(int UserId);
[QueryHandler(Name="GetUserHandler", RequestType = typeof(GetUserQuery))]
public class GetUserService(IUserRepository repository)
{
public async Task<User> GetAsync(GetUserRequest request, CancellationToken ct) { ... }
}
2. Legacy Retrofitting (Existing Code)
Method-level attributes that wrap existing services without modification:
public class LegacyUserService
{
[RequestHandler(Name="GetUserHandler")]
public async Task<User> GetUserAsync(int userId, CancellationToken ct) { ... }
[RequestHandler(Name="CreateUserHandler")]
public async Task AddNewUserAsync(NewUserModel model, CancellationToken ct) { ... }
[RequestHandler(Name="LegacyUserUpdateUserHandler")]
public async Task<User> UpdateAsync(int userId, string email, string firstName, DateTime updateDate, CancellationToken cancellationToken = default) { ... }
}
Both generate the same mediator infrastructure but serve different migration strategies.
Quick Start
Add the source generator to your project:
<ItemGroup> <ProjectReference Include="path/to/Knara.SourceGenerators.DesignPatterns.Mediator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup>
Or via NuGet (when published):
dotnet add package Knara.SourceGenerators.DesignPatterns.Mediator
If you are using the generator in .net 4.+ projects, refer to this guide for additional steps.
Usage Patterns
CQRS-Style Usage
// Generated: GetUserQuery : IQuery<User>
var user = await mediator.Send(new GetUserQuery { UserId = 123 });
// Generated: CreateUserCommand : ICommand<bool>
var success = await mediator.Send(new CreateUserCommand { Email = "test@example.com" });
Legacy Retrofitting Usage
// Generated: GetUserRequest : IRequest<User>
var user = await mediator.Send(new GetUserRequest { UserId = 123 });
// Generated: CreateUserRequest : IRequest
await mediator.Send(new CreateUserRequest { Email = "test@example.com" });
Registration
// In your Startup.cs or Program.cs if using .NET 6+
services.AddDeclarativeMediator(); // Auto-registers all handlers and services
If you are using the generator in .net 4.+ projects, you need to manually register the generated types and mediator.
When to Use Each Pattern
CQRS-Style ([Query]/[Command])
Use for:
- New feature development
- Clean architectural boundaries
- Complex business domains
- Teams adopting CQRS principles
Legacy Retrofitting ([RequestHandler])
Use for:
- Existing codebases with established patterns
- Gradual mediator adoption
- Risk-averse migration strategies
- Mixed architectural approaches during transition
Pros
✅ Dual approach - Supports both clean CQRS and pragmatic retrofitting
✅ Compile-time generation - No runtime reflection, better performance than MediatR
✅ Non-breaking - Legacy services remain callable directly during transition
✅ Type safety - All request routing resolved at compile-time
✅ Minimal friction - Add attributes to existing code without restructuring
Cons
❌ Complexity - Two different patterns in same codebase can confuse teams
❌ Type proliferation - Generates many request/handler classes
❌ Generated code debugging - Harder to troubleshoot than explicit implementations
❌ Limited pipeline - Missing MediatR's rich behavior/middleware ecosystem
❌ Learning curve - Teams need to understand both patterns and when to use each
Performance
Faster than MediatR due to compile-time generation:
- Direct method calls vs reflection
- Pattern matching vs runtime type resolution
GetRequiredService<ConcreteType>()vs generic service location
Limitations
This is NOT a drop-in MediatR replacement. Missing:
- Pipeline behaviors and middleware
- Request preprocessing/postprocessing
- Polymorphic request handling
- Validation pipeline integration
- Advanced error handling patterns
- Request/response decorators
For full-featured mediator requirements, use MediatR directly.
Migration Strategy
Recommended approach:
- Start with legacy pattern - Add
[RequestHandler]to existing methods - Establish mediator usage - Route new features through generated mediator
- Introduce CQRS gradually - Use
[Query]/[Command]for new bounded contexts - Migrate incrementally - Convert legacy handlers to CQRS as business needs require
- Consider full MediatR - When you need advanced pipeline features
Use Cases
✅ Good Fit
- Legacy modernization with risk constraints
- Mixed architectural periods during large migrations
- Performance-sensitive applications where reflection overhead matters
- Simple request/response patterns without complex pipelines
- Teams learning mediator patterns incrementally
❌ Poor Fit
- Greenfield projects (just use MediatR)
- Complex pipeline requirements (validation, caching, logging, etc.)
- Heavy behavior composition needs
- Small codebases where generated complexity outweighs benefits
- Teams wanting industry-standard patterns
Code Generation Output
For each pattern, the generator creates:
- Request classes implementing appropriate interfaces
- Handler classes wrapping your service methods
- Mediator implementation with compile-time request routing
- DI registration extensions for all generated types
Example generated structure:
├── GetUserQuery.Request.g.cs # CQRS request class
├── GetUserHandler.Handler.g.cs # CQRS handler wrapper
├── GetUserRequest.Request.g.cs # Legacy request class
├── GetUserHandler.Handler.g.cs # Legacy handler wrapper
├── GeneratedMediator.g.cs # Unified mediator implementation
└── MediatorDIExtensions.g.cs # Service registration
Bottom Line
Consider this a stepping stone toward full mediator adoption rather than a permanent architectural decision.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 9.0.8)
- Microsoft.CodeAnalysis.CSharp (>= 4.14.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.