ResultR 1.0.0-beta.2

This is a prerelease version of ResultR.
There is a newer version of this package available.
See the version list below for details.
dotnet add package ResultR --version 1.0.0-beta.2
                    
NuGet\Install-Package ResultR -Version 1.0.0-beta.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ResultR" Version="1.0.0-beta.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ResultR" Version="1.0.0-beta.2" />
                    
Directory.Packages.props
<PackageReference Include="ResultR" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ResultR --version 1.0.0-beta.2
                    
#r "nuget: ResultR, 1.0.0-beta.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ResultR@1.0.0-beta.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ResultR&version=1.0.0-beta.2&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ResultR&version=1.0.0-beta.2&prerelease
                    
Install as a Cake Tool

🎯 ResultR

GitHub Release GitHub Actions Workflow Status GitHub Downloads (all assets, all releases) GitHub License

A lightweight, opinionated C# mediator library focused on simplicity and clean design.

πŸ“– Overview

ResultR provides a minimal yet powerful mediator pattern implementation with built-in result handling, validation, and request lifecycle hooks. It's designed as a modern alternative to MediatR with a smaller surface area and a clearer result pattern.

✨ Key Features

  • πŸ”Œ Single Interface Pattern: Uses only IRequest<TResponse> and IRequestHandler<TRequest, TResponse> - no distinction between commands and queries
  • πŸ“¦ Unified Result Type: All operations return Result<T> or Result, supporting success/failure states, exception capture, and optional metadata
  • πŸͺ Optional Inline Hooks: Handlers can override ValidateAsync(), OnPreHandleAsync(), and OnPostHandleAsync() methods without requiring base classes or separate interfaces
  • πŸ“ Request-Specific Logging: Built-in support for per-request logging via ILoggerFactory
  • ⚑ Minimal Configuration: Simple DI integration with minimal setup
  • πŸ”’ Strong Typing: Full type safety throughout the pipeline

πŸ’‘ Design Philosophy

ResultR prioritizes:

  • Simplicity over flexibility: Opinionated design choices reduce boilerplate
  • Clean architecture: No magic strings, reflection-heavy operations, or hidden behaviors
  • Explicit over implicit: Clear pipeline execution with predictable behavior
  • Modern C# practices: Leverages latest language features and patterns

πŸ”„ Pipeline Execution

Each request flows through a simple, predictable pipeline:

  1. βœ… Validation - Calls ValidateAsync() if overridden, short-circuits on failure
  2. πŸš€ Pre-Handle - Invokes OnPreHandleAsync() for optional logging or setup
  3. βš™οΈ Handle - Executes the core HandleAsync() logic
  4. 🏁 Post-Handle - Invokes OnPostHandleAsync() for logging or cleanup
  5. πŸ›‘οΈ Exception Handling - Any exceptions are caught and returned as Result.Failure with the exception attached

πŸ“₯ Installation

dotnet add package ResultR

πŸš€ Quick Start

1. Define a Request

public record CreateUserRequest(string Email, string Name) : IRequest<User>;

2. Create a Handler

public class CreateUserHandler : IRequestHandler<CreateUserRequest, User>
{
    private readonly IUserRepository _repository;
    private readonly ILogger<CreateUserHandler> _logger;

    public CreateUserHandler(IUserRepository repository, ILoggerFactory loggerFactory)
    {
        _repository = repository;
        _logger = loggerFactory.CreateLogger<CreateUserHandler>();
    }

    // Optional: Validate the request (override virtual method)
    public ValueTask<Result> ValidateAsync(CreateUserRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Email))
            return new(Result.Failure("Email is required"));
        
        if (!request.Email.Contains("@"))
            return new(Result.Failure("Invalid email format"));
        
        return new(Result.Success());
    }

    // Optional: Pre-handle hook (override virtual method)
    public ValueTask OnPreHandleAsync(CreateUserRequest request)
    {
        _logger.LogInformation("Creating user with email: {Email}", request.Email);
        return default;
    }

    // Required: Core handler logic
    public async ValueTask<Result<User>> HandleAsync(CreateUserRequest request, CancellationToken cancellationToken)
    {
        // Exceptions are automatically caught and converted to Result.Failure
        var user = new User(request.Email, request.Name);
        await _repository.AddAsync(user, cancellationToken);
        return Result<User>.Success(user);
    }

    // Optional: Post-handle hook (override virtual method)
    public ValueTask OnPostHandleAsync(CreateUserRequest request, Result<User> result)
    {
        if (result.IsSuccess)
            _logger.LogInformation("User created successfully: {UserId}", result.Value.Id);
        else
            _logger.LogError("User creation failed: {Error}", result.Error);
        return default;
    }
}

3. Register with DI

// Simple: auto-scans entry assembly
services.AddResultR();

// Or explicit: scan specific assemblies (for multi-project solutions)
services.AddResultR(
    typeof(Program).Assembly,
    typeof(MyHandlers).Assembly);

4. Send Requests

public class UserController : ControllerBase
{
    private readonly IMediator _mediator;

    public UserController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> CreateUser(CreateUserRequest request)
    {
        var result = await _mediator.Send(request);
        
        return result.IsSuccess 
            ? Ok(result.Value) 
            : BadRequest(result.Error);
    }
}

πŸ“¦ Result Type

The Result<T> type provides a clean way to handle success and failure states:

// Success
var success = Result<User>.Success(user);

// Failure with message
var failure = Result<User>.Failure("User not found");

// Failure with exception
var error = Result<User>.Failure("Database error", exception);

// Checking results
if (result.IsSuccess)
{
    var value = result.Value;
}
else
{
    var error = result.Error;
    var exception = result.Exception;
}

For void operations, use the non-generic Result:

public record DeleteUserRequest(Guid UserId) : IRequest<Result>;

public async ValueTask<Result<Result>> HandleAsync(DeleteUserRequest request, CancellationToken cancellationToken)
{
    await _repository.DeleteAsync(request.UserId);
    return Result<Result>.Success(Result.Success());
}

πŸ”§ Advanced Features

Metadata Support

var result = Result<User>.Success(user)
    .WithMetadata("CreatedAt", DateTime.UtcNow)
    .WithMetadata("Source", "API");

Validation Only

// Handlers can override validation without other hooks
public class ValidatingHandler : IRequestHandler<MyRequest, MyResponse>
{
    public ValueTask<Result> ValidateAsync(MyRequest request)
    {
        // Validation logic
        return new(Result.Success());
    }

    public async ValueTask<Result<MyResponse>> HandleAsync(MyRequest request, CancellationToken cancellationToken)
    {
        // Handle logic
    }
}

πŸ“Š Benchmarks

There are many great Mediator implementations out there. Here is a comparision between ResultR and some of the other popular ones:

Performance comparison between ResultR (latest), MediatR (12.5.0), DispatchR (2.1.1), and Mediator.SourceGenerator (2.1.7):

Method Mean Allocated Ratio
MediatorSG - With Validation 18.68 ns 72 B 0.31
MediatorSG - Simple 18.87 ns 72 B 0.31
MediatorSG - Full Pipeline 20.26 ns 72 B 0.34
DispatchR - With Validation 29.28 ns 96 B 0.49
DispatchR - Simple 29.58 ns 96 B 0.49
DispatchR - Full Pipeline 30.15 ns 96 B 0.50
MediatR - Full Pipeline 59.89 ns 296 B 1.00
MediatR - Simple 60.02 ns 296 B 1.00
MediatR - With Validation 62.95 ns 296 B 1.05
ResultR - With Validation 80.73 ns 264 B 1.35
ResultR - Full Pipeline 80.97 ns 264 B 1.35
ResultR - Simple 81.65 ns 264 B 1.36

What does this mean? The difference between ResultR (~81ns) and MediatR (~60ns) is roughly 20 nanoseconds - that's 0.00002 milliseconds. In real applications where a typical database query takes 1-10ms and HTTP calls take 50-500ms, this difference is completely negligible. ResultR also allocates less memory per request (264B vs 296B), which can reduce garbage collection pressure in high-throughput scenarios.

Run benchmarks locally:

cd src/ResultR.Benchmarks
dotnet run -c Release

πŸ€” Why ResultR?

vs MediatR

  • Simpler: No pipeline behaviors, notifications, or stream support - just requests and handlers
  • Opinionated: Built-in validation and lifecycle hooks without configuration
  • Result-focused: Every operation returns a Result type for consistent error handling
  • Smaller: Minimal API surface area and dependencies

vs Custom Implementation

  • Battle-tested patterns: Proven mediator implementation
  • DI integration: Automatic handler registration and resolution
  • Type safety: Compile-time guarantees for request/response matching
  • Extensibility: Optional hooks without forcing inheritance

πŸ“‹ Requirements

  • .NET 10.0 or later
  • C# 14.0 or later

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“„ License

ISC License - see LICENSE file for details

πŸ—ΊοΈ Roadmap

  • Core mediator implementation
  • Result types with metadata support
  • DI registration extensions
  • Comprehensive unit tests
  • Performance benchmarks
  • NuGet package publication

πŸ’¬ Support


Built with ❀️ for clean, maintainable C# applications.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on ResultR:

Package Downloads
ResultR.Validation

Lightweight inline validation framework for ResultR. Define validation rules directly in ValidateAsync() using a fluent API, with seamless integration into ResultR's pipeline hooks.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 128 1/28/2026
1.0.1 131 1/2/2026
1.0.0 152 12/28/2025
1.0.0-beta.6 268 12/18/2025
1.0.0-beta.5 653 12/2/2025
1.0.0-beta.4 94 11/29/2025
1.0.0-beta.3 93 11/29/2025
1.0.0-beta.2 167 11/26/2025
1.0.0-beta.1 169 11/26/2025