Generic NotificationHandler not called #118
Comments
You might need to register the open generic explicitly. |
Hmm, I thought open generics were handled nowadays. Still doesn't explain why the other handler is called twice though. When I add this before builder.Build: builder.Services.AddTransient<INotificationHandler<EventNotification<SomethingHappenedEvent>>, GenericEventHandler<EventNotification<SomethingHappenedEvent>>>(); The generic handler does indeed get called... but the other one is still called twice! |
This will come down to the features of the container I think. Check to see what's registered in the container. You might have to switch containers, the default one isn't too great with the more complex generic scenarios. |
These are all the services related to MediatR that are registered:
The last one is added by the manual registration (from my previous comment), but I think the culprit is
Which somehow manages to call the one above it instead? |
you might want to have the open generics defined explicitly. |
This is exactly the same issue I'm experiencing here like you are... but I'm developing on windows, and I have it on my machine... so I don't think the os has anything to do with it (at least in my case). It has indeed something to do with how the handlers are resolved from DI... I'm not sure MediatR can do anything about it, but it's very confusing when you run into it to say the least... I don't think my case is very special, as it can be minimized to the code you see above. I would think that would have to work out of the box, or otherwise throw some meaningful exception that there's something wrong with the registration? Just thinking out loud here... not sure if it's possible or feasible... but when you're resolving the handlers to be executed couldn't you compare them against each other first to see if they're not the same and throw an exception if they are? |
This test passes for me: [Fact]
public void Should_resolve_handlers()
{
var services = new ServiceCollection();
services.AddMediatR(typeof(HandlerResolutionTests));
var serviceProvider = services.BuildServiceProvider();
var handlers = serviceProvider
.GetServices<INotificationHandler<EventNotification<SomethingHappenedEvent>>>()
.ToList();
handlers.Count.ShouldBe(1);
handlers.Select(handler => handler.GetType())
.ShouldContain(typeof(SomethingHappenedEventHandler));
} In .NET 6 on Windows. |
It doesn't if you define a GenericEventHandler like this in the same assembly: public class GenericEventHandler<TNotification> : INotificationHandler<TNotification>
where TNotification : INotification
{
public Task Handle(TNotification notification, CancellationToken cancellationToken)
{
Debug.WriteLine("generic handled");
return Task.CompletedTask;
}
} I mean that's probably normal in how the test is set up... but my point is that I don't have a problem as long as I don't define that Generic handler... And apparently it's only in an aspnetcore context? I can't seem to repeat the problem in a test context in the way you set up your test above (when I do a publish there, both handlers are called correctly like they should). |
This is the best I could do to set up tests that fail in this situation: https://github.com/fretje/MediatRHandlerCountTest/blob/master/TestProject1/UnitTest1.cs |
So I converted these to unit tests just around the container (to remove the test host and actions from the equation, and the tests all pass: [Fact]
public async Task TestSomethingHappened()
{
var services = new ServiceCollection();
services.AddMediatR(typeof(HandlerResolutionTests));
var counts = new HandlerCounter();
services.AddSingleton(counts);
var serviceProvider = services.BuildServiceProvider();
var mediator = serviceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new EventNotification<SomethingHappenedEvent>(new SomethingHappenedEvent()));
counts.ShouldNotBeNull();
counts.SomethingHappenedCount.ShouldBe(1);
counts.SimpleCount.ShouldBe(0);
counts.GenericCount.ShouldBe(1);
}
[Fact]
public async Task TestSimple()
{
var services = new ServiceCollection();
services.AddMediatR(typeof(HandlerResolutionTests));
var counts = new HandlerCounter();
services.AddSingleton(counts);
var serviceProvider = services.BuildServiceProvider();
var mediator = serviceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new SimpleNotification());
counts.ShouldNotBeNull();
counts.SomethingHappenedCount.ShouldBe(0);
counts.SimpleCount.ShouldBe(1);
counts.GenericCount.ShouldBe(1);
}
[Fact]
public async Task TestBoth()
{
var services = new ServiceCollection();
services.AddMediatR(typeof(HandlerResolutionTests));
var counts = new HandlerCounter();
services.AddSingleton(counts);
var serviceProvider = services.BuildServiceProvider();
var mediator = serviceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new EventNotification<SomethingHappenedEvent>(new SomethingHappenedEvent()));
await mediator.Publish(new SimpleNotification());
counts.ShouldNotBeNull();
counts.SomethingHappenedCount.ShouldBe(1);
counts.SimpleCount.ShouldBe(1);
counts.GenericCount.ShouldBe(2);
} It looks like your code doesn't |
Async await is not the problem. I was really hoping to, but I just forgot it in the example code (my original code has the await). I fixed that in the example code... same problem still exists. The problem is only when run from an action method. Can't repeat it by only using the serviceProvider. Could it be a bug in aspnetcore maybe? |
Well nuts. When I debug and examine the Looking at the return values from the container in the test: var services = application.Services
.GetServices<INotificationHandler<EventNotification<SomethingHappenedEvent>>>()
.ToList(); |
Yep, strange indeed... it should actually give you 2 handlers, but one of them should be that |
This is even odder, building the service provider right after registration and resolving there: WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
var singleton = new HandlerCounter();
builder.Services.AddSingleton(singleton);
builder.Services.AddMediatR(typeof(Program));
var serviceProvider = builder.Services.BuildServiceProvider();
var handlers = serviceProvider.GetServices<INotificationHandler<EventNotification<SomethingHappenedEvent>>>(); |
Yep, what I was trying to say the whole time :-) only in the context of a request... |
Oh yeah, was just trying to find that out as wel... guess it is a bug in aspnetcore then? |
Yeah it's bizarre, I can't repro that in a unit test. This passes: [Fact]
public void Should_register_correctly()
{
var builder = WebApplication.CreateBuilder();
var singleton = new HandlerCounter();
builder.Services.AddSingleton(singleton);
builder.Services.AddMediatR(typeof(Program));
var serviceProvider = builder.Services.BuildServiceProvider();
var handlers = serviceProvider
.GetServices<INotificationHandler<EventNotification<SomethingHappenedEvent>>>()
.ToList();
handlers.Count.ShouldBe(2);
var handlersTypes = handlers
.Select(h => h.GetType())
.ToList();
handlersTypes.ShouldContain(typeof(SomethingHappenedEventHandler));
handlersTypes.ShouldContain(typeof(GenericEventHandler<EventNotification<SomethingHappenedEvent>>));
var app = builder.Build();
var appHandlers = app.Services
.GetServices<INotificationHandler<EventNotification<SomethingHappenedEvent>>>()
.ToList();
appHandlers.Count.ShouldBe(2);
var appHandlersTypes = appHandlers
.Select(h => h.GetType())
.ToList();
appHandlersTypes.ShouldContain(typeof(SomethingHappenedEventHandler));
appHandlersTypes.ShouldContain(typeof(GenericEventHandler<EventNotification<SomethingHappenedEvent>>));
} |
So maybe kestrel messes it up somehow? |
No, Kestrel isn't run from the unit tests. I got it to fail though, it's args that are different when you run from VS. The Development environment makes the test fail: [Fact]
public void Should_register_correctly()
{
var args = new[]
{
"--environment=Development"
};
var builder = WebApplication.CreateBuilder(args);
var singleton = new HandlerCounter();
builder.Services.AddSingleton(singleton);
builder.Services.AddMediatR(typeof(Program));
var serviceProvider = builder.Services.BuildServiceProvider();
var handlers = serviceProvider
.GetServices<INotificationHandler<EventNotification<SomethingHappenedEvent>>>()
.ToList();
handlers.Count.ShouldBe(2);
var handlersTypes = handlers
.Select(h => h.GetType())
.ToList();
handlersTypes.ShouldContain(typeof(SomethingHappenedEventHandler));
handlersTypes.ShouldContain(typeof(GenericEventHandler<EventNotification<SomethingHappenedEvent>>));
var app = builder.Build();
var appHandlers = app.Services
.GetServices<INotificationHandler<EventNotification<SomethingHappenedEvent>>>()
.ToList();
appHandlers.Count.ShouldBe(2);
var appHandlersTypes = appHandlers
.Select(h => h.GetType())
.ToList();
appHandlersTypes.ShouldContain(typeof(SomethingHappenedEventHandler));
appHandlersTypes.ShouldContain(typeof(GenericEventHandler<EventNotification<SomethingHappenedEvent>>));
} This looks like a bug. |
Ahaaa, nice catch! Now how to report this... or will you do the honours? ;-) |
I've got a repro that completely removes MediatR from the equation, I'll open an issue with those tests. |
Nice! Thanks a lot! Please post a link... I'd like to follow ;-) |
Sure thing! dotnet/runtime#65145 |
Nice, thanks again. Spotted a mistake I think in your last part... It says
Should probably be
|
Lol good catch! |
Is there any known workaround? |
I think I found a Solution that works for me and I'll leave it here in case someone else is in a similar situation You can extend this solution by iterating over all INotificationHandler in your assemblies, but in my project this is enough Events:
NotificationHandler:
Configure Services:
|
@jbogard I found some problems in MediatR.Courier KuraiAndras/MediatR.Courier#10, and I am unsure whether I am affected by this bug? using Microsoft.Extensions.DependencyInjection;
namespace MediatR.Courier.GenericTests;
public class GenericTest
{
public record GenericMessage<T>(T Data) : INotification;
public class GenericHandler<T> : INotificationHandler<GenericMessage<T>>
{
public Task Handle(GenericMessage<T> notification, CancellationToken cancellationToken) => Task.CompletedTask;
}
[Fact]
public void This_Should_Not_Throw()
{
var services = new ServiceCollection()
.AddMediatR(typeof(GenericTest).Assembly);
var serviceProvider = services.BuildServiceProvider();
var mediator = serviceProvider.GetRequiredService<IMediator>();
mediator.Publish(new GenericMessage<string>("Hello"));
}
[Fact]
public void This_Should_Not_Throw_Also()
{
var services = new ServiceCollection()
.AddMediatR(typeof(GenericTest).Assembly)
.AddTransient(typeof(INotificationHandler<>), typeof(GenericHandler<>));
var serviceProvider = services.BuildServiceProvider();
var mediator = serviceProvider.GetRequiredService<IMediator>();
mediator.Publish(new GenericMessage<string>("Hello"));
}
} Both of these tests throw the following:
Is this: the same bug/I am doing something wrong/unsupported behavior? |
This doesn't work for me, as it's identical to the way I already register the generic handlers. |
The last solution didn't work for me either: tests are fine with a brand new ServiceCollection, but at runtime it gets confused. The only only suspicious difference between runtime and test is an ObjectPool being transient in one and singleton in the other but it shouldn't affect any of this stuff. In the end I replaced the mediator implementation with a custom one (ReMediator :) ) where I overrode PublishCore and made sure each MethodInfo is only called once. Is this an ok solution for now?
I then registered the new Mediator this way:
|
Not sure if this is a bug, or something I'm doing wrong...
Also not sure how to explain it properly... I think the easiest will be to just show the code.
This is a basic full web project (yay for minimal apis) with everything functioning as intended:
When you go to the homepage, an event is published and handled by the
SomethingHappendEventHandler
. (something happened handled
is printed in the debug output).Now I want to add another notificationhandler that handles all notifications:
Now when I go to the homepage, the
SomethingHappenedEventHandler
is called twice (!) and theGenericEventHandler
isn't called at all. (something happened handled
is printed twice in the debug output, nogeneric handled
is printed)I'm really confused as to how this happens or where to go from here...
The problem might be with how the DomainEvents are designed... but this is what I have to work with for now...
When I create a simple notification (not derived from EventNotification):
And publish that, the generic handler does get called.
Really strange...
The text was updated successfully, but these errors were encountered: