From 46fc2a33a0c7d60d76825bbb73091b2d48405d24 Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Mon, 3 Jun 2024 06:16:43 -0500 Subject: [PATCH] Ability to configure the ServiceLifetime when doing service discovery using Connect***** scanning. Bumps to 1.7 --- .../Conventions/type_scanning_end_to_end.cs | 29 +++++++++++++++++++ src/JasperFx.Core/IoC/AssemblyScanner.cs | 8 ++++- src/JasperFx.Core/IoC/FindAllTypesFilter.cs | 2 +- .../IoC/GenericConnectionScanner.cs | 12 ++++---- src/JasperFx.Core/IoC/IAssemblyScanner.cs | 6 ++++ src/JasperFx.Core/JasperFx.Core.csproj | 2 +- .../Reflection/ReflectionExtensions.cs | 11 +++++++ 7 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/JasperFx.Core.Tests/IoC/Conventions/type_scanning_end_to_end.cs b/src/JasperFx.Core.Tests/IoC/Conventions/type_scanning_end_to_end.cs index e0169d0..8c0277c 100644 --- a/src/JasperFx.Core.Tests/IoC/Conventions/type_scanning_end_to_end.cs +++ b/src/JasperFx.Core.Tests/IoC/Conventions/type_scanning_end_to_end.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using JasperFx.Core.IoC; +using JasperFx.Core.Reflection; using Microsoft.Extensions.DependencyInjection; using Shouldly; using Widgets1; @@ -12,6 +13,7 @@ public class type_scanning_end_to_end public void can_configure_plugin_families_via_dsl() { var registry = new ServiceCollection(); + registry.AddSingleton(); registry.Scan(x => { x.TheCallingAssembly(); @@ -31,6 +33,7 @@ public void can_configure_plugin_families_via_dsl() public void can_find_the_closed_finders() { var x = new ServiceCollection(); + x.AddSingleton(); x.Scan(o => { o.TheCallingAssembly(); @@ -231,12 +234,38 @@ public void use_default_scanning_with_if_newer_and_a_match() container.GetServices().Single().ShouldBeOfType(); } + [Fact] + public void connect_implementations_with_lifetime_rule() + { + var services = new ServiceCollection(); + services.AddScoped(); + services.Scan(x => + { + x.AssemblyContainingType(); + x.ConnectImplementationsToTypesClosing(typeof(IFinder<>), type => type.HasConstructorsWithArguments() + ? ServiceLifetime.Scoped + : ServiceLifetime.Singleton); + }); + + var container = services.BuildServiceProvider(); + + var scope1 = container.CreateScope(); + + var scope2 = container.CreateScope(); + + scope1.ServiceProvider.GetServices>().ShouldBeSameAs(scope2.ServiceProvider.GetServices>()); + scope1.ServiceProvider.GetServices>().ShouldNotBeSameAs(scope2.ServiceProvider.GetServices>()); + } + public interface IFinder { } public class StringFinder : IFinder { + public StringFinder(IRanger ranger) + { + } } public class IntFinder : IFinder diff --git a/src/JasperFx.Core/IoC/AssemblyScanner.cs b/src/JasperFx.Core/IoC/AssemblyScanner.cs index 8fa88bc..b08e9ac 100644 --- a/src/JasperFx.Core/IoC/AssemblyScanner.cs +++ b/src/JasperFx.Core/IoC/AssemblyScanner.cs @@ -162,7 +162,13 @@ public void ConnectImplementationsToTypesClosing(Type openGenericType) public void ConnectImplementationsToTypesClosing(Type openGenericType, ServiceLifetime lifetime) { - var convention = new GenericConnectionScanner(openGenericType, lifetime); + var convention = new GenericConnectionScanner(openGenericType, t => lifetime); + With(convention); + } + + public void ConnectImplementationsToTypesClosing(Type openGenericType, Func lifetimeRule) + { + var convention = new GenericConnectionScanner(openGenericType, lifetimeRule); With(convention); } diff --git a/src/JasperFx.Core/IoC/FindAllTypesFilter.cs b/src/JasperFx.Core/IoC/FindAllTypesFilter.cs index ceaa85d..356e2dc 100644 --- a/src/JasperFx.Core/IoC/FindAllTypesFilter.cs +++ b/src/JasperFx.Core/IoC/FindAllTypesFilter.cs @@ -19,7 +19,7 @@ void IRegistrationConvention.ScanTypes(TypeSet types, IServiceCollection service { if (_serviceType.IsOpenGeneric()) { - var scanner = new GenericConnectionScanner(_serviceType, _lifetime); + var scanner = new GenericConnectionScanner(_serviceType); scanner.ScanTypes(types, services); } else diff --git a/src/JasperFx.Core/IoC/GenericConnectionScanner.cs b/src/JasperFx.Core/IoC/GenericConnectionScanner.cs index 2eaeb9a..3b63d2f 100644 --- a/src/JasperFx.Core/IoC/GenericConnectionScanner.cs +++ b/src/JasperFx.Core/IoC/GenericConnectionScanner.cs @@ -8,13 +8,14 @@ internal class GenericConnectionScanner : IRegistrationConvention { private readonly IList _concretions = new List(); private readonly IList _interfaces = new List(); - private readonly ServiceLifetime _lifetime; private readonly Type _openType; + private readonly Func _lifetimeRule; - public GenericConnectionScanner(Type openType, ServiceLifetime lifetime = ServiceLifetime.Transient) + public GenericConnectionScanner(Type openType, Func? lifetimeRule = null) { _openType = openType; - _lifetime = lifetime; + _lifetimeRule = lifetimeRule; + _lifetimeRule ??= _ => ServiceLifetime.Scoped; if (!_openType.IsOpenGeneric()) { @@ -45,7 +46,7 @@ public void ScanTypes(TypeSet types, IServiceCollection services) foreach (var @interface in _interfaces) { var exactMatches = _concretions.Where(x => x.CanBeCastTo(@interface)).ToArray(); - foreach (var type in exactMatches) services.Add(new ServiceDescriptor(@interface, type, _lifetime)); + foreach (var type in exactMatches) services.Add(new ServiceDescriptor(@interface, type, _lifetimeRule(type))); if (!@interface.IsOpenGeneric()) { @@ -70,8 +71,9 @@ private void addConcretionsThatCouldBeClosed(Type @interface, IServiceCollection { try { + var concreteType = type.MakeGenericType(@interface.GetGenericArguments()); services.Add(new ServiceDescriptor(@interface, - type.MakeGenericType(@interface.GetGenericArguments()), _lifetime)); + concreteType, _lifetimeRule(concreteType))); } catch (Exception) { diff --git a/src/JasperFx.Core/IoC/IAssemblyScanner.cs b/src/JasperFx.Core/IoC/IAssemblyScanner.cs index f798215..eaad5d9 100644 --- a/src/JasperFx.Core/IoC/IAssemblyScanner.cs +++ b/src/JasperFx.Core/IoC/IAssemblyScanner.cs @@ -222,4 +222,10 @@ void AssembliesAndExecutablesFromPath(string path, void AssembliesFromPath(string path, Func assemblyFilter); + /// + /// Scans for ServiceType's and Concrete Types that close the given open generic type + /// + /// + /// Configurable rule about how the lifetime is determined + void ConnectImplementationsToTypesClosing(Type openGenericType, Func lifetimeRule); } \ No newline at end of file diff --git a/src/JasperFx.Core/JasperFx.Core.csproj b/src/JasperFx.Core/JasperFx.Core.csproj index 39eb719..d6a8f4e 100644 --- a/src/JasperFx.Core/JasperFx.Core.csproj +++ b/src/JasperFx.Core/JasperFx.Core.csproj @@ -2,7 +2,7 @@ Common extension methods and reflection helpers used by JasperFx projects - 1.6.0 + 1.7.0 Jeremy D. Miller JasperFx.Core JasperFx.Core diff --git a/src/JasperFx.Core/Reflection/ReflectionExtensions.cs b/src/JasperFx.Core/Reflection/ReflectionExtensions.cs index df7e9ea..652d6e5 100644 --- a/src/JasperFx.Core/Reflection/ReflectionExtensions.cs +++ b/src/JasperFx.Core/Reflection/ReflectionExtensions.cs @@ -179,6 +179,17 @@ public static bool HasDefaultConstructor(this Type t) null, Type.EmptyTypes, null) != null; } + /// + /// Does this type have any constructors with arguments? + /// + /// + /// + public static bool HasConstructorsWithArguments(this Type t) + { + return t.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Any(x => x.GetParameters().Any()); + } + // http://stackoverflow.com/a/15273117/426840 /// /// Is the object an anonymous type that is not within a .Net