login
RepoCritics — Review. Share. Archive. Every open-source repo.

LuckyPennySoftware/MediatR

Wiki: LuckyPennySoftware/MediatR

Source: https://github.com/LuckyPennySoftware/MediatR

Last synced 2026-07-21 · 1275 words · Edit wiki on GitHub →

LuckyPennySoftware/MediatR

> In-process mediator for .NET — request/response, notifications, and a pipeline — that went commercial in 2025.

GitHub repo · Licensing · License: RPL-1.5 / commercial (was Apache-2.0)

Overview

MediatR is a small library that implements the mediator pattern in .NET. Instead of a controller or service calling a handler class directly, it constructs a request object and hands it to IMediator, which resolves and invokes the matching handler through the DI container. It covers three shapes: request/response (one handler, IRequest<T>), notifications (zero-to-many handlers, INotification), and streaming (IStreamRequest<T>). A pipeline of IPipelineBehavior wrappers sits between send and handler for cross-cutting concerns[^1]. It is deliberately narrow — "simple, unambitious," in the repo's own words — and holds no queues, no persistence, and no cross-process transport.

It was created in 2014 by Jimmy Bogard[^2] and for most of its life shipped under Apache-2.0. It is one of the most-installed .NET packages on NuGet and the default backbone of countless "CQRS" and vertical-slice codebases. Its ubiquity outran its ambitions: Bogard himself has repeatedly cautioned that MediatR is a library, not an architecture, and that teams routinely over-apply it[^3].

The defining event of the project's recent history is commercial. In 2025 ownership moved to Lucky Penny Software (Bogard's company), the repository was relocated from jbogard/MediatR to LuckyPennySoftware/MediatR, and the license changed from Apache-2.0 to the Reciprocal Public License 1.5 (RPL-1.5) with a paid commercial option and a license-key mechanism[^4]. Any evaluation of MediatR today is really an evaluation of that transition — the code is largely the same; the terms are not.

Getting Started

dotnet add package MediatR
// 1. Define a request and its handler
public record Ping(string Message) : IRequest<string>;

public class PingHandler : IRequestHandler<Ping, string>
{
    public Task<string> Handle(Ping request, CancellationToken ct)
        => Task.FromResult($"Pong: {request.Message}");
}

// 2. Register (scans the assembly for handlers)
services.AddMediatR(cfg =>
    cfg.RegisterServicesFromAssemblyContaining<Program>());

// 3. Send
public class Controller(ISender sender)
{
    public Task<string> Get() => sender.Send(new Ping("hi"));
}

For projects that share request/notification types across assemblies (gRPC, API contracts, Blazor), the contracts-only MediatR.Contracts package exposes IRequest, INotification, and IStreamRequest without the runtime[^1].

Architecture / How It Works

IMediator is the union of two interfaces you usually depend on separately: ISender (Send, for request/response and streams) and IPublisher (Publish, for notifications). AddMediatR scans the assemblies you point it at, finds every closed IRequestHandler<,>, INotificationHandler<>, IStreamRequestHandler<,>, and open-generic behavior, and registers them as transient services[^1]. At dispatch time MediatR builds the closed generic handler type from the request's runtime type and asks the container for it — C# generic variance is what lets a single Send call route to the right handler.

Requests are one-to-one: exactly one handler per IRequest<T>, and a missing or duplicate registration is a runtime error, not a compile error. Notifications are one-to-many: Publish resolves every INotificationHandler<T> and, by default, awaits them sequentially in registration order. You can swap the notification publisher strategy (run handlers in parallel, or continue past exceptions), but the default is the foreach-await that surprises people.

IPipelineBehavior<TRequest,TResponse> is the extension point that carries most of MediatR's real-world value: logging, validation (FluentValidation), caching, transactions, and retries are all written as behaviors that wrap the inner handler via a next() delegate. Behavior order is registration order, and getting it wrong (validation after the handler, transaction inside the retry) is a common and silent bug. Pre/post processors are a thinner convenience over the same idea.

The cost of all this is indirection. sender.Send(new Ping(...)) has no static link to PingHandler; "go to definition" lands on the interface, not the code that runs. That is the intended decoupling and also the most-cited critique — the call graph moves from the compiler to the DI container.

Production Notes

The license change is the headline operational risk, not a footnote. Versions through v11 are Apache-2.0 and stay that way. Starting with the Lucky Penny era (v12 onward, and firmly by v13, 2025-07-02) the code is RPL-1.5 with a commercial option[^4]. RPL-1.5 is a strong reciprocal (copyleft-style) license that can require you to publish source of derivative work — for many closed-source shops that means buying a commercial license instead. A license key can be supplied via cfg.LicenseKey, or the MEDIATR_LICENSE_KEY / LUCKYPENNY_LICENSE_KEY environment variables (the latter shared with AutoMapper), and unlicensed use logs a warning[^1]. Confirm current pricing and free-tier thresholds against Lucky Penny's site before upgrading; treat the jump past v11 as a licensing decision with legal sign-off, not a routine bump.

Handler resolution is reflection- and container-driven. For the overwhelming majority of apps the per-Send overhead is irrelevant next to I/O, but it is not free: closed generic type construction plus a container resolve per call, with allocations. Hot in-process fan-out at very high throughput is where alternatives that use source generators (compile-time dispatch) measurably win.

Assembly scanning is a startup and correctness surface. Point AddMediatR at the wrong assemblies and handlers silently do not register — you get a runtime "no handler for request" only when that path is exercised. There are no compile-time guarantees that every IRequest<T> has a handler.

Notifications carry ordering and failure assumptions. Default sequential publish means one slow or throwing handler blocks or aborts the rest. If you need isolation, parallelism, or continue-on-error, you must supply a custom publisher — it is not the default.

Upgrade friction beyond licensing. v12 reworked DI registration onto Microsoft.Extensions.DependencyInjection and removed the older per-container adapter packages, so pre-v12 registration code does not port unchanged. Combined with the license shift, v11→v13+ is the migration that actually costs time.

When to Use / When Not

Use when:

  • You want thin controllers/endpoints and cross-cutting concerns (validation, logging, transactions) expressed once as pipeline behaviors.
  • You are doing in-process CQRS or vertical-slice architecture and want a uniform send/handle shape.
  • Your organization can accept RPL-1.5 or budget a commercial license.

Avoid when:

  • The app is small enough that a directly-injected handler class is clearer than a request object plus a dispatcher.
  • You need real messaging: queues, durable retries, or cross-process delivery — that is a message bus, not a mediator.
  • You cannot accept the license terms; v11-and-earlier Apache builds are frozen, and source-generated MIT alternatives now match most of the API.
  • You are adopting it as "architecture." Indirection without a decoupling need is net-negative navigability.

Alternatives

  • martinothamar/Mediator — source-generator mediator with a near-identical API, compile-time dispatch, and an MIT license; use when you want the MediatR shape without runtime reflection or licensing fees.
  • Cysharp/MessagePipe — high-performance in-process (and inter-process) pub/sub for .NET; use when notification throughput or allocation pressure is the concern.
  • MassTransit/MassTransit — actual distributed messaging over transports; use when you need queues, durability, or cross-service delivery, not in-process dispatch.
  • rebus-org/Rebus — lightweight service bus for eventing across process boundaries rather than a request pipeline.
  • Direct DI (no mediator) — inject the handler interface and call it; use when the indirection buys nothing and you value navigable call graphs.

History

| Version | Date | Notes | |---------|------|-------| | Initial | 2014-03-03 | Created by Jimmy Bogard; Apache-2.0[^2]. | | 9.0 | 2020-10-07 | Mature Apache-2.0 line; widely adopted. | | 11.0 | 2022-09-30 | Last of the pre-consolidation DI packages. | | 12.0 | 2023-02-14 | DI reworked onto Microsoft.Extensions.DependencyInjection. | | 13.0 | 2025-07-02 | Lucky Penny era: RPL-1.5 + commercial license, license keys[^4]. | | 14.0 | 2025-11-20 | Continued under commercial licensing. | | 14.2 | 2026 | Current line (see NuGet for exact latest)[^5]. |

As of mid-2026 the repository is active — pushed within the last two weeks, ~11.8k stars, ~2.2k forks — with a near-empty issue tracker, consistent with a small, now-commercial project maintained by its owner rather than a large contributor pool.

References

[^1]: MediatR README and wiki. https://github.com/LuckyPennySoftware/MediatR [^2]: Repository created 2014-03-03 (GitHub API created_at). Original owner Jimmy Bogard (jbogard/MediatR, now redirected). https://github.com/jbogard [^3]: Jimmy Bogard, recurring guidance that MediatR is a library, not an architecture. https://www.jimmybogard.com/ [^4]: License text (LICENSE.md), Reciprocal Public License 1.5 with commercial option via Lucky Penny Software. https://luckypennysoftware.com/license [^5]: MediatR on NuGet. https://www.nuget.org/packages/MediatR

Tags

csharp, dotnet, mediator-pattern, cqrs, in-process-messaging, dependency-injection, pipeline-behavior, commercial-license, rpl-1.5, request-response