Selective MediatR handlers in .NET

Apr 10, 2025

Selective MediatR registration

There is a classic scenario in clean architecture when you have multiple daemons/services, each only needing a subset of MediatR commands/handlers. Registering all the commands from the Application layer can lead to unnecessary dependencies being injected into the wrong context or causing DI exceptions.

The Problem:

  • All your MediatR commands/handlers live in the Application layer.
  • When one daemon registers MediatR, it discovers all handlers.
  • Some handlers have dependencies not available in this daemon, causing exceptions.

βœ… Solution: Selective Registration of MediatR Handlers

You can split your MediatR registration by assembly scanning, so that each daemon only registers the handlers it actually needs.

πŸ›  Option 1: Use Custom Assembly Scanning

Use marker types to specify only the handlers that each daemon should load:

services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<MyDaemonOnlyHandler>();
});
  • Define a dummy class in the same namespace/assembly as the handlers relevant to that daemon. Use it as a marker.

If your handlers are all in the same assembly, but you want per-namespace registration, you need to go further.


🧩 Option 2: Custom IRequestHandler Filtering

Write a custom MediatR ServiceFactory or use something like Scrutor to only register specific IRequestHandler<T> implementations that meet a criteria (e.g., namespace or attributes).

Example using Scrutor:

services.Scan(scan => scan
    .FromAssemblyOf<SomeHandlerFromThisDaemon>()
    .AddClasses(classes => classes.AssignableTo(typeof(IRequestHandler<,>))
                                  .InNamespaceOf<SomeHandlerFromThisDaemon>())
    .AsImplementedInterfaces()
    .WithScopedLifetime());

This avoids registering all handlers indiscriminately.


Let’s walk through Option 2β€”using namespace-based filtering with Scrutor to register only the MediatR handlers relevant to each daemon.


🎯 Goal:

Each daemon only registers the IRequestHandler implementations from its own namespace to avoid loading unrelated dependencies.


βœ… Step-by-Step Setup

1. Folder Structure & Handler Organization

Imagine you have this structure in the Application layer:

Application/
β”‚
β”œβ”€β”€ Common/
β”‚   └── SharedHandler.cs
β”‚
β”œβ”€β”€ DaemonA/
β”‚   └── Commands/
β”‚       β”œβ”€β”€ CreateFooCommand.cs
β”‚       └── CreateFooCommandHandler.cs
β”‚
β”œβ”€β”€ DaemonB/
β”‚   └── Commands/
β”‚       β”œβ”€β”€ CreateBarCommand.cs
β”‚       └── CreateBarCommandHandler.cs

Each handler lives in a namespace like:

namespace Application.DaemonA.Commands
{
    public class CreateFooCommandHandler : IRequestHandler<CreateFooCommand> { ... }
}

2. Use Scrutor to Register Only Relevant Handlers

In DaemonA’s Startup.cs or wherever you build your DI container:

using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Scrutor;

public static class MediatRExtensions
{
    public static IServiceCollection AddDaemonAHandlers(this IServiceCollection services)
    {
        services.Scan(scan => scan
            .FromAssemblyOf<CreateFooCommandHandler>() // Assembly of your Application project
            .AddClasses(c => c.AssignableTo(typeof(IRequestHandler<,>))
                              .InNamespace("Application.DaemonA.Commands"))
            .AsImplementedInterfaces()
            .WithScopedLifetime());

        services.AddMediatR(cfg => 
        {
	        cfg.RegisterServicesFromAssembly(typeof(IMediator).Assembly)
        });

        return services;
    }
}

You can create a similar method for DaemonB by changing the namespace and handler type.


3. Register It in Your Daemon's DI Setup

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDaemonAHandlers(); // Register only DaemonA-related MediatR handlers
        // other services...
    }
}

πŸ§ͺ Optional Debug

If you want to confirm what handlers are being registered, log them:

var handlerTypes = typeof(CreateFooCommandHandler).Assembly
    .GetTypes()
    .Where(t => typeof(IRequestHandler<,>).IsAssignableFrom(t) && 
                t.Namespace == "Application.DaemonA.Commands");
foreach (var t in handlerTypes)
{
    Console.WriteLine($"Registering handler: {t.FullName}");
}

πŸš€ Result

  • Only handlers in Application.DaemonA.Commands are registered.

  • No DI issues from missing dependencies in DaemonB.

  • Keeps Application clean and reusable across daemons.

Vitalii Lakomov