Skip to content

Commit 761256f

Browse files
committed
Optimize Generic Handler Registration in MediatR
Summary This PR introduces an optimized mechanism for registering generic handlers in MediatR. The current implementation scans all assemblies passed to MediatR during startup to find every possible concrete implementation and service type that satisfies all generic handler constraints. This PR modifies the behavior to scan and register generic handler services on-demand, triggered only when a specific service is requested. This feature remains opt-in and can be enabled by setting the RegisterGenericHandlers configuration flag to true. Changes Made Optimized Generic Handler Registration: The registration process now scans assemblies only when a specific service is requested, rather than eagerly scanning all possible types during startup. Once a service is resolved, the registration is cached for future requests. Dynamic Service Provider Integration: Introduced dynamic resolution and caching for generic handlers, minimizing the startup overhead. Backward Compatibility: The feature remains optional and is controlled via the RegisterGenericHandlers flag in the MediatR configuration. Code Refactor: Extracted and modularized key logic for resolving generic handler types. Improved readability and maintainability by reducing redundant logic and clarifying workflows.
1 parent db235f8 commit 761256f

File tree

10 files changed

+376
-383
lines changed

10 files changed

+376
-383
lines changed

src/MediatR/MediatR.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
</PackageReference>
3535
<PackageReference Include="MediatR.Contracts" Version="[2.0.1, 3.0.0)" />
3636
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
37+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
3738
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
3839
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
3940
<PackageReference Include="MinVer" Version="4.3.0" PrivateAssets="All" />

src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,27 +69,7 @@ public class MediatRServiceConfiguration
6969
/// Automatically register processors during assembly scanning
7070
/// </summary>
7171
public bool AutoRegisterRequestProcessors { get; set; }
72-
73-
/// <summary>
74-
/// Configure the maximum number of type parameters that a generic request handler can have. To Disable this constraint, set the value to 0.
75-
/// </summary>
76-
public int MaxGenericTypeParameters { get; set; } = 10;
77-
78-
/// <summary>
79-
/// Configure the maximum number of types that can close a generic request type parameter constraint. To Disable this constraint, set the value to 0.
80-
/// </summary>
81-
public int MaxTypesClosing { get; set; } = 100;
82-
83-
/// <summary>
84-
/// Configure the Maximum Amount of Generic RequestHandler Types MediatR will try to register. To Disable this constraint, set the value to 0.
85-
/// </summary>
86-
public int MaxGenericTypeRegistrations { get; set; } = 125000;
87-
88-
/// <summary>
89-
/// Configure the Timeout in Milliseconds that the GenericHandler Registration Process will exit with error. To Disable this constraint, set the value to 0.
90-
/// </summary>
91-
public int RegistrationTimeout { get; set; } = 15000;
92-
72+
9373
/// <summary>
9474
/// Flag that controlls whether MediatR will attempt to register handlers that containg generic type parameters.
9575
/// </summary>

src/MediatR/MicrosoftExtensionsDI/ServiceCollectionExtensions.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,14 @@ public static IServiceCollection AddMediatR(this IServiceCollection services,
4747
throw new ArgumentException("No assemblies found to scan. Supply at least one assembly to scan for handlers.");
4848
}
4949

50-
ServiceRegistrar.SetGenericRequestHandlerRegistrationLimitations(configuration);
50+
ServiceRegistrar.AddMediatRClasses(services, configuration);
5151

52-
ServiceRegistrar.AddMediatRClassesWithTimeout(services, configuration);
53-
54-
ServiceRegistrar.AddRequiredServices(services, configuration);
52+
ServiceRegistrar.AddRequiredServices(services, configuration);
53+
54+
if (configuration.RegisterGenericHandlers)
55+
{
56+
ServiceRegistrar.AddDynamicServiceProvider(services, configuration);
57+
}
5558

5659
return services;
5760
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Text;
7+
8+
namespace MediatR.Registration
9+
{
10+
public class DynamicServiceProvider : IServiceProvider, IDisposable
11+
{
12+
private readonly IServiceProvider _rootProvider;
13+
private ServiceProvider _currentProvider;
14+
private readonly IServiceCollection _services;
15+
private readonly Type[] RequestHandlerTypes = new Type[] { typeof(IRequestHandler<>), typeof(IRequestHandler<,>) };
16+
17+
public DynamicServiceProvider(IServiceProvider rootProvider)
18+
{
19+
_rootProvider = rootProvider;
20+
_services = new ServiceCollection();
21+
_currentProvider = _services.BuildServiceProvider();
22+
}
23+
24+
public IEnumerable<ServiceDescriptor> GetAllServiceDescriptors()
25+
{
26+
return _services;
27+
}
28+
29+
public void AddService(Type serviceType, Type implementationType, ServiceLifetime lifetime = ServiceLifetime.Transient)
30+
{
31+
var constructor = implementationType.GetConstructors().OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
32+
if (constructor != null)
33+
{
34+
var parameters = constructor.GetParameters();
35+
36+
foreach (var parameter in parameters)
37+
{
38+
// Check if the dependency is already registered
39+
if (_currentProvider.GetService(parameter.ParameterType) != null)
40+
continue;
41+
42+
// Attempt to resolve from the root provider
43+
var dependency = _rootProvider.GetService(parameter.ParameterType);
44+
if (dependency != null)
45+
{
46+
// Dynamically register the dependency in the dynamic registry
47+
_services.Add(new ServiceDescriptor(parameter.ParameterType, _ => dependency, lifetime));
48+
RebuildProvider(); // Rebuild the internal provider to include the new service
49+
}
50+
else
51+
{
52+
throw new InvalidOperationException(
53+
$"Unable to resolve dependency {parameter.ParameterType.FullName} for {serviceType.FullName}");
54+
}
55+
}
56+
}
57+
_services.Add(new ServiceDescriptor(serviceType, implementationType, lifetime));
58+
RebuildProvider();
59+
}
60+
61+
public void AddService(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime = ServiceLifetime.Transient)
62+
{
63+
if (serviceType == null) throw new ArgumentNullException(nameof(serviceType));
64+
if (implementationFactory == null) throw new ArgumentNullException(nameof(implementationFactory));
65+
66+
// Add the service descriptor with the factory
67+
_services.Add(new ServiceDescriptor(serviceType, implementationFactory, lifetime));
68+
RebuildProvider();
69+
}
70+
71+
//public IServiceProvider RootProvider { get { return _rootProvider; } }
72+
public object? GetService(Type serviceType)
73+
{
74+
// Handle requests for IEnumerable<T>
75+
if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
76+
{
77+
// typeof(T) for IEnumerable<T>
78+
var elementType = serviceType.GenericTypeArguments[0];
79+
return CastToEnumerableOfType(GetServices(elementType), elementType);
80+
}
81+
82+
// Try resolving from the current provider
83+
var service = _currentProvider.GetService(serviceType);
84+
if (service != null) return service;
85+
86+
//fall back to root provider
87+
service = _rootProvider.GetService(serviceType);
88+
if(service != null) return service;
89+
90+
//if not found in current or root then try to find the implementation and register it.
91+
if (serviceType.IsGenericType)
92+
{
93+
var genericArguments = serviceType.GetGenericArguments();
94+
var hasResponseType = genericArguments.Length > 1;
95+
var openInterface = hasResponseType ? typeof(IRequestHandler<,>) : typeof(IRequestHandler<>);
96+
97+
if(openInterface != null)
98+
{
99+
var requestType = genericArguments[0];
100+
Type? responseType = hasResponseType ? genericArguments[1] : null;
101+
102+
var implementationType = FindOpenGenericHandlerType(requestType, responseType);
103+
if(implementationType == null)
104+
throw new InvalidOperationException($"No implementation found for {openInterface.FullName}");
105+
106+
AddService(serviceType, implementationType);
107+
}
108+
}
109+
110+
//find the newly registered service
111+
service = _currentProvider.GetService(serviceType);
112+
if (service != null) return service;
113+
114+
// Fallback to the root provider as a last resort
115+
return _rootProvider.GetService(serviceType);
116+
}
117+
118+
public IEnumerable<object> GetServices(Type serviceType)
119+
{
120+
// Collect services from the dynamic provider
121+
var dynamicServices = _services
122+
.Where(d => d.ServiceType == serviceType)
123+
.Select(d => _currentProvider.GetService(d.ServiceType))
124+
.Where(s => s != null);
125+
126+
// Collect services from the root provider
127+
var rootServices = _rootProvider
128+
.GetServices(serviceType)
129+
.Cast<object>();
130+
131+
// Combine results and remove duplicates
132+
return dynamicServices.Concat(rootServices).Distinct()!;
133+
}
134+
135+
private object CastToEnumerableOfType(IEnumerable<object> services, Type elementType)
136+
{
137+
var castMethod = typeof(Enumerable)
138+
.GetMethod(nameof(Enumerable.Cast))
139+
?.MakeGenericMethod(elementType);
140+
141+
var toListMethod = typeof(Enumerable)
142+
.GetMethod(nameof(Enumerable.ToList))
143+
?.MakeGenericMethod(elementType);
144+
145+
if (castMethod == null || toListMethod == null)
146+
throw new InvalidOperationException("Unable to cast services to the specified enumerable type.");
147+
148+
var castedServices = castMethod.Invoke(null, new object[] { services });
149+
return toListMethod.Invoke(null, new[] { castedServices })!;
150+
}
151+
152+
public Type? FindOpenGenericHandlerType(Type requestType, Type? responseType = null)
153+
{
154+
if (!requestType.IsGenericType)
155+
return null;
156+
157+
// Define the target generic handler type
158+
var openHandlerType = responseType == null ? typeof(IRequestHandler<>) : typeof(IRequestHandler<,>);
159+
var genericArguments = responseType == null ? new Type[] { requestType } : new Type[] { requestType, responseType };
160+
var closedHandlerType = openHandlerType.MakeGenericType(genericArguments);
161+
162+
// Get the current assembly
163+
var currentAssembly = Assembly.GetExecutingAssembly();
164+
165+
// Get assemblies that reference the current assembly
166+
var consumingAssemblies = AppDomain.CurrentDomain.GetAssemblies()
167+
.Where(assembly => assembly.GetReferencedAssemblies()
168+
.Any(reference => reference.FullName == currentAssembly.FullName));
169+
170+
// Search for matching types
171+
var types = consumingAssemblies.SelectMany(x => x.GetTypes())
172+
.Where(t => t.IsClass && !t.IsAbstract && t.IsGenericTypeDefinition)
173+
.ToList();
174+
175+
foreach (var type in types)
176+
{
177+
var interfaces = type.GetInterfaces();
178+
if (interfaces.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == openHandlerType))
179+
{
180+
// Check generic constraints
181+
var concreteHandlerGenericArgs = type.GetGenericArguments();
182+
var concreteHandlerConstraints = concreteHandlerGenericArgs.Select(x => x.GetGenericParameterConstraints());
183+
var concreteRequestTypeGenericArgs = requestType.GetGenericArguments();
184+
//var secondArgConstrants = genericArguments[1];
185+
186+
// Ensure the constraints are compatible
187+
if (concreteHandlerConstraints
188+
.Select((list, i) => new { List = list, Index = i })
189+
.All(x => x.List.All(c => c.IsAssignableFrom(concreteRequestTypeGenericArgs[x.Index]))))
190+
{
191+
return type.MakeGenericType(concreteRequestTypeGenericArgs);
192+
}
193+
}
194+
}
195+
196+
return null; // No matching type found
197+
}
198+
199+
private void RebuildProvider()
200+
{
201+
_currentProvider.Dispose();
202+
_currentProvider = _services.BuildServiceProvider();
203+
}
204+
205+
public void Dispose()
206+
{
207+
_currentProvider.Dispose();
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)