From 811ce54414612f13eac19e6eef3e0585b5ffb191 Mon Sep 17 00:00:00 2001 From: zach painter Date: Fri, 12 Jul 2024 17:18:58 -0700 Subject: [PATCH] Added Opt Out functionality - created a feature flag that enables a user to opt out of registering generic handlers they have defined. - created a test that ensures that, when the flag is set, the generic handlers defined are not registered. --- .../MediatrServiceConfiguration.cs | 911 +++++++++--------- src/MediatR/Registration/ServiceRegistrar.cs | 1 + .../GenericRequestHandlerTests.cs | 30 + .../BaseGenericRequestHandlerTests.cs | 6 + 4 files changed, 495 insertions(+), 453 deletions(-) diff --git a/src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs b/src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs index 890ee77b..5dae21d0 100644 --- a/src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs +++ b/src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs @@ -1,72 +1,72 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using MediatR; -using MediatR.NotificationPublishers; -using MediatR.Pipeline; -using MediatR.Registration; - -namespace Microsoft.Extensions.DependencyInjection; - -public class MediatRServiceConfiguration -{ - /// - /// Optional filter for types to register. Default value is a function returning true. - /// - public Func TypeEvaluator { get; set; } = t => true; - - /// - /// Mediator implementation type to register. Default is - /// - public Type MediatorImplementationType { get; set; } = typeof(Mediator); - - /// - /// Strategy for publishing notifications. Defaults to - /// - public INotificationPublisher NotificationPublisher { get; set; } = new ForeachAwaitPublisher(); - - /// - /// Type of notification publisher strategy to register. If set, overrides - /// - public Type? NotificationPublisherType { get; set; } - - /// - /// Service lifetime to register services under. Default value is - /// - public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient; - - /// - /// Request exception action processor strategy. Default value is - /// - public RequestExceptionActionProcessorStrategy RequestExceptionActionProcessorStrategy { get; set; } - = RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions; - - internal List AssembliesToRegister { get; } = new(); - - /// - /// List of behaviors to register in specific order - /// - public List BehaviorsToRegister { get; } = new(); - - /// - /// List of stream behaviors to register in specific order - /// - public List StreamBehaviorsToRegister { get; } = new(); - - /// - /// List of request pre processors to register in specific order - /// - public List RequestPreProcessorsToRegister { get; } = new(); - - /// - /// List of request post processors to register in specific order - /// - public List RequestPostProcessorsToRegister { get; } = new(); - - /// - /// Automatically register processors during assembly scanning - /// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using MediatR; +using MediatR.NotificationPublishers; +using MediatR.Pipeline; +using MediatR.Registration; + +namespace Microsoft.Extensions.DependencyInjection; + +public class MediatRServiceConfiguration +{ + /// + /// Optional filter for types to register. Default value is a function returning true. + /// + public Func TypeEvaluator { get; set; } = t => true; + + /// + /// Mediator implementation type to register. Default is + /// + public Type MediatorImplementationType { get; set; } = typeof(Mediator); + + /// + /// Strategy for publishing notifications. Defaults to + /// + public INotificationPublisher NotificationPublisher { get; set; } = new ForeachAwaitPublisher(); + + /// + /// Type of notification publisher strategy to register. If set, overrides + /// + public Type? NotificationPublisherType { get; set; } + + /// + /// Service lifetime to register services under. Default value is + /// + public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient; + + /// + /// Request exception action processor strategy. Default value is + /// + public RequestExceptionActionProcessorStrategy RequestExceptionActionProcessorStrategy { get; set; } + = RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions; + + internal List AssembliesToRegister { get; } = new(); + + /// + /// List of behaviors to register in specific order + /// + public List BehaviorsToRegister { get; } = new(); + + /// + /// List of stream behaviors to register in specific order + /// + public List StreamBehaviorsToRegister { get; } = new(); + + /// + /// List of request pre processors to register in specific order + /// + public List RequestPreProcessorsToRegister { get; } = new(); + + /// + /// List of request post processors to register in specific order + /// + public List RequestPostProcessorsToRegister { get; } = new(); + + /// + /// Automatically register processors during assembly scanning + /// public bool AutoRegisterRequestProcessors { get; set; } /// @@ -89,388 +89,393 @@ public class MediatRServiceConfiguration /// public int RegistrationTimeout { get; set; } = 15000; - /// - /// Register various handlers from assembly containing given type - /// - /// Type from assembly to scan - /// This - public MediatRServiceConfiguration RegisterServicesFromAssemblyContaining() - => RegisterServicesFromAssemblyContaining(typeof(T)); - - /// - /// Register various handlers from assembly containing given type - /// - /// Type from assembly to scan - /// This - public MediatRServiceConfiguration RegisterServicesFromAssemblyContaining(Type type) - => RegisterServicesFromAssembly(type.Assembly); - - /// - /// Register various handlers from assembly - /// - /// Assembly to scan - /// This - public MediatRServiceConfiguration RegisterServicesFromAssembly(Assembly assembly) - { - AssembliesToRegister.Add(assembly); - - return this; - } - - /// - /// Register various handlers from assemblies - /// - /// Assemblies to scan - /// This - public MediatRServiceConfiguration RegisterServicesFromAssemblies( - params Assembly[] assemblies) - { - AssembliesToRegister.AddRange(assemblies); - - return this; - } - - /// - /// Register a closed behavior type - /// - /// Closed behavior interface type - /// Closed behavior implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - => AddBehavior(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); - - /// - /// Register a closed behavior type against all implementations - /// - /// Closed behavior implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - return AddBehavior(typeof(TImplementationType), serviceLifetime); - } - - /// - /// Register a closed behavior type against all implementations - /// - /// Closed behavior implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddBehavior(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IPipelineBehavior<,>)).ToList(); - - if (implementedGenericInterfaces.Count == 0) - { - throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IPipelineBehavior<,>).FullName}"); - } - - foreach (var implementedBehaviorType in implementedGenericInterfaces) - { - BehaviorsToRegister.Add(new ServiceDescriptor(implementedBehaviorType, implementationType, serviceLifetime)); - } - - return this; - } - - /// - /// Register a closed behavior type - /// - /// Closed behavior interface type - /// Closed behavior implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddBehavior(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - BehaviorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); - - return this; - } - - /// - /// Registers an open behavior type against the open generic interface type - /// - /// An open generic behavior type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddOpenBehavior(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - if (!openBehaviorType.IsGenericType) - { - throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); - } - - var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); - var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IPipelineBehavior<,>))); - - if (implementedOpenBehaviorInterfaces.Count == 0) - { - throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IPipelineBehavior<,>).FullName}"); - } - - foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) - { - BehaviorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); - } - - return this; - } - - /// - /// Register a closed stream behavior type - /// - /// Closed stream behavior interface type - /// Closed stream behavior implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddStreamBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - => AddStreamBehavior(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); - - /// - /// Register a closed stream behavior type - /// - /// Closed stream behavior interface type - /// Closed stream behavior implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddStreamBehavior(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - StreamBehaviorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); - - return this; - } - - /// - /// Register a closed stream behavior type against all implementations - /// - /// Closed stream behavior implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddStreamBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - => AddStreamBehavior(typeof(TImplementationType), serviceLifetime); - - /// - /// Register a closed stream behavior type against all implementations - /// - /// Closed stream behavior implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddStreamBehavior(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IStreamPipelineBehavior<,>)).ToList(); - - if (implementedGenericInterfaces.Count == 0) - { - throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IStreamPipelineBehavior<,>).FullName}"); - } - - foreach (var implementedBehaviorType in implementedGenericInterfaces) - { - StreamBehaviorsToRegister.Add(new ServiceDescriptor(implementedBehaviorType, implementationType, serviceLifetime)); - } - - return this; - } - - /// - /// Registers an open stream behavior type against the open generic interface type - /// - /// An open generic stream behavior type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddOpenStreamBehavior(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - if (!openBehaviorType.IsGenericType) - { - throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); - } - - var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); - var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IStreamPipelineBehavior<,>))); - - if (implementedOpenBehaviorInterfaces.Count == 0) - { - throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IStreamPipelineBehavior<,>).FullName}"); - } - - foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) - { - StreamBehaviorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); - } - - return this; - } - - /// - /// Register a closed request pre processor type - /// - /// Closed request pre processor interface type - /// Closed request pre processor implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddRequestPreProcessor(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - => AddRequestPreProcessor(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); - - /// - /// Register a closed request pre processor type - /// - /// Closed request pre processor interface type - /// Closed request pre processor implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddRequestPreProcessor(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - RequestPreProcessorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); - - return this; - } - - /// - /// Register a closed request pre processor type against all implementations - /// - /// Closed request pre processor implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddRequestPreProcessor( - ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - => AddRequestPreProcessor(typeof(TImplementationType), serviceLifetime); - - /// - /// Register a closed request pre processor type against all implementations - /// - /// Closed request pre processor implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddRequestPreProcessor(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IRequestPreProcessor<>)).ToList(); - - if (implementedGenericInterfaces.Count == 0) - { - throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IRequestPreProcessor<>).FullName}"); - } - - foreach (var implementedPreProcessorType in implementedGenericInterfaces) - { - RequestPreProcessorsToRegister.Add(new ServiceDescriptor(implementedPreProcessorType, implementationType, serviceLifetime)); - } - - return this; - } - - /// - /// Registers an open request pre processor type against the open generic interface type - /// - /// An open generic request pre processor type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddOpenRequestPreProcessor(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - if (!openBehaviorType.IsGenericType) - { - throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); - } - - var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); - var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IRequestPreProcessor<>))); - - if (implementedOpenBehaviorInterfaces.Count == 0) - { - throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IRequestPreProcessor<>).FullName}"); - } - - foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) - { - RequestPreProcessorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); - } - - return this; - } - - /// - /// Register a closed request post processor type - /// - /// Closed request post processor interface type - /// Closed request post processor implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddRequestPostProcessor(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - => AddRequestPostProcessor(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); - - /// - /// Register a closed request post processor type - /// - /// Closed request post processor interface type - /// Closed request post processor implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddRequestPostProcessor(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - RequestPostProcessorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); - - return this; - } - - /// - /// Register a closed request post processor type against all implementations - /// - /// Closed request post processor implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddRequestPostProcessor(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - => AddRequestPostProcessor(typeof(TImplementationType), serviceLifetime); - - /// - /// Register a closed request post processor type against all implementations - /// - /// Closed request post processor implementation type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddRequestPostProcessor(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IRequestPostProcessor<,>)).ToList(); - - if (implementedGenericInterfaces.Count == 0) - { - throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IRequestPostProcessor<,>).FullName}"); - } - - foreach (var implementedPostProcessorType in implementedGenericInterfaces) - { - RequestPostProcessorsToRegister.Add(new ServiceDescriptor(implementedPostProcessorType, implementationType, serviceLifetime)); - } - return this; - } - - /// - /// Registers an open request post processor type against the open generic interface type - /// - /// An open generic request post processor type - /// Optional service lifetime, defaults to . - /// This - public MediatRServiceConfiguration AddOpenRequestPostProcessor(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - { - if (!openBehaviorType.IsGenericType) - { - throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); - } - - var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); - var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IRequestPostProcessor<,>))); - - if (implementedOpenBehaviorInterfaces.Count == 0) - { - throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IRequestPostProcessor<,>).FullName}"); - } - - foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) - { - RequestPostProcessorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); - } - - return this; - } - - + /// + /// Flag that controlls whether MediatR will attempt to register handlers that containg generic type parameters. + /// + public bool RegisterGenericHandlers { get; set; } = true; + + /// + /// Register various handlers from assembly containing given type + /// + /// Type from assembly to scan + /// This + public MediatRServiceConfiguration RegisterServicesFromAssemblyContaining() + => RegisterServicesFromAssemblyContaining(typeof(T)); + + /// + /// Register various handlers from assembly containing given type + /// + /// Type from assembly to scan + /// This + public MediatRServiceConfiguration RegisterServicesFromAssemblyContaining(Type type) + => RegisterServicesFromAssembly(type.Assembly); + + /// + /// Register various handlers from assembly + /// + /// Assembly to scan + /// This + public MediatRServiceConfiguration RegisterServicesFromAssembly(Assembly assembly) + { + AssembliesToRegister.Add(assembly); + + return this; + } + + /// + /// Register various handlers from assemblies + /// + /// Assemblies to scan + /// This + public MediatRServiceConfiguration RegisterServicesFromAssemblies( + params Assembly[] assemblies) + { + AssembliesToRegister.AddRange(assemblies); + + return this; + } + + /// + /// Register a closed behavior type + /// + /// Closed behavior interface type + /// Closed behavior implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddBehavior(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); + + /// + /// Register a closed behavior type against all implementations + /// + /// Closed behavior implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + return AddBehavior(typeof(TImplementationType), serviceLifetime); + } + + /// + /// Register a closed behavior type against all implementations + /// + /// Closed behavior implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddBehavior(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IPipelineBehavior<,>)).ToList(); + + if (implementedGenericInterfaces.Count == 0) + { + throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IPipelineBehavior<,>).FullName}"); + } + + foreach (var implementedBehaviorType in implementedGenericInterfaces) + { + BehaviorsToRegister.Add(new ServiceDescriptor(implementedBehaviorType, implementationType, serviceLifetime)); + } + + return this; + } + + /// + /// Register a closed behavior type + /// + /// Closed behavior interface type + /// Closed behavior implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddBehavior(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + BehaviorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); + + return this; + } + + /// + /// Registers an open behavior type against the open generic interface type + /// + /// An open generic behavior type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddOpenBehavior(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + if (!openBehaviorType.IsGenericType) + { + throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); + } + + var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); + var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IPipelineBehavior<,>))); + + if (implementedOpenBehaviorInterfaces.Count == 0) + { + throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IPipelineBehavior<,>).FullName}"); + } + + foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) + { + BehaviorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); + } + + return this; + } + + /// + /// Register a closed stream behavior type + /// + /// Closed stream behavior interface type + /// Closed stream behavior implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddStreamBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddStreamBehavior(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); + + /// + /// Register a closed stream behavior type + /// + /// Closed stream behavior interface type + /// Closed stream behavior implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddStreamBehavior(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + StreamBehaviorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); + + return this; + } + + /// + /// Register a closed stream behavior type against all implementations + /// + /// Closed stream behavior implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddStreamBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddStreamBehavior(typeof(TImplementationType), serviceLifetime); + + /// + /// Register a closed stream behavior type against all implementations + /// + /// Closed stream behavior implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddStreamBehavior(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IStreamPipelineBehavior<,>)).ToList(); + + if (implementedGenericInterfaces.Count == 0) + { + throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IStreamPipelineBehavior<,>).FullName}"); + } + + foreach (var implementedBehaviorType in implementedGenericInterfaces) + { + StreamBehaviorsToRegister.Add(new ServiceDescriptor(implementedBehaviorType, implementationType, serviceLifetime)); + } + + return this; + } + + /// + /// Registers an open stream behavior type against the open generic interface type + /// + /// An open generic stream behavior type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddOpenStreamBehavior(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + if (!openBehaviorType.IsGenericType) + { + throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); + } + + var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); + var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IStreamPipelineBehavior<,>))); + + if (implementedOpenBehaviorInterfaces.Count == 0) + { + throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IStreamPipelineBehavior<,>).FullName}"); + } + + foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) + { + StreamBehaviorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); + } + + return this; + } + + /// + /// Register a closed request pre processor type + /// + /// Closed request pre processor interface type + /// Closed request pre processor implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddRequestPreProcessor(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddRequestPreProcessor(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); + + /// + /// Register a closed request pre processor type + /// + /// Closed request pre processor interface type + /// Closed request pre processor implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddRequestPreProcessor(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + RequestPreProcessorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); + + return this; + } + + /// + /// Register a closed request pre processor type against all implementations + /// + /// Closed request pre processor implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddRequestPreProcessor( + ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddRequestPreProcessor(typeof(TImplementationType), serviceLifetime); + + /// + /// Register a closed request pre processor type against all implementations + /// + /// Closed request pre processor implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddRequestPreProcessor(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IRequestPreProcessor<>)).ToList(); + + if (implementedGenericInterfaces.Count == 0) + { + throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IRequestPreProcessor<>).FullName}"); + } + + foreach (var implementedPreProcessorType in implementedGenericInterfaces) + { + RequestPreProcessorsToRegister.Add(new ServiceDescriptor(implementedPreProcessorType, implementationType, serviceLifetime)); + } + + return this; + } + + /// + /// Registers an open request pre processor type against the open generic interface type + /// + /// An open generic request pre processor type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddOpenRequestPreProcessor(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + if (!openBehaviorType.IsGenericType) + { + throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); + } + + var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); + var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IRequestPreProcessor<>))); + + if (implementedOpenBehaviorInterfaces.Count == 0) + { + throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IRequestPreProcessor<>).FullName}"); + } + + foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) + { + RequestPreProcessorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); + } + + return this; + } + + /// + /// Register a closed request post processor type + /// + /// Closed request post processor interface type + /// Closed request post processor implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddRequestPostProcessor(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddRequestPostProcessor(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); + + /// + /// Register a closed request post processor type + /// + /// Closed request post processor interface type + /// Closed request post processor implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddRequestPostProcessor(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + RequestPostProcessorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); + + return this; + } + + /// + /// Register a closed request post processor type against all implementations + /// + /// Closed request post processor implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddRequestPostProcessor(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + => AddRequestPostProcessor(typeof(TImplementationType), serviceLifetime); + + /// + /// Register a closed request post processor type against all implementations + /// + /// Closed request post processor implementation type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddRequestPostProcessor(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IRequestPostProcessor<,>)).ToList(); + + if (implementedGenericInterfaces.Count == 0) + { + throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IRequestPostProcessor<,>).FullName}"); + } + + foreach (var implementedPostProcessorType in implementedGenericInterfaces) + { + RequestPostProcessorsToRegister.Add(new ServiceDescriptor(implementedPostProcessorType, implementationType, serviceLifetime)); + } + return this; + } + + /// + /// Registers an open request post processor type against the open generic interface type + /// + /// An open generic request post processor type + /// Optional service lifetime, defaults to . + /// This + public MediatRServiceConfiguration AddOpenRequestPostProcessor(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + { + if (!openBehaviorType.IsGenericType) + { + throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); + } + + var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); + var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IRequestPostProcessor<,>))); + + if (implementedOpenBehaviorInterfaces.Count == 0) + { + throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IRequestPostProcessor<,>).FullName}"); + } + + foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) + { + RequestPostProcessorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); + } + + return this; + } + + } \ No newline at end of file diff --git a/src/MediatR/Registration/ServiceRegistrar.cs b/src/MediatR/Registration/ServiceRegistrar.cs index 6aa23907..c4cdd008 100644 --- a/src/MediatR/Registration/ServiceRegistrar.cs +++ b/src/MediatR/Registration/ServiceRegistrar.cs @@ -103,6 +103,7 @@ private static void ConnectImplementationsToTypesClosing(Type openRequestInterfa var types = assembliesToScan .SelectMany(a => a.DefinedTypes) + .Where(t => !t.ContainsGenericParameters || configuration.RegisterGenericHandlers) .Where(t => t.IsConcrete() && t.FindInterfacesThatClose(openRequestInterface).Any()) .Where(configuration.TypeEvaluator) .ToList(); diff --git a/test/MediatR.Tests/GenericRequestHandlerTests.cs b/test/MediatR.Tests/GenericRequestHandlerTests.cs index 8efa11d1..3daa17e6 100644 --- a/test/MediatR.Tests/GenericRequestHandlerTests.cs +++ b/test/MediatR.Tests/GenericRequestHandlerTests.cs @@ -186,5 +186,35 @@ public void ShouldThrowExceptionWhenTimeoutOccurs() }) .Message.ShouldBe("The generic handler registration process timed out."); } + + [Fact] + public void ShouldNotRegisterGenericHandlersWhenOptingOut() + { + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(new Logger()); + + var assembly = GenerateOptOutAssembly(); + services.AddMediatR(cfg => + { + //opt out flag set + cfg.RegisterGenericHandlers = false; + cfg.RegisterServicesFromAssembly(assembly); + }); + + var provider = services.BuildServiceProvider(); + var testClasses = Enumerable.Range(1, 2) + .Select(i => assembly.GetType($"TestClass{i}")!) + .ToArray(); + var requestType = assembly.GetType("OptOutRequest")!; + var combinations = GenerateCombinations(testClasses, 2); + + var concreteRequestType = requestType.MakeGenericType(combinations.First()); + var requestHandlerInterface = typeof(IRequestHandler<>).MakeGenericType(concreteRequestType); + + var handler = provider.GetService(requestHandlerInterface); + handler.ShouldBeNull($"Handler for {concreteRequestType} should be null"); + + + } } } diff --git a/test/MediatR.Tests/MicrosoftExtensionsDI/BaseGenericRequestHandlerTests.cs b/test/MediatR.Tests/MicrosoftExtensionsDI/BaseGenericRequestHandlerTests.cs index afb8799e..b87fc02b 100644 --- a/test/MediatR.Tests/MicrosoftExtensionsDI/BaseGenericRequestHandlerTests.cs +++ b/test/MediatR.Tests/MicrosoftExtensionsDI/BaseGenericRequestHandlerTests.cs @@ -26,6 +26,12 @@ protected static Assembly GenerateGenericTypeParametersExceedsMaximumAssembly() protected static Assembly GenerateTimeoutOccursAssembly() => CreateAssemblyModuleBuilder("TimeOutOccursAssembly", 400, 3, CreateHandlerForTimeoutOccursTest); + protected static Assembly GenerateOptOutAssembly() => + CreateAssemblyModuleBuilder("OptOutAssembly", 2, 2, CreateHandlerForOptOutTest); + + protected static void CreateHandlerForOptOutTest(ModuleBuilder moduleBuilder) => + CreateRequestHandler(moduleBuilder, "OptOutRequest", 2); + protected static void CreateHandlerForMissingConstraintsTest(ModuleBuilder moduleBuilder) => CreateRequestHandler(moduleBuilder, "MissingConstraintsRequest", 3, 0, false);