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
MediatRcommands/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
DaemonBby 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.Commandsare registered. -
No DI issues from missing dependencies in
DaemonB. -
Keeps
Applicationclean and reusable across daemons.