MediatR
MediatR copied to clipboard
NotificationHandler gets called twice
Hello,
unfortunately, I´ve discovered that the NotificationHandler is getting called twice. I´ve read a few articles on the web but the problem is still not fixed.
I am using the following versions:
- MediatR 10.0.1
- MediatR.Extensions.FluentValidation.AspNetCore 2.0.0
- MediatR.Extensions.Microsoft.DependencyInjection 10.0.1
This is how I register MediatR
services.AddMediatR(typeof(IInstaller).Assembly);
services.AddValidatorsFromAssembly(typeof(IInstaller).Assembly);
ValidatorOptions.Global.LanguageManager.Culture = new CultureInfo("de-DE");
The type 'IInstaller' is in the corresponding project where my NoticationHandlers and RequestHandlers are.
This is how I publish a Notification
public abstract class DomainEvent {
public Guid EventId { get; set; }
public bool IsPublished { get; set; }
public DateTimeOffset DateOccurred { get; set; }
protected DomainEvent() {
EventId = Guid.NewGuid();
DateOccurred = DateTimeOffset.Now;
}
}
public class UserStateChangedEvent : DomainEvent {
public ApplicationUser ApplicationUser { get; }
public UserStateChangedEvent(ApplicationUser applicationUser) {
ApplicationUser = applicationUser;
}
}
private async Task DispatchDomainEventsAsync(ICollection objectsToSave, ICollection objectsToDelete) {
IEnumerable<DomainEvent> saveDomainEvents = objectsToSave.OfType<AggregatedRoot>().Where(x=> x.DomainEvents != null).Where(x => x.DomainEvents.Any()).SelectMany(x=>x.DomainEvents);
IEnumerable<DomainEvent> deleteDomainEvents = objectsToDelete.OfType<AggregatedRoot>().Where(x=> x.DomainEvents != null).Where(x => x.DomainEvents.Any()).SelectMany(x=>x.DomainEvents);
IEnumerable<DomainEvent> concatDomainEvents = saveDomainEvents.Concat(deleteDomainEvents);
foreach (DomainEvent domainEvent in concatDomainEvents) {
domainEvent.IsPublished = true;
await _domainEventService.Publish(domainEvent);
}
}
public class DomainEventService : IDomainEventService {
private readonly IPublisher _publisher;
public DomainEventService(IPublisher publisher) {
_publisher = publisher;
}
public async Task Publish(DomainEvent domainEvent) {
await _publisher.Publish(GetNotificationCorrespondingToDomainEvent(domainEvent));
}
private INotification GetNotificationCorrespondingToDomainEvent(DomainEvent domainEvent)
{
return (INotification)Activator.CreateInstance(
typeof(DomainEventNotification<>).MakeGenericType(domainEvent.GetType()), domainEvent)!;
}
}
public class UserStateChangedEventHandler : INotificationHandler<DomainEventNotification<UserStateChangedEvent>> {
public async Task Handle(DomainEventNotification<UserStateChangedEvent> notification, CancellationToken cancellationToken) {
//DoStuff
}
}
Hello, I experienced the same issue I think this one in the DependencyInjection Extension captures the root cause
https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/issues/118
in the blazor mode have the same issue Does anyone know how to solve the code
Same problem.
I think that it is not a Mediatr problem. I have investigated my project and Mediatr source code and found out that the handler was registered twice. In my case I'm using Autofac and MediatR.Extensions.Autofac.DependencyInjection:
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterMediatR(typeof(Startup).Assembly);
builder.RegisterAssemblyTypes(Assembly.GetEntryAssembly()).AsImplementedInterfaces();
}
According to my investigation, both RegisterMediatR and RegisterAssemblyTypes registered my handler. I've checked Autofac registration list and saw that handler was registered twice:
var types = container.ComponentRegistry.Registrations
.Where(r => typeof(MyNotificationHandler).IsAssignableFrom(r.Activator.LimitType))
.Select(r => r.Activator.LimitType);
So, I think you need to look towards dependencies registration
if you follow my link, you'll end up with this issue, which seems to be the root cause: https://github.com/dotnet/runtime/issues/65145
Hey , after fighting with this today for quite some time it all boils down to your service container setup , make sure your DI is setup correctly.
Just noticed this issue yesterday while doing some tests with MediatR notifications, I investigated around, looked at source code and finally found this issue, I can confirm the related issue on dotnet/runtime seems to be the root cause, I set up a pair of tests using the Startup class of my web service using the following structure:
// Arrange
var host = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder => {
builder.UseTestServer()
.UseStartup<Startup>();
})
.UseEnvironment(Environments.Development) // Changing this between Development and Production
.Build();
var provider = host. Services;
var handlers = provider
.GetServices<INotificationHandler<MyDomainEvent>>()
.ToArray();
// Assert
Assert.That(handlers, Has.Length.EqualTo(1));
Both tests are identical except for the Environment, one using Development
other using Production
and the one for Development
fails.
Given the timeline for the proper fix (.NET 8 apparently), has anyone found a workaround?
Edit: For now I'm thinking on instantiating and executing notifications on development through custom code on a dispatcher abstraction that filters duplicate handlers, but if anyone knows of a better workaround, it would be appreciated
@Inheritech https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/issues/118#issuecomment-1096954146 This is my workaround
I had same trouble. Check whether in the same asembly is not generic notificationhandler class. App builder creates two instance per this item. Typicaly for decorator.
internal class DomainEventHandlerDecorator<T> : INotificationHandler<T>
where T : IDomainEvent
Mediator adds it into service collection and app builder creates two instances for this type. Or more when multiple generic class is present.
- move this class outside the mediators scaner assembly
- remove it from iservices by descriptor
- or is there any better solution ?
source https://github.com/Lux2057/.NET-Core-CRUD/issues/75
@LukasKuchta you are right and you saved my life :-)
Inspired by your issue i decided to move the generic interface outside the assembly and i created an external common class library leaving the same namespaces to avoid a more complex refactoring.
After moving the classes in the external lib i hadn't anymore the duplicate notification.
Thank you a lot!!
Luigi
I had same trouble. Check whether in the same asembly is not generic notificationhandler class. App builder creates two instance per this item. Typicaly for decorator.
internal class DomainEventHandlerDecorator<T> : INotificationHandler<T> where T : IDomainEvent
Mediator adds it into service collection and app builder creates two instances for this type. Or more when multiple generic class is present.
- move this class outside the mediators scaner assembly
- remove it from iservices by descriptor
- or is there any better solution ?
source Lux2057/.NET-Core-CRUD#75
-
solution based on: https://github.com/Lux2057/.NET-Core-CRUD/issues/75 ` public class ReMediatr : Mediator { public ReMediatr(IServiceProvider serviceProvider) : base(serviceProvider) { }
protected override async Task PublishCore(IEnumerable<NotificationHandlerExecutor> handlerExecutors, INotification notification, CancellationToken cancellationToken) { HashSet<Type> processedHandlers = new(); foreach (var handler in handlerExecutors) { if (handler is null) { continue; } Type handlerType = handler.GetType(); if (processedHandlers.Contains(handlerType)) { continue; } processedHandlers.Add(handlerType); await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false); } }
} `
Then just register:
services.AddMediatR(options=> { options.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()); options.MediatorImplementationType = typeof(ReMediatr); });
- Remove remove it from IServiceCollection by service descriptor:
var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(INotificationHandler<>); if (serviceDescriptor != null) { services.Remove(serviceDescriptor); }
I'm using Mediatr version: 12.0.1