Skip to content

Commit 2cdfc8d

Browse files
mokarchikhellang
authored andcommitted
Add support for service key registration
Introduced service key registration in the `ILifetimeSelector` interface with two new methods: `WithServiceKey(object serviceKey)` and `WithServiceKey(Func<Type, object?> selector)`. These enable registering services with fixed or dynamically determined service keys. Updated the `LifetimeSelector` class to handle service key functionality, including adding the `ServiceKeySelectorFn` property, modifying the `Populate` method to support keyed `ServiceDescriptor` creation, and adding helper methods for service key resolution. Enhanced the `ServiceTypeSelector` class to propagate service key selectors to `LifetimeSelector` instances. Added unit tests in `ScanningTests.cs` to validate service key registration and resolution, including tests for fixed keys, selector-based keys, and keyed service resolution. Made minor formatting adjustments and ensured proper validation of the new functionality.
1 parent a68a193 commit 2cdfc8d

File tree

4 files changed

+136
-13
lines changed

4 files changed

+136
-13
lines changed

src/Scrutor/ILifetimeSelector.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System;
2-
using Microsoft.Extensions.DependencyInjection;
1+
using Microsoft.Extensions.DependencyInjection;
2+
using System;
33

44
namespace Scrutor;
55

@@ -29,4 +29,16 @@ public interface ILifetimeSelector : IServiceTypeSelector
2929
/// Registers each matching concrete type with a lifetime based on the provided <paramref name="selector"/>.
3030
/// </summary>
3131
IImplementationTypeSelector WithLifetime(Func<Type, ServiceLifetime> selector);
32+
33+
/// <summary>
34+
/// Registers each matching concrete type with the specified <paramref name="serviceKey"/>.
35+
/// </summary>
36+
/// <param name="serviceKey">The service key to use for registration.</param>
37+
ILifetimeSelector WithServiceKey(object serviceKey);
38+
39+
/// <summary>
40+
/// Registers each matching concrete type with a service key based on the provided <paramref name="selector"/>.
41+
/// </summary>
42+
/// <param name="selector">A function to determine the service key for each type.</param>
43+
ILifetimeSelector WithServiceKey(Func<Type, object?> selector);
3244
}

src/Scrutor/LifetimeSelector.cs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
using System;
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.DependencyModel;
3+
using System;
24
using System.Collections.Generic;
35
using System.Diagnostics.CodeAnalysis;
46
using System.Reflection;
5-
using Microsoft.Extensions.DependencyInjection;
6-
using Microsoft.Extensions.DependencyModel;
77

88
namespace Scrutor;
99

@@ -24,6 +24,8 @@ public LifetimeSelector(ServiceTypeSelector inner, IEnumerable<TypeMap> typeMaps
2424

2525
public Func<Type, ServiceLifetime>? SelectorFn { get; set; }
2626

27+
public Func<Type, object?>? ServiceKeySelectorFn { get; set; }
28+
2729
public IImplementationTypeSelector WithSingletonLifetime()
2830
{
2931
return WithLifetime(ServiceLifetime.Singleton);
@@ -54,6 +56,22 @@ public IImplementationTypeSelector WithLifetime(Func<Type, ServiceLifetime> sele
5456
return this;
5557
}
5658

59+
public ILifetimeSelector WithServiceKey(object serviceKey)
60+
{
61+
Preconditions.NotNull(serviceKey, nameof(serviceKey));
62+
63+
return WithServiceKey(_ => serviceKey);
64+
}
65+
66+
public ILifetimeSelector WithServiceKey(Func<Type, object?> selector)
67+
{
68+
Preconditions.NotNull(selector, nameof(selector));
69+
70+
Inner.PropagateServiceKey(selector);
71+
72+
return this;
73+
}
74+
5775
#region Chain Methods
5876

5977
[ExcludeFromCodeCoverage]
@@ -231,6 +249,7 @@ void ISelector.Populate(IServiceCollection services, RegistrationStrategy? strat
231249
strategy ??= RegistrationStrategy.Append;
232250

233251
var lifetimes = new Dictionary<Type, ServiceLifetime>();
252+
var serviceKeys = new Dictionary<Type, object?>();
234253

235254
foreach (var typeMap in TypeMaps)
236255
{
@@ -244,8 +263,10 @@ void ISelector.Populate(IServiceCollection services, RegistrationStrategy? strat
244263
}
245264

246265
var lifetime = GetOrAddLifetime(lifetimes, implementationType);
247-
248-
var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
266+
var serviceKey = GetOrAddServiceKey(serviceKeys, implementationType);
267+
var descriptor = serviceKey is not null
268+
? new ServiceDescriptor(serviceType, serviceKey, implementationType, lifetime)
269+
: new ServiceDescriptor(serviceType, implementationType, lifetime);
249270

250271
strategy.Apply(services, descriptor);
251272
}
@@ -256,8 +277,11 @@ void ISelector.Populate(IServiceCollection services, RegistrationStrategy? strat
256277
foreach (var serviceType in typeFactoryMap.ServiceTypes)
257278
{
258279
var lifetime = GetOrAddLifetime(lifetimes, typeFactoryMap.ImplementationType);
280+
var serviceKey = GetOrAddServiceKey(serviceKeys, typeFactoryMap.ImplementationType);
259281

260-
var descriptor = new ServiceDescriptor(serviceType, typeFactoryMap.ImplementationFactory, lifetime);
282+
var descriptor = serviceKey is not null
283+
? new ServiceDescriptor(serviceType, serviceKey, WrapImplementationFactory(typeFactoryMap.ImplementationFactory), lifetime)
284+
: new ServiceDescriptor(serviceType, typeFactoryMap.ImplementationFactory, lifetime);
261285

262286
strategy.Apply(services, descriptor);
263287
}
@@ -277,4 +301,23 @@ private ServiceLifetime GetOrAddLifetime(Dictionary<Type, ServiceLifetime> lifet
277301

278302
return lifetime;
279303
}
304+
305+
private object? GetOrAddServiceKey(Dictionary<Type, object?> serviceKeys, Type implementationType)
306+
{
307+
if (serviceKeys.TryGetValue(implementationType, out var serviceKey))
308+
{
309+
return serviceKey;
310+
}
311+
312+
serviceKey = ServiceKeySelectorFn?.Invoke(implementationType);
313+
314+
serviceKeys[implementationType] = serviceKey;
315+
316+
return serviceKey;
317+
}
318+
319+
private static Func<IServiceProvider, object?, object> WrapImplementationFactory(Func<IServiceProvider, object> factory)
320+
{
321+
return (sp, _) => factory(sp);
322+
}
280323
}

src/Scrutor/ServiceTypeSelector.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
using System;
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.DependencyModel;
3+
using System;
24
using System.Collections;
35
using System.Collections.Generic;
46
using System.Linq;
57
using System.Reflection;
6-
using Microsoft.Extensions.DependencyInjection;
7-
using Microsoft.Extensions.DependencyModel;
88

99
namespace Scrutor;
1010

@@ -207,6 +207,14 @@ internal void PropagateLifetime(Func<Type, ServiceLifetime> selectorFn)
207207
}
208208
}
209209

210+
internal void PropagateServiceKey(Func<Type, object?> selectorFn)
211+
{
212+
foreach (var selector in Selectors.OfType<LifetimeSelector>())
213+
{
214+
selector.ServiceKeySelectorFn = selectorFn;
215+
}
216+
}
217+
210218
void ISelector.Populate(IServiceCollection services, RegistrationStrategy? registrationStrategy)
211219
{
212220
if (Selectors.Count == 0)

test/Scrutor.Tests/ScanningTests.cs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,10 +584,70 @@ public void ShouldAllowOptInToCompilerGeneratedTypes()
584584
.AsSelf()
585585
.WithTransientLifetime());
586586
});
587-
587+
588588
var compilerGeneratedSubclass = provider.GetService<AllowedCompilerGeneratedSubclass>();
589589
Assert.NotNull(compilerGeneratedSubclass);
590590
}
591+
592+
[Fact]
593+
public void CanRegisterWithServiceKey()
594+
{
595+
Collection.Scan(scan => scan
596+
.FromTypes<TransientService1, TransientService2>()
597+
.AsImplementedInterfaces(x => x != typeof(IOtherInheritance))
598+
.WithServiceKey("my-key")
599+
.WithSingletonLifetime());
600+
601+
Assert.Equal(2, Collection.Count);
602+
603+
Assert.All(Collection, x =>
604+
{
605+
Assert.Equal(ServiceLifetime.Singleton, x.Lifetime);
606+
Assert.Equal(typeof(ITransientService), x.ServiceType);
607+
Assert.True(x.IsKeyedService);
608+
Assert.Equal("my-key", x.ServiceKey);
609+
});
610+
}
611+
612+
[Fact]
613+
public void CanRegisterWithServiceKeySelector()
614+
{
615+
Collection.Scan(scan => scan
616+
.FromTypes<TransientService1, TransientService2>()
617+
.AsImplementedInterfaces(x => x != typeof(IOtherInheritance))
618+
.WithServiceKey(type => type.Name)
619+
.WithSingletonLifetime());
620+
621+
Assert.Equal(2, Collection.Count);
622+
623+
var service1 = Collection.First(x => x.ServiceKey as string == nameof(TransientService1));
624+
Assert.Equal(typeof(ITransientService), service1.ServiceType);
625+
Assert.Equal(ServiceLifetime.Singleton, service1.Lifetime);
626+
Assert.True(service1.IsKeyedService);
627+
628+
var service2 = Collection.First(x => x.ServiceKey as string == nameof(TransientService2));
629+
Assert.Equal(typeof(ITransientService), service2.ServiceType);
630+
Assert.Equal(ServiceLifetime.Singleton, service2.Lifetime);
631+
Assert.True(service2.IsKeyedService);
632+
}
633+
634+
[Fact]
635+
public void CanResolveKeyedServices()
636+
{
637+
Collection.Scan(scan => scan
638+
.FromTypes<TransientService1, TransientService2>()
639+
.AsSelf()
640+
.WithServiceKey(type => type.Name)
641+
.WithTransientLifetime());
642+
643+
var provider = Collection.BuildServiceProvider();
644+
645+
var service1 = provider.GetRequiredKeyedService<TransientService1>(nameof(TransientService1));
646+
var service2 = provider.GetRequiredKeyedService<TransientService2>(nameof(TransientService2));
647+
648+
Assert.NotNull(service1);
649+
Assert.NotNull(service2);
650+
}
591651
}
592652

593653
// ReSharper disable UnusedTypeParameter
@@ -671,7 +731,7 @@ public class DefaultAttributes : IDefault3Level2, IDefault1, IDefault2 { }
671731
[CompilerGenerated]
672732
public class CompilerGenerated { }
673733

674-
public class CombinedService2: IDefault1, IDefault2, IDefault3Level2 { }
734+
public class CombinedService2 : IDefault1, IDefault2, IDefault3Level2 { }
675735

676736
public interface IGenericAttribute { }
677737

0 commit comments

Comments
 (0)