You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Allow Custom Registration of INotificationHandler without Duplicate Registrations
Problem Description
Currently, when using Mediator, developers cannot register their INotificationHandler<T> implementations manually without causing duplicate service registrations. If you register a handler yourself and then call services.AddMediator(...), the handler gets registered twice in the DI container.
Additionally, calling services.AddMediator() first and then attempting to manually register handlers doesn't work either, because Mediator uses factory functions (GetRequiredService<T>()) rather than direct type registrations for notification handlers.
Current Behavior
// Scenario 1: Manual registration first (causes duplicates)services.AddScoped<INotificationHandler<MyNotification>,MyNotificationHandler>();services.AddMediator();// Results in duplicate registration// Scenario 2: AddMediator first (doesn't work due to factory pattern)services.AddMediator();services.AddScoped<INotificationHandler<MyNotification>,MyNotificationHandler>();// Again duplicate registration// Scenario 3: Attempt to override after AddMediator (impossible due to factory registration)services.AddMediator();// Try to find and remove auto-registration to replace with custom lifetimevarexistingHandler=services.FirstOrDefault(s =>s.ServiceType==typeof(INotificationHandler<MyNotification>)&&s.ImplementationType==typeof(MyNotificationHandler));// This returns null!// existingHandler is null because ImplementationType is null for factory registrations// The actual registration uses: sp => sp.GetRequiredService<MyNotificationHandler>()
Proposed Solutions
I see two potential approaches to resolve this issue:
Option 1: Roslyn Analyzer (Less Preferred)
Create a Roslyn analyzer that emits compilation errors when developers attempt to manually register INotificationHandler<T> implementations.
Option 2: Smart Registration Check (Preferred)
Modify the Mediator registration logic to check for existing handler registrations before adding new ones.
Preferred Implementation
My preferred solution would implement the following behavior:
Allow manual handler registration with any desired lifetime (Transient, Scoped, Singleton)
Smart duplicate detection in services.AddMediator(...):
Check if a service for the given handler type and implementation type is already registered
If already registered → ignore it
If not registered → register with default lifetime
Code Examples
Desired Usage Pattern
publicclassMyNotificationHandler:INotificationHandler<OrderCreated>{publicTaskHandle(OrderCreatednotification,CancellationTokencancellationToken){// Handle the notificationreturnTask.CompletedTask;}}// In Program.cs or Startup.csservices.AddSingleton<INotificationHandler<OrderCreated>,MyNotificationHandler>();// Custom lifetimeservices.AddScoped<INotificationHandler<CustomerUpdated>,CustomerUpdatedHandler>();// Custom lifetime// This should not duplicate the manually registered handlersservices.AddMediator(options =>{options.DefaultHandlerLifetime=ServiceLifetime.Transient;// Default for auto-registered handlers});
Implementation Logic (Based on Generated Code Analysis)
Looking at the actual generated code, the current registration pattern for notification handlers is:
// 1. Register the concrete handler implementationservices.TryAdd(newServiceDescriptor(typeof(MyNotificationHandler),typeof(MyNotificationHandler),ServiceLifetime.Singleton));// 2. Register the interface using a factory that resolves the concrete typeservices.Add(newServiceDescriptor(typeof(INotificationHandler<MyNotification>),
sp =>sp.GetRequiredService<MyNotificationHandler>(),ServiceLifetime.Singleton));
The proposed smart registration logic would need to:
/// <summary>/// Enhanced registration method that respects existing handler registrations/// while maintaining factory pattern compatibility/// </summary>publicstaticIServiceCollectionAddMediator(thisIServiceCollectionservices,Action<MediatorOptions>configure=null){varoptions=newMediatorOptions();configure?.Invoke(options);// Register core Mediator services first...RegisterMediatorCore(services);// Build cache of existing registrations for efficient lookupvarexistingRegistrations=BuildRegistrationCache(services);foreach(varhandlerInfoindiscoveredHandlers){varregistrationKey=newServiceRegistrationKey(handlerInfo.ConcreteType,handlerInfo.ConcreteType);if(!existingRegistrations.TryGetValue(registrationKey,outvarexistingDescriptor)){// Register concrete handler with default lifetime if not already presentservices.TryAdd(newServiceDescriptor(handlerInfo.ConcreteType,handlerInfo.ConcreteType,options.DefaultHandlerLifetime));existingDescriptor=newServiceDescriptor(handlerInfo.ConcreteType,handlerInfo.ConcreteType,options.DefaultHandlerLifetime);}// If already registered, respect the existing registration and its lifetime// Always register the interface mapping using factory pattern// This overwrites any direct interface registrations (which wouldn't work anyway)services.Add(newServiceDescriptor(handlerInfo.InterfaceType,
sp =>sp.GetRequiredService(handlerInfo.ConcreteType),existingDescriptor.Lifetime));}returnservices;}/// <summary>/// Builds a dictionary cache of existing service registrations for efficient lookup/// </summary>/// <param name="services">The service collection to cache</param>/// <returns>Dictionary with ServiceRegistrationKey as key and ServiceDescriptor as value</returns>privatestaticDictionary<ServiceRegistrationKey,ServiceDescriptor>BuildRegistrationCache(IServiceCollectionservices){varcache=newDictionary<ServiceRegistrationKey,ServiceDescriptor>();foreach(varserviceinservices){if(service.ImplementationType!=null){varkey=newServiceRegistrationKey(service.ServiceType,service.ImplementationType);cache[key]=service;}}returncache;}/// <summary>/// Composite key for service registration lookup combining ServiceType and ImplementationType/// </summary>privatereadonlystructServiceRegistrationKey:IEquatable<ServiceRegistrationKey>{publicTypeServiceType{get;}publicTypeImplementationType{get;}publicServiceRegistrationKey(TypeserviceType,TypeimplementationType){ServiceType=serviceType;ImplementationType=implementationType;}publicboolEquals(ServiceRegistrationKeyother)=>ServiceType==other.ServiceType&&ImplementationType==other.ImplementationType;publicoverrideboolEquals(objectobj)=>objisServiceRegistrationKeyother&&Equals(other);publicoverrideintGetHashCode()=>HashCode.Combine(ServiceType,ImplementationType);}
Developer Control: Allows explicit lifetime management for specific handlers
Backward Compatibility: Existing code continues to work unchanged
Clean Registration: No duplicate services in the DI container
Performance Optimized: Uses dictionary caching for efficient registration lookups
Disadvantages
Performance: since we need to create a dictionary and loop through all handlers, the startup performance would be worse of course. But maybe this feature could be configured using a new "AddMediator" option. (i.e. bool AllowExplicitHandlerRegistrations If that would be false, the roslyn analyzer that provides a build error if you'd register handlers yourself would again come in handy ;-)
Questions
Is this approach technically feasible with the current architecture?
Would you prefer a different detection mechanism for existing registrations?
Should there be any configuration options to control this behavior?
This enhancement would significantly improve the developer experience when working with custom handler lifetimes and DI container management.
Allow Custom Registration of INotificationHandler without Duplicate Registrations
Problem Description
Currently, when using Mediator, developers cannot register their
INotificationHandler<T>implementations manually without causing duplicate service registrations. If you register a handler yourself and then callservices.AddMediator(...), the handler gets registered twice in the DI container.Additionally, calling
services.AddMediator()first and then attempting to manually register handlers doesn't work either, because Mediator uses factory functions (GetRequiredService<T>()) rather than direct type registrations for notification handlers.Current Behavior
Proposed Solutions
I see two potential approaches to resolve this issue:
Option 1: Roslyn Analyzer (Less Preferred)
Create a Roslyn analyzer that emits compilation errors when developers attempt to manually register
INotificationHandler<T>implementations.Option 2: Smart Registration Check (Preferred)
Modify the Mediator registration logic to check for existing handler registrations before adding new ones.
Preferred Implementation
My preferred solution would implement the following behavior:
services.AddMediator(...):Code Examples
Desired Usage Pattern
Implementation Logic (Based on Generated Code Analysis)
Looking at the actual generated code, the current registration pattern for notification handlers is:
The proposed smart registration logic would need to:
Benefits
Disadvantages
bool AllowExplicitHandlerRegistrationsIf that would be false, the roslyn analyzer that provides a build error if you'd register handlers yourself would again come in handy ;-)Questions
This enhancement would significantly improve the developer experience when working with custom handler lifetimes and DI container management.