From 84812262388cf0a195b15a72ea6f1a42b9642b7c Mon Sep 17 00:00:00 2001 From: eneshoxha Date: Tue, 24 Feb 2026 14:35:17 +0100 Subject: [PATCH] v3/feature/217: Remove Scrutor Dependency / Offer Manual Registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove Scrutor; add custom assembly scanning for DI Replaced Scrutor-based assembly scanning and registration with a custom ScanAndRegister method using reflection. Updated all handler and processor registrations to use the new approach. Removed Scrutor package reference and updated comments to reflect the change. The codebase no longer depends on Scrutor for DI scanning. ServiceCollectionExtensions.cs — Replaced all 8 services.Scan(...) Scrutor calls with a new ScanAndRegister private helper method that uses reflection (GetExportedTypes/GetTypes) to discover and register handlers. Updated usings: added System.Reflection, removed unused System.Data. Cortex.Mediator.csproj — Removed the Scrutor 6.0.1 PackageReference. HandlerLifetimeTests.cs — Updated comment from "Scrutor" to "assembly scanning". Verification: All 226 mediator tests pass, and Scrutor no longer appears in the dependency list. --- src/Cortex.Mediator/Cortex.Mediator.csproj | 1 - .../ServiceCollectionExtensions.cs | 101 +++++++++--------- .../Mediator/Tests/HandlerLifetimeTests.cs | 2 +- 3 files changed, 50 insertions(+), 54 deletions(-) diff --git a/src/Cortex.Mediator/Cortex.Mediator.csproj b/src/Cortex.Mediator/Cortex.Mediator.csproj index b9e165b..e0e6275 100644 --- a/src/Cortex.Mediator/Cortex.Mediator.csproj +++ b/src/Cortex.Mediator/Cortex.Mediator.csproj @@ -67,6 +67,5 @@ - diff --git a/src/Cortex.Mediator/DependencyInjection/ServiceCollectionExtensions.cs b/src/Cortex.Mediator/DependencyInjection/ServiceCollectionExtensions.cs index 4ebde1c..60e0c06 100644 --- a/src/Cortex.Mediator/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Cortex.Mediator/DependencyInjection/ServiceCollectionExtensions.cs @@ -7,8 +7,8 @@ using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; -using System.Data; using System.Linq; +using System.Reflection; namespace Cortex.Mediator.DependencyInjection { @@ -43,43 +43,17 @@ private static void RegisterHandlers( var assemblies = assemblyMarkerTypes.Select(t => t.Assembly).ToArray(); var lifetime = options.HandlerLifetime; - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes - .AssignableTo(typeof(ICommandHandler<,>)), options.OnlyPublicClasses) - .AsImplementedInterfaces() - .WithLifetime(lifetime)); - + ScanAndRegister(services, assemblies, typeof(ICommandHandler<,>), options.OnlyPublicClasses, lifetime); // feature #141 - Register void command handlers - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes - .AssignableTo(typeof(ICommandHandler<>)), options.OnlyPublicClasses) - .AsImplementedInterfaces() - .WithLifetime(lifetime)); - - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes - .AssignableTo(typeof(IQueryHandler<,>)), options.OnlyPublicClasses) - .AsImplementedInterfaces() - .WithLifetime(lifetime)); - - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes - .AssignableTo(typeof(INotificationHandler<>)), options.OnlyPublicClasses) - .AsImplementedInterfaces() - .WithLifetime(lifetime)); + ScanAndRegister(services, assemblies, typeof(ICommandHandler<>), options.OnlyPublicClasses, lifetime); + + ScanAndRegister(services, assemblies, typeof(IQueryHandler<,>), options.OnlyPublicClasses, lifetime); + + ScanAndRegister(services, assemblies, typeof(INotificationHandler<>), options.OnlyPublicClasses, lifetime); // Register streaming query handlers - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes - .AssignableTo(typeof(IStreamQueryHandler<,>)), options.OnlyPublicClasses) - .AsImplementedInterfaces() - .WithLifetime(lifetime)); + ScanAndRegister(services, assemblies, typeof(IStreamQueryHandler<,>), options.OnlyPublicClasses, lifetime); } private static void RegisterProcessors( @@ -90,28 +64,51 @@ private static void RegisterProcessors( var assemblies = assemblyMarkerTypes.Select(t => t.Assembly).ToArray(); // Register pre-processors - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes - .AssignableTo(typeof(IRequestPreProcessor<>)), options.OnlyPublicClasses) - .AsImplementedInterfaces() - .WithTransientLifetime()); + ScanAndRegister(services, assemblies, typeof(IRequestPreProcessor<>), options.OnlyPublicClasses, ServiceLifetime.Transient); // Register post-processors with response - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes - .AssignableTo(typeof(IRequestPostProcessor<,>)), options.OnlyPublicClasses) - .AsImplementedInterfaces() - .WithTransientLifetime()); + ScanAndRegister(services, assemblies, typeof(IRequestPostProcessor<,>), options.OnlyPublicClasses, ServiceLifetime.Transient); // Register post-processors without response (for void commands) - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes - .AssignableTo(typeof(IRequestPostProcessor<>)), options.OnlyPublicClasses) - .AsImplementedInterfaces() - .WithTransientLifetime()); + ScanAndRegister(services, assemblies, typeof(IRequestPostProcessor<>), options.OnlyPublicClasses, ServiceLifetime.Transient); + } + + private static void ScanAndRegister( + IServiceCollection services, + IEnumerable assemblies, + Type openGenericInterface, + bool onlyPublicClasses, + ServiceLifetime lifetime) + { + foreach (var assembly in assemblies) + { + Type[] types; + try + { + types = onlyPublicClasses + ? assembly.GetExportedTypes() + : assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + types = ex.Types.Where(t => t != null).ToArray(); + } + + foreach (var type in types) + { + if (type.IsInterface || type.IsAbstract || type.IsGenericTypeDefinition) + continue; + + var matchingInterfaces = type.GetInterfaces() + .Where(i => i.IsGenericType && + i.GetGenericTypeDefinition() == openGenericInterface); + + foreach (var serviceType in matchingInterfaces) + { + services.Add(new ServiceDescriptor(serviceType, type, lifetime)); + } + } + } } private static void RegisterPipelineBehaviors(IServiceCollection services, MediatorOptions options) diff --git a/src/Cortex.Tests/Mediator/Tests/HandlerLifetimeTests.cs b/src/Cortex.Tests/Mediator/Tests/HandlerLifetimeTests.cs index 947f8c2..47eae2d 100644 --- a/src/Cortex.Tests/Mediator/Tests/HandlerLifetimeTests.cs +++ b/src/Cortex.Tests/Mediator/Tests/HandlerLifetimeTests.cs @@ -10,7 +10,7 @@ namespace Cortex.Tests.Mediator.Tests { #region Test Types for Handler Lifetime Tests - // These types live in this assembly so Scrutor can discover them via the marker type. + // These types live in this assembly so assembly scanning can discover them via the marker type. public class LifetimeTestCommand : ICommand {