Skip to content

De-duping notification handlers before dispatching#1159

Merged
jbogard merged 1 commit intomainfrom
1118-notification-handler-called-twice-for-same-event
Feb 23, 2026
Merged

De-duping notification handlers before dispatching#1159
jbogard merged 1 commit intomainfrom
1118-notification-handler-called-twice-for-same-event

Conversation

@jbogard
Copy link
Collaborator

@jbogard jbogard commented Feb 23, 2026

Fix: Duplicate Notification Handler Invocation (Issue #1118)

Context

When a derived notification type (E2 : E1) exists alongside a handler for the base type (C1 : INotificationHandler<E1>) and a handler for the derived type (C2 : INotificationHandler<E2>), publishing E2 incorrectly calls C1 twice with DI containers that support variance (e.g. DryIoc).

Root cause: INotificationHandler<in TNotification> is contravariant. MediatR's ServiceRegistrar registers C1 for both INotificationHandler<E1> AND INotificationHandler<E2> (via CanBeCastTo / IsAssignableFrom). This is intentional — it allows MSDI (which does not support variance) to dispatch C1 when publishing E2. However, DI containers that do support variance (like DryIoc) additionally return C1 from their own contravariant resolution of INotificationHandler<E1>, yielding C1 twice.

Why a registration-only fix doesn't work: Removing C1's INotificationHandler<E2> registration breaks MSDI users — C1 would never be called when publishing E2 with MSDI.

Fix

Deduplicate by handler type in NotificationHandlerWrapperImpl<TNotification>.Handle() before building the executor list. Each handler class is then invoked at most once, regardless of how many times the DI container returns it.

src/MediatR/Wrappers/NotificationHandlerWrapper.cs:

var handlers = serviceFactory
    .GetServices<INotificationHandler<TNotification>>()
    .GroupBy(static x => x.GetType())   // deduplicate
    .Select(static g => g.First())
    .Select(static x => new NotificationHandlerExecutor(...));

ServiceRegistrar.cs is unchanged.

Tests

test/MediatR.Tests/MicrosoftExtensionsDI/Issue1118Tests.cs (new file):

  • Publishing_BaseEvent_Should_Call_BaseEventHandler_Once
  • Publishing_DerivedEvent_Should_Call_BaseEventHandler_ExactlyOnce
  • Publishing_DerivedEvent_Should_Call_DerivedEventHandler_Once

Handlers use IServiceProvider (always injectable) to soft-resolve CallLog, so PipelineTests (which uses ValidateOnBuild=true on the same assembly) does not fail.

Verification

dotnet test test/MediatR.Tests/MediatR.Tests.csproj
# 169 passed, 0 failed

@jbogard jbogard added this to the 14.1.0 milestone Feb 23, 2026
@jbogard jbogard linked an issue Feb 23, 2026 that may be closed by this pull request
@jbogard jbogard requested a review from Copilot February 23, 2026 16:27
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes issue #1118 where notification handlers were being called multiple times when using DI containers that support variance (e.g., DryIoc). When a derived notification type (E2 : E1) exists alongside handlers for both base (C1 : INotificationHandler) and derived types (C2 : INotificationHandler), publishing E2 would incorrectly invoke C1 twice due to the contravariant interface combined with MediatR's intentional dual registration for MSDI compatibility.

Changes:

  • Added deduplication logic in NotificationHandlerWrapper.cs using GroupBy(x => x.GetType()) pattern to ensure each handler type is invoked only once
  • Added comprehensive regression tests in Issue1118Tests.cs to verify handlers are called exactly once for both base and derived events
  • Minor whitespace cleanup in ServiceRegistrar.cs

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated no comments.

File Description
src/MediatR/Wrappers/NotificationHandlerWrapper.cs Added deduplication logic using GroupBy/Select pattern to prevent duplicate handler invocations
test/MediatR.Tests/MicrosoftExtensionsDI/Issue1118Tests.cs New regression tests verifying handlers are called exactly once for base and derived notification events
src/MediatR/Registration/ServiceRegistrar.cs Removed trailing whitespace (formatting only)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jbogard jbogard merged commit 1325768 into main Feb 23, 2026
9 checks passed
@jbogard jbogard deleted the 1118-notification-handler-called-twice-for-same-event branch February 23, 2026 16:35
This was referenced Mar 3, 2026
This was referenced Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Notification handler called twice for same event

2 participants