Mediocr.Interfaces
0.1.0-alpha.4
dotnet add package Mediocr.Interfaces --version 0.1.0-alpha.4
NuGet\Install-Package Mediocr.Interfaces -Version 0.1.0-alpha.4
<PackageReference Include="Mediocr.Interfaces" Version="0.1.0-alpha.4" />
<PackageVersion Include="Mediocr.Interfaces" Version="0.1.0-alpha.4" />
<PackageReference Include="Mediocr.Interfaces" />
paket add Mediocr.Interfaces --version 0.1.0-alpha.4
#r "nuget: Mediocr.Interfaces, 0.1.0-alpha.4"
#:package Mediocr.Interfaces@0.1.0-alpha.4
#addin nuget:?package=Mediocr.Interfaces&version=0.1.0-alpha.4&prerelease
#tool nuget:?package=Mediocr.Interfaces&version=0.1.0-alpha.4&prerelease
Mediocr
Not great, not terrible
A minimal Mediator with automatic handler registration via source generation.
Mediocr provides a lightweight mediator pattern implementation with automatic request handler registration using C# source generators. No reflection, no startup scanning, just compile-time code generation.
Installation
dotnet add package Mediocr
dotnet add package Mediocr.Interfaces
Or via Package Manager:
Install-Package Mediocr
Install-Package Mediocr.Interfaces
Quick Start
Define a Request
using Mediocr.Interfaces;
namespace MyApp.Requests;
public class GetUserRequest : IRequest<UserDto>
{
public int UserId { get; set; }
}
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
Create a Handler
using Mediocr.Interfaces;
namespace MyApp.Handlers;
public class GetUserRequestHandler : IRequestHandler<GetUserRequest, UserDto>
{
private readonly IUserRepository _repository;
public GetUserRequestHandler(IUserRepository repository)
{
_repository = repository;
}
public async Task<UserDto> Handle(GetUserRequest request, CancellationToken cancellationToken)
{
var user = await _repository.GetByIdAsync(request.UserId, cancellationToken);
return new UserDto
{
Id = user.Id,
Name = user.Name,
Email = user.Email
};
}
}
Register Handlers
The source generator automatically creates an extension method:
using Mediocr.Interfaces;
var builder = WebApplication.CreateBuilder(args);
// Register all handlers automatically!
builder.Services.AddMediocrHandlers();
var app = builder.Build();
Resolve and Execute
public class UserController : ControllerBase
{
private readonly IRequestHandler<GetUserRequest, UserDto> _handler;
public UserController(IRequestHandler<GetUserRequest, UserDto> handler)
{
_handler = handler;
}
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var request = new GetUserRequest { UserId = id };
var result = await _handler.Handle(request, HttpContext.RequestAborted);
return Ok(result);
}
}
How It Works
Source Generation
When you build your project, Mediocr's source generator:
- Scans your code for classes implementing
IRequestHandler<TInput, TOutput> - Generates an extension method
AddMediocrHandlers() - Registers all handlers as scoped services in the DI container
Generated Code Example
For the handlers above, Mediocr generates:
// <auto-generated/>
using Microsoft.Extensions.DependencyInjection;
namespace Mediocr.Interfaces
{
public static class MediocRServiceCollectionExtensions
{
public static IServiceCollection AddMediocrHandlers(this IServiceCollection services)
{
services.AddScoped<global::Mediocr.Interfaces.IRequestHandler<global::MyApp.Requests.GetUserRequest, global::MyApp.Requests.UserDto>,
global::MyApp.Handlers.GetUserRequestHandler>();
return services;
}
}
}
Viewing Generated Code
To see the generated code in your project:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)/GeneratedFiles</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
Generated files appear in: obj/GeneratedFiles/Mediocr/Mediocr.RequestHandlerGenerator/
Advanced Usage
Multiple Handlers Per Class
A single class can implement multiple handler interfaces:
public class UserCommandHandlers :
IRequestHandler<CreateUserCommand, int>,
IRequestHandler<UpdateUserCommand, bool>
{
public async Task<int> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
// Create user logic
return newUserId;
}
public async Task<bool> Handle(UpdateUserCommand request, CancellationToken cancellationToken)
{
// Update user logic
return true;
}
}
Both handlers are automatically registered.
Generic Response Types
Handlers can return any type, including collections and complex generics:
// List response
public class GetUsersRequest : IRequest<List<UserDto>> { }
public class GetUsersHandler : IRequestHandler<GetUsersRequest, List<UserDto>>
{
public async Task<List<UserDto>> Handle(GetUsersRequest request, CancellationToken cancellationToken)
{
return await _repository.GetAllAsync();
}
}
// Dictionary response
public class GetUserPreferencesRequest : IRequest<Dictionary<string, string>> { }
// Tuple response
public class GetUserStatsRequest : IRequest<(int TotalPosts, int TotalLikes)> { }
Nested Classes
Handlers can be nested within other classes:
namespace MyApp.Features.Users;
public static class GetUser
{
public class Query : IRequest<Result>
{
public int UserId { get; set; }
}
public class Result
{
public string Name { get; set; }
}
public class Handler : IRequestHandler<Query, Result>
{
public async Task<Result> Handle(Query request, CancellationToken cancellationToken)
{
// Handler logic
}
}
}
Record Types
Works seamlessly with C# records:
public record GetUserQuery(int UserId) : IRequest<UserDto>;
public class GetUserQueryHandler : IRequestHandler<GetUserQuery, UserDto>
{
public async Task<UserDto> Handle(GetUserQuery request, CancellationToken cancellationToken)
{
// Handler logic
}
}
Service Lifetime
All handlers are registered with Scoped lifetime by default.
TODO: Extend this to other lifetime types
Testing
Unit Testing Handlers
Handlers are plain classes and easy to test:
[Fact]
public async Task GetUserHandler_ReturnsUser_WhenUserExists()
{
// Arrange
var mockRepo = new Mock<IUserRepository>();
mockRepo.Setup(r => r.GetByIdAsync(1, default))
.ReturnsAsync(new User { Id = 1, Name = "John" });
var handler = new GetUserRequestHandler(mockRepo.Object);
var request = new GetUserRequest { UserId = 1 };
// Act
var result = await handler.Handle(request, CancellationToken.None);
// Assert
Assert.Equal(1, result.Id);
Assert.Equal("John", result.Name);
}
Integration Testing
public class UserEndpointTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
[Fact]
public async Task GetUser_ReturnsOk_WithValidId()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/api/users/1");
// Assert
response.EnsureSuccessStatusCode();
var user = await response.Content.ReadFromJsonAsync<UserDto>();
Assert.NotNull(user);
}
}
Troubleshooting
Check generator is loaded:
dotnet build -v:detailedLook for "Loading analyzer" messages.
Verify project references:
<ItemGroup> <ProjectReference Include="../Mediocr.Interfaces/Mediocr.Interfaces.csproj" /> <ProjectReference Include="../Mediocr/Mediocr.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup>
Handlers Not Found
- Ensure handlers implement
IRequestHandler<TInput, TOutput> - Handlers must not be abstract
- Handlers must have a base list (implement at least one interface)
- Check namespace imports
Build Errors
If you see errors about missing types:
dotnet restore
dotnet clean
dotnet build
Examples
CQRS Pattern
// Command (changes state)
public class CreateOrderCommand : IRequest<int>
{
public string CustomerName { get; set; }
public List<OrderItem> Items { get; set; }
}
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, int>
{
private readonly IOrderRepository _orders;
public async Task<int> Handle(CreateOrderCommand command, CancellationToken cancellationToken)
{
var order = new Order
{
CustomerName = command.CustomerName,
Items = command.Items
};
await _orders.AddAsync(order);
await _orders.SaveChangesAsync();
return order.Id;
}
}
// Query (reads state)
public class GetOrderQuery : IRequest<OrderDto>
{
public int OrderId { get; set; }
}
public class GetOrderHandler : IRequestHandler<GetOrderQuery, OrderDto>
{
private readonly IOrderRepository _orders;
public async Task<OrderDto> Handle(GetOrderQuery query, CancellationToken cancellationToken)
{
var order = await _orders.GetByIdAsync(query.OrderId);
return new OrderDto
{
Id = order.Id,
CustomerName = order.CustomerName,
Items = order.Items
};
}
}
API Controller
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IRequestHandler<GetOrderQuery, OrderDto> _getOrderHandler;
private readonly IRequestHandler<CreateOrderCommand, int> _createOrderHandler;
public OrdersController(
IRequestHandler<GetOrderQuery, OrderDto> getOrderHandler,
IRequestHandler<CreateOrderCommand, int> createOrderHandler)
{
_getOrderHandler = getOrderHandler;
_createOrderHandler = createOrderHandler;
}
[HttpGet("{id}")]
public async Task<ActionResult<OrderDto>> GetOrder(int id)
{
var query = new GetOrderQuery { OrderId = id };
var result = await _getOrderHandler.Handle(query, HttpContext.RequestAborted);
return Ok(result);
}
[HttpPost]
public async Task<ActionResult<int>> CreateOrder(CreateOrderCommand command)
{
var orderId = await _createOrderHandler.Handle(command, HttpContext.RequestAborted);
return CreatedAtAction(nameof(GetOrder), new { id = orderId }, orderId);
}
}
Minimal API
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMediocrHandlers();
var app = builder.Build();
app.MapGet("/api/orders/{id}", async (
int id,
IRequestHandler<GetOrderQuery, OrderDto> handler) =>
{
var query = new GetOrderQuery { OrderId = id };
var result = await handler.Handle(query, CancellationToken.None);
return Results.Ok(result);
});
app.MapPost("/api/orders", async (
CreateOrderCommand command,
IRequestHandler<CreateOrderCommand, int> handler) =>
{
var orderId = await handler.Handle(command, CancellationToken.None);
return Results.Created($"/api/orders/{orderId}", orderId);
});
app.Run();
License
This project is licensed under the MIT License - see the LICENSE file for details.
| 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
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Mediocr.Interfaces:
| Package | Downloads |
|---|---|
|
Mediocr
Source generator for Mediocr - automatically registers request handlers |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-alpha.4 | 274 | 12/17/2025 |
| 0.1.0-alpha.3 | 252 | 12/17/2025 |
| 0.1.0-alpha.2 | 257 | 12/17/2025 |
| 0.1.0-alpha.1 | 258 | 12/16/2025 |