diff --git a/src/Scrutor/IImplementationTypeFilter.cs b/src/Scrutor/IImplementationTypeFilter.cs
index 15458765..d176ede3 100644
--- a/src/Scrutor/IImplementationTypeFilter.cs
+++ b/src/Scrutor/IImplementationTypeFilter.cs
@@ -32,6 +32,12 @@ public interface IImplementationTypeFilter : IFluentInterface
/// If the argument is null.
IImplementationTypeFilter AssignableToAny(IEnumerable types);
+ ///
+ /// Will match all types that are assignable to ServiceKeyAttribute />.
+ ///
+ ///
+ IImplementationTypeFilter WithKeyName();
+
///
/// Will match all types that has an attribute of type defined.
///
diff --git a/src/Scrutor/ImplementationTypeFilter.cs b/src/Scrutor/ImplementationTypeFilter.cs
index 5afe35d4..2da68c98 100644
--- a/src/Scrutor/ImplementationTypeFilter.cs
+++ b/src/Scrutor/ImplementationTypeFilter.cs
@@ -42,6 +42,10 @@ public IImplementationTypeFilter AssignableToAny(IEnumerable types)
return Where(t => types.Any(t.IsBasedOn));
}
+
+ public IImplementationTypeFilter WithKeyName()
+ => WithAttribute();
+
public IImplementationTypeFilter WithAttribute() where T : Attribute
{
return WithAttribute(typeof(T));
@@ -52,7 +56,7 @@ public IImplementationTypeFilter WithAttribute(Type attributeType)
Preconditions.NotNull(attributeType, nameof(attributeType));
IncludeCompilerGeneratedTypes |= attributeType == typeof(CompilerGeneratedAttribute);
- return Where(t => t.HasAttribute(attributeType));
+ return Where(t => t.HasAttribute(attributeType) );
}
public IImplementationTypeFilter WithAttribute(Func predicate) where T : Attribute
diff --git a/src/Scrutor/LifetimeSelector.cs b/src/Scrutor/LifetimeSelector.cs
index 595ab23d..cd183e43 100644
--- a/src/Scrutor/LifetimeSelector.cs
+++ b/src/Scrutor/LifetimeSelector.cs
@@ -245,7 +245,11 @@ void ISelector.Populate(IServiceCollection services, RegistrationStrategy? strat
var lifetime = GetOrAddLifetime(lifetimes, implementationType);
- var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
+ ServiceKeyAttribute? attr = implementationType.GetCustomAttribute();
+
+ ServiceDescriptor descriptor = attr != null
+ ? new ServiceDescriptor(serviceType, attr.Name, implementationType, lifetime)
+ : new ServiceDescriptor(serviceType, implementationType, lifetime);
strategy.Apply(services, descriptor);
}
diff --git a/src/Scrutor/Scrutor.csproj b/src/Scrutor/Scrutor.csproj
index b43c51b4..bd4f35fb 100644
--- a/src/Scrutor/Scrutor.csproj
+++ b/src/Scrutor/Scrutor.csproj
@@ -7,7 +7,7 @@
$(NoWarn);CS1591
latest
enable
- true
+ false
true
Scrutor
Dependency;Injection;DI;Scanning;Conventions;Decoration
diff --git a/src/Scrutor/ServiceKeyAttribute.cs b/src/Scrutor/ServiceKeyAttribute.cs
new file mode 100644
index 00000000..e45afd00
--- /dev/null
+++ b/src/Scrutor/ServiceKeyAttribute.cs
@@ -0,0 +1,11 @@
+using JetBrains.Annotations;
+using System;
+
+namespace Scrutor;
+
+[PublicAPI]
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+public sealed class ServiceKeyAttribute(string name): Attribute
+{
+ public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
+}
diff --git a/test/Scrutor.Tests/ScanningTests.cs b/test/Scrutor.Tests/ScanningTests.cs
index 4dcbbf2e..c523e7bb 100644
--- a/test/Scrutor.Tests/ScanningTests.cs
+++ b/test/Scrutor.Tests/ScanningTests.cs
@@ -584,10 +584,35 @@ public void ShouldAllowOptInToCompilerGeneratedTypes()
.AsSelf()
.WithTransientLifetime());
});
-
+
var compilerGeneratedSubclass = provider.GetService();
Assert.NotNull(compilerGeneratedSubclass);
}
+
+
+ [Fact]
+ public void KeyedTransientService()
+ {
+ ServiceProvider provider = ConfigureProvider(services =>
+ {
+ services.Scan(scan => scan
+ .FromAssemblyOf()
+ .AddClasses(classes => classes.AssignableTo())
+ .AsImplementedInterfaces()
+ .WithTransientLifetime());
+
+ var namedServices = services.GetDescriptors();
+
+ Assert.Equal(2, namedServices.Count(x => x.ServiceType == typeof(INamedService)));
+ });
+
+ var transientKeyedService1 = provider.GetKeyedService("t1");
+ Assert.NotNull(transientKeyedService1);
+
+ var transientKeyedService2 = provider.GetKeyedService("t1");
+ Assert.NotNull(transientKeyedService2);
+
+ }
}
// ReSharper disable UnusedTypeParameter
@@ -597,6 +622,7 @@ public interface ITransientService { }
[ServiceDescriptor(typeof(ITransientService))]
public class TransientService1 : ITransientService { }
+
public class TransientService2 : ITransientService, IOtherInheritance { }
public class TransientService : ITransientService, IEnumerable
@@ -671,7 +697,7 @@ public class DefaultAttributes : IDefault3Level2, IDefault1, IDefault2 { }
[CompilerGenerated]
public class CompilerGenerated { }
- public class CombinedService2: IDefault1, IDefault2, IDefault3Level2 { }
+ public class CombinedService2 : IDefault1, IDefault2, IDefault3Level2 { }
public interface IGenericAttribute { }
@@ -688,6 +714,15 @@ public abstract class AllowedCompilerGeneratedBase { }
[CompilerGenerated]
public class AllowedCompilerGeneratedSubclass : AllowedCompilerGeneratedBase { }
+
+
+ public interface INamedService { }
+
+ [ServiceKey("t1")]
+ public class NameService1 : INamedService { }
+
+ [ServiceKey("t2")]
+ public class NameService2 : INamedService { }
}
namespace Scrutor.Tests.ChildNamespace