-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
We are using MediatR 12.5.0 with the DryIoc IoC container. We are finding that in some cases, notification handlers are being called twice for the same event.
Here's a simple repro:
App.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
<PackageReference Include="MediatR" Version="12.5.0" />
</ItemGroup>
</Project>Program.cs
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace App;
public class E1 : INotification { }
public class E2 : E1 { }
public class C1 : INotificationHandler<E1>
{
public Task Handle(
E1 notification,
CancellationToken cancellationToken)
{
Console.WriteLine($"C1 handling {notification.GetType().Name}");
return Task.CompletedTask;
}
}
public class C2 : INotificationHandler<E2>
{
public Task Handle(
E2 notification,
CancellationToken cancellationToken)
{
Console.WriteLine($"C2 handling {notification.GetType().Name}");
return Task.CompletedTask;
}
}
public class Program
{
private static Rules DefaultRules => Rules.Default
.With(Made.Of(FactoryMethod.ConstructorWithResolvableArguments));
public static void Main(string[] args)
{
var container = new Container(DefaultRules);
var services = new ServiceCollection();
var assembly = typeof(Program).Assembly;
services.AddMediatR(config =>
{
config.RegisterServicesFromAssemblies(assembly);
});
container.Populate(services);
var publisher = container.Resolve<IPublisher>();
publisher.Publish(new E1());
Console.WriteLine("---");
publisher.Publish(new E2());
Console.WriteLine();
Console.Write("Press any key to exit...");
Console.ReadKey();
}
}When you run this, you'll get the following output:
C1 handling E1
---
C1 handling E2
C1 handling E2
C2 handling E2
Note that C1 handles the E2 event twice. Is this a bug, or by design? It feels odd that defining a notification handler for a derived event (E2) on a type (C2) would cause a notification handler for a base event (E1) on a completely unrelated type (C1) to be called twice.
If you comment out the definition of C2, then C1 only gets called once, and you get the expected output:
C1 handling E1
---
C1 handling E2
For now, we are working around this by de-duping handlers in a custom EventPublisher, but this is obviously not ideal, and it's frankly subject to edge cases where the kludge wouldn't work, e.g., if the same class wanted to handle both a base and derived event separately (not sure if this is supported, or even a good idea, but it's just an example). It might be possible to work around this issue further upstream too by de-duping the ServiceCollection before calling IContainer.Populate, but either way, it's still a hack.
To that last point, the "damage appears to be done" after the IServiceCollection.AddMediatR call, because the returned IServiceCollection contains two ServiceDescriptors for C1 - one for E1, and one for E2:
ServiceType: MediatR.INotificationHandler`1[App.E1] Lifetime: Transient ImplementationType: App.C1
ServiceType: MediatR.INotificationHandler`1[App.E2] Lifetime: Transient ImplementationType: App.C1
ServiceType: MediatR.INotificationHandler`1[App.E2] Lifetime: Transient ImplementationType: App.C2
I believe this rules out the IoC container (DryIoc in my case) as being an influencing factor, as this is all MediatR code before the IoC container is involved; that doesn't happen until the Populate call later.
I see other issues dealing with duplicate notification handling, but I'm not sure that they're the same as this case.
Any guidance would be appreciated. Thanks.