Skip to content

Commit

Permalink
Ability to configure the ServiceLifetime when doing service discovery…
Browse files Browse the repository at this point in the history
… using Connect***** scanning. Bumps to 1.7
  • Loading branch information
jeremydmiller committed Jun 3, 2024
1 parent 2c74962 commit 46fc2a3
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.ComponentModel;
using JasperFx.Core.IoC;
using JasperFx.Core.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Widgets1;
Expand All @@ -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<IRanger, RedRanger>();
registry.Scan(x =>
{
x.TheCallingAssembly();
Expand All @@ -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<IRanger, RedRanger>();
x.Scan(o =>
{
o.TheCallingAssembly();
Expand Down Expand Up @@ -231,12 +234,38 @@ public void use_default_scanning_with_if_newer_and_a_match()
container.GetServices<IRanger>().Single().ShouldBeOfType<Ranger>();
}

[Fact]
public void connect_implementations_with_lifetime_rule()
{
var services = new ServiceCollection();
services.AddScoped<IRanger, Ranger>();
services.Scan(x =>
{
x.AssemblyContainingType<IRanger>();
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<IFinder<int>>().ShouldBeSameAs(scope2.ServiceProvider.GetServices<IFinder<int>>());
scope1.ServiceProvider.GetServices<IFinder<string>>().ShouldNotBeSameAs(scope2.ServiceProvider.GetServices<IFinder<string>>());
}

public interface IFinder<T>
{
}

public class StringFinder : IFinder<string>
{
public StringFinder(IRanger ranger)
{
}
}

public class IntFinder : IFinder<int>
Expand Down
8 changes: 7 additions & 1 deletion src/JasperFx.Core/IoC/AssemblyScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Type, ServiceLifetime> lifetimeRule)
{
var convention = new GenericConnectionScanner(openGenericType, lifetimeRule);
With(convention);
}

Expand Down
2 changes: 1 addition & 1 deletion src/JasperFx.Core/IoC/FindAllTypesFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions src/JasperFx.Core/IoC/GenericConnectionScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ internal class GenericConnectionScanner : IRegistrationConvention
{
private readonly IList<Type> _concretions = new List<Type>();
private readonly IList<Type> _interfaces = new List<Type>();
private readonly ServiceLifetime _lifetime;
private readonly Type _openType;
private readonly Func<Type,ServiceLifetime> _lifetimeRule;

public GenericConnectionScanner(Type openType, ServiceLifetime lifetime = ServiceLifetime.Transient)
public GenericConnectionScanner(Type openType, Func<Type, ServiceLifetime>? lifetimeRule = null)
{
_openType = openType;
_lifetime = lifetime;
_lifetimeRule = lifetimeRule;
_lifetimeRule ??= _ => ServiceLifetime.Scoped;

if (!_openType.IsOpenGeneric())
{
Expand Down Expand Up @@ -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())
{
Expand All @@ -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)
{
Expand Down
6 changes: 6 additions & 0 deletions src/JasperFx.Core/IoC/IAssemblyScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,10 @@ void AssembliesAndExecutablesFromPath(string path,
void AssembliesFromPath(string path,
Func<Assembly, bool> assemblyFilter);

/// <summary>
/// Scans for ServiceType's and Concrete Types that close the given open generic type
/// </summary>
/// <param name="openGenericType"></param>
/// <param name="lifetimeRule">Configurable rule about how the lifetime is determined</param>
void ConnectImplementationsToTypesClosing(Type openGenericType, Func<Type, ServiceLifetime> lifetimeRule);
}
2 changes: 1 addition & 1 deletion src/JasperFx.Core/JasperFx.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Description>Common extension methods and reflection helpers used by JasperFx projects</Description>
<Version>1.6.0</Version>
<Version>1.7.0</Version>
<Authors>Jeremy D. Miller</Authors>
<AssemblyName>JasperFx.Core</AssemblyName>
<PackageId>JasperFx.Core</PackageId>
Expand Down
11 changes: 11 additions & 0 deletions src/JasperFx.Core/Reflection/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,17 @@ public static bool HasDefaultConstructor(this Type t)
null, Type.EmptyTypes, null) != null;
}

/// <summary>
/// Does this type have any constructors with arguments?
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
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
/// <summary>
/// Is the object an anonymous type that is not within a .Net
Expand Down

0 comments on commit 46fc2a3

Please sign in to comment.