# Two Biggest MediatR Mistakes


MediatR is useful at an application boundary: a caller sends one request, and one handler owns that use case. Two common shortcuts weaken that model—sending more requests from inside a handler and combining several request contracts in one handler class.

Both shortcuts look like ways to reuse dependencies or avoid small classes. In practice, they hide orchestration, make navigation harder, and couple use cases that should be independently testable and replaceable. This article explains the failure modes and the simpler boundaries that avoid them.

## Mistake 1: Calling One MediatR Handler from Another

Consider the following situation. Suppose you need to handle a message:

```csharp
public class PingHandler : IRequestHandler<Ping, string>
{
    public Task<string> Handle(Ping request, CancellationToken token = default) =>
        Task.FromResult("Pong");
}
```

Then a new requirement arrives: after handling this message, you need to process other messages based on either the original handler's result or the incoming message. One tempting implementation looks like this:

```csharp
public class PingHandler : IRequestHandler<Ping, string>
{
    private readonly IMediator _mediator;

    public PingHandler(IMediator mediator) => _mediator = mediator;

    public async Task<string> Handle(Ping request, CancellationToken token = default)
    {
        await _mediator.Send(new AfterPing(request), token);
        return "Pong";
    }
}
```

Keep this orchestration out of the handler.

First, it is confusing. MediatR already makes IntelliSense navigation harder; there is no need to make it worse.

It is also worth remembering that, by design, MediatR serves as an **external** bridge to your application's actual behavior—behavior that is specific to the domain you have chosen. That is precisely what the cover image illustrates.

Calling other messages from inside a handler is therefore an anti-pattern in the context of the library itself. In the words of MediatR's author:

> The indirection of a handler is good at the application level, but just got confusing once we got inside a handler (and it introduced coupling).

The tool exists to provide request-response entry points at the top level of the application while minimizing coupling. The code above does exactly the opposite.

There are several ways to keep the orchestration visible:

*   Return the data needed to construct the next message, then send it at the higher level where the original handler was called—the simplest option.
    
*   Move the shared logic into an application or domain service.
    
*   Use an extension method for genuinely local, stateless reuse.
    
*   Use a MediatR pipeline behavior for cross-cutting concerns.
    

## Mistake 2: Handling Multiple MediatR Requests in One Handler

Suppose you have a group of messages that work with the same entity. They have roughly the same dependencies, so instead of writing several handlers, you combine them into one and end up with something like this:

```csharp
public class MyEntityRequestHandler :
    IRequestHandler<CreateMyEntityRequest, MyEntity>,
    IRequestHandler<GetMyEntityRequest, MyEntity>,
    IRequestHandler<GetAllMyEntitiesRequest, List<MyEntity>>;
```

Once again, this goes against the library's core idea: splitting an application into a set of distinct requests to improve flexibility and maintainability. This approach takes us back to bloated interfaces and violates the Interface Segregation Principle (ISP).

Again, in the library author's words:

> Don't combine your handlers, keep them separate, reduce coupling across handlers.

## Better MediatR Handler Boundaries

Keep orchestration visible at the application boundary, and keep one handler focused on one request contract. Shared behavior can still live in domain services, application services, decorators, or pipeline behaviors; it does not need to be hidden behind another mediator call.

These are design rules, not syntax rules. If a workflow genuinely coordinates several use cases, model that workflow explicitly and test it as such. The warning is against accidental handler graphs whose control flow can be discovered only by tracing `Send` calls.

## MediatR Handler FAQ

### Should a MediatR handler call another handler?

Usually, no. Put workflow orchestration at the application boundary and move reusable behavior into an application service, domain service, decorator, or pipeline behavior. That keeps the sequence visible and prevents handlers from becoming an implicit call graph.

### Can one class implement several `IRequestHandler` interfaces?

The type system allows it, but sharing a class couples request contracts that should be independently navigable, testable, and replaceable. Separate handler classes can still depend on the same lower-level service when they genuinely share behavior.

### When should I use a MediatR pipeline behavior?

Use a pipeline behavior for concerns that consistently wrap many requests, such as validation, logging, or transaction handling. Do not use it to hide a business workflow whose sequence is important to the use case.

You can read more about the subject in these sources:

*   [Dealing with Duplication in MediatR Handlers](https://lostechies.com/jimmybogard/2016/12/12/dealing-with-duplication-in-mediatr-handlers/)
    
*   [MediatR issue #400](https://github.com/jbogard/MediatR/issues/400)
    
*   [MediatR issue #434](https://github.com/jbogard/MediatR/issues/434)
    
*   [MediatR issue #281](https://github.com/jbogard/MediatR/issues/281)
    

* * *

## Related .NET Architecture Guides

- [AutoMapper in .NET: When You May Not Need It](https://steponeit.hashnode.dev/automapper-in-net-when-you-may-not-need-it)

Follow [Stepami on GitHub](https://github.com/Stepami) for open-source C#/.NET projects, compiler experiments, production-focused examples, and new releases.
