diff --git a/README.md b/README.md
index 8687e3a..b1159fa 100644
--- a/README.md
+++ b/README.md
@@ -116,5 +116,5 @@ public static partial class ServiceCollectionExtensions
| **AsSelf** | If true, types will be registered with their actual type. It can be combined with `AsImplementedInterfaces`. In that case implemented interfaces will be "forwarded" to an actual implementation type |
| **TypeNameFilter** | Set this value to filter the types to register by their full name. You can use '*' wildcards. You can also use ',' to separate multiple filters. |
| **WithAttribute** | Filter types by specified attribute type present. |
-| **KeySelector** | Set this value to a static method name returning string. Returned value will be used as a key for the registration. Method should either be generic, or have a single parameter of type `Type`. |
-| **CustomHandler** | Set this property to a static generic method name in the current class. This property is incompatible with `Lifetime`, `AsImplementedInterfaces`, `AsSelf`, `KeySelector` properties. |
+| **KeySelector** | Set this property to add types as keyed services. This property should point to one of the following:
- Name of the static method in the current type with string return type. Method should be either generic, or have a single parameter of type `Type`.
- Const field or static property in the implementation type. |
+| **CustomHandler** | Set this property to a static generic method name in the current class. This property is incompatible with `Lifetime`, `AsImplementedInterfaces`, `AsSelf`, `KeySelector` properties. |
\ No newline at end of file
diff --git a/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs b/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs
index d364492..8001fd6 100644
--- a/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs
+++ b/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs
@@ -632,6 +632,42 @@ public class MyService2 : IService { }
Assert.Equal(Sources.GetMethodImplementation(registrations), results.GeneratedTrees[1].ToString());
}
+ [Fact]
+ public void AddAsKeyedServices_ConstantFieldInType()
+ {
+ var attribute = @"[GenerateServiceRegistrations(AssignableTo = typeof(IService), KeySelector = ""Key"")]";
+
+ var compilation = CreateCompilation(
+ Sources.MethodWithAttribute(attribute),
+ """
+ namespace GeneratorTests;
+
+ public interface IService { }
+
+ public class MyService1 : IService
+ {
+ public const string Key = "MSR1";
+ }
+
+ public class MyService2 : IService
+ {
+ public const string Key = "MSR2";
+ }
+ """);
+
+ var results = CSharpGeneratorDriver
+ .Create(_generator)
+ .RunGenerators(compilation)
+ .GetRunResult();
+
+ var registrations = $"""
+ return services
+ .AddKeyedTransient(GeneratorTests.MyService1.Key)
+ .AddKeyedTransient(GeneratorTests.MyService2.Key);
+ """;
+ Assert.Equal(Sources.GetMethodImplementation(registrations), results.GeneratedTrees[1].ToString());
+ }
+
[Fact]
public void DontGenerateAnythingIfTypeIsInvalid()
{
diff --git a/ServiceScan.SourceGenerator.Tests/DiagnosticTests.cs b/ServiceScan.SourceGenerator.Tests/DiagnosticTests.cs
index 2ee9612..f81d6a9 100644
--- a/ServiceScan.SourceGenerator.Tests/DiagnosticTests.cs
+++ b/ServiceScan.SourceGenerator.Tests/DiagnosticTests.cs
@@ -202,32 +202,6 @@ public static partial class ServicesExtensions
Assert.Equal(results.Diagnostics.Single().Descriptor, DiagnosticDescriptors.MissingSearchCriteria);
}
- [Fact]
- public void KeySelectorMethodDoesNotExist()
- {
- var attribute = @"
- private static string GetName() => typeof(T).Name.Replace(""Service"", """");
-
- [GenerateServiceRegistrations(AssignableTo = typeof(IService), KeySelector = ""NoSuchMethodHere"")]";
-
- var compilation = CreateCompilation(
- Sources.MethodWithAttribute(attribute),
- """
- namespace GeneratorTests;
-
- public interface IService { }
- public class MyService1 : IService { }
- public class MyService2 : IService { }
- """);
-
- var results = CSharpGeneratorDriver
- .Create(_generator)
- .RunGenerators(compilation)
- .GetRunResult();
-
- Assert.Equal(results.Diagnostics.Single().Descriptor, DiagnosticDescriptors.KeySelectorMethodNotFound);
- }
-
[Fact]
public void KeySelectorMethod_GenericButHasParameters()
{
diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs
index 29c14f7..de7b396 100644
--- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs
+++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs
@@ -61,7 +61,7 @@ private static DiagnosticModel FindServicesToRegister
false,
true,
attribute.KeySelector,
- attribute.KeySelectorGeneric);
+ attribute.KeySelectorType);
registrations.Add(registration);
}
@@ -75,7 +75,7 @@ private static DiagnosticModel FindServicesToRegister
shouldResolve,
false,
attribute.KeySelector,
- attribute.KeySelectorGeneric);
+ attribute.KeySelectorType);
registrations.Add(registration);
}
diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs
index 6f58f0b..f64de87 100644
--- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs
+++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs
@@ -35,17 +35,17 @@ private static DiagnosticModel ParseRegisterMethodMod
var keySelectorMethod = method.ContainingType.GetMembers().OfType()
.FirstOrDefault(m => m.IsStatic && m.Name == attribute.KeySelector);
- if (keySelectorMethod is null)
- return Diagnostic.Create(KeySelectorMethodNotFound, attribute.Location);
+ if (keySelectorMethod is not null)
+ {
+ if (keySelectorMethod.ReturnsVoid)
+ return Diagnostic.Create(KeySelectorMethodHasIncorrectSignature, attribute.Location);
- if (keySelectorMethod.ReturnsVoid)
- return Diagnostic.Create(KeySelectorMethodHasIncorrectSignature, attribute.Location);
+ var validGenericKeySelector = keySelectorMethod.TypeArguments.Length == 1 && keySelectorMethod.Parameters.Length == 0;
+ var validNonGenericKeySelector = !keySelectorMethod.IsGenericMethod && keySelectorMethod.Parameters is [{ Type.Name: nameof(Type) }];
- var validGenericKeySelector = keySelectorMethod.TypeArguments.Length == 1 && keySelectorMethod.Parameters.Length == 0;
- var validNonGenericKeySelector = !keySelectorMethod.IsGenericMethod && keySelectorMethod.Parameters is [{ Type.Name: nameof(Type) }];
-
- if (!validGenericKeySelector && !validNonGenericKeySelector)
- return Diagnostic.Create(KeySelectorMethodHasIncorrectSignature, attribute.Location);
+ if (!validGenericKeySelector && !validNonGenericKeySelector)
+ return Diagnostic.Create(KeySelectorMethodHasIncorrectSignature, attribute.Location);
+ }
}
if (attribute.CustomHandler != null)
diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs
index 654cfb2..0d46ef6 100644
--- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs
+++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs
@@ -66,18 +66,19 @@ private static string GenerateRegistrationsSource(MethodModel method, EquatableA
}
else
{
- var addMethod = registration.KeySelectorMethodName != null
+ var addMethod = registration.KeySelector != null
? $"AddKeyed{registration.Lifetime}"
: $"Add{registration.Lifetime}";
- var keyMethodInvocation = registration.KeySelectorMethodGeneric switch
+ var keySelectorInvocation = registration.KeySelectorType switch
{
- true => $"{registration.KeySelectorMethodName}<{registration.ImplementationTypeName}>()",
- false => $"{registration.KeySelectorMethodName}(typeof({registration.ImplementationTypeName}))",
- null => null
+ KeySelectorType.GenericMethod => $"{registration.KeySelector}<{registration.ImplementationTypeName}>()",
+ KeySelectorType.Method => $"{registration.KeySelector}(typeof({registration.ImplementationTypeName}))",
+ KeySelectorType.TypeMember => $"{registration.ImplementationTypeName}.{registration.KeySelector}",
+ _ => null
};
- return $" .{addMethod}<{registration.ServiceTypeName}, {registration.ImplementationTypeName}>({keyMethodInvocation})";
+ return $" .{addMethod}<{registration.ServiceTypeName}, {registration.ImplementationTypeName}>({keySelectorInvocation})";
}
}
}));
diff --git a/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs b/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs
index 7f45897..bfe388f 100644
--- a/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs
+++ b/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs
@@ -39,13 +39,6 @@ public static class DiagnosticDescriptors
DiagnosticSeverity.Warning,
true);
- public static readonly DiagnosticDescriptor KeySelectorMethodNotFound = new("DI0006",
- "Provided KeySelector method is not found",
- "KeySelector parameter should point to a static method in the class",
- "Usage",
- DiagnosticSeverity.Error,
- true);
-
public static readonly DiagnosticDescriptor KeySelectorMethodHasIncorrectSignature = new("DI0007",
"Provided KeySelector method has incorrect signature",
"KeySelector should have non-void return type, and either be generic with no parameters, or non-generic with a single Type parameter",
diff --git a/ServiceScan.SourceGenerator/GenerateAttributeSource.cs b/ServiceScan.SourceGenerator/GenerateAttributeSource.cs
index 4e2caef..f0db928 100644
--- a/ServiceScan.SourceGenerator/GenerateAttributeSource.cs
+++ b/ServiceScan.SourceGenerator/GenerateAttributeSource.cs
@@ -61,9 +61,11 @@ internal class GenerateServiceRegistrationsAttribute : Attribute
public string? TypeNameFilter { get; set; }
///
- /// Set this property to a static method name returning string.
- /// Returned value will be used as a key for the registration.
- /// Method should either be generic, or have a single parameter of type .
+ /// Set this property to add types as keyed services.
+ /// This property should point to one of the following:
+ /// - Name of the static method in the current type with string return type.
+ /// Method should be either generic, or have a single parameter of type .
+ /// - Const field or static property in the implementation type.
///
/// nameof(GetKey)
public string? KeySelector { get; set; }
diff --git a/ServiceScan.SourceGenerator/Model/AttributeModel.cs b/ServiceScan.SourceGenerator/Model/AttributeModel.cs
index 249ae4c..91fd633 100644
--- a/ServiceScan.SourceGenerator/Model/AttributeModel.cs
+++ b/ServiceScan.SourceGenerator/Model/AttributeModel.cs
@@ -3,6 +3,8 @@
namespace ServiceScan.SourceGenerator.Model;
+enum KeySelectorType { Method, GenericMethod, TypeMember };
+
record AttributeModel(
string? AssignableToTypeName,
EquatableArray? AssignableToGenericArguments,
@@ -11,7 +13,7 @@ record AttributeModel(
string Lifetime,
string? TypeNameFilter,
string? KeySelector,
- bool? KeySelectorGeneric,
+ KeySelectorType? KeySelectorType,
string? CustomHandler,
bool AsImplementedInterfaces,
bool AsSelf,
@@ -31,7 +33,7 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho
var keySelector = attribute.NamedArguments.FirstOrDefault(a => a.Key == "KeySelector").Value.Value as string;
var customHandler = attribute.NamedArguments.FirstOrDefault(a => a.Key == "CustomHandler").Value.Value as string;
- bool? keySelectorGeneric = null;
+ KeySelectorType? keySelectorType = null;
if (keySelector != null)
{
var keySelectorMethod = method.ContainingType.GetMembers()
@@ -40,7 +42,11 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho
if (keySelectorMethod != null)
{
- keySelectorGeneric = keySelectorMethod.IsGenericMethod;
+ keySelectorType = keySelectorMethod.IsGenericMethod ? Model.KeySelectorType.GenericMethod : Model.KeySelectorType.Method;
+ }
+ else
+ {
+ keySelectorType = Model.KeySelectorType.TypeMember;
}
}
@@ -77,7 +83,7 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho
lifetime,
typeNameFilter,
keySelector,
- keySelectorGeneric,
+ keySelectorType,
customHandler,
asImplementedInterfaces,
asSelf,
diff --git a/ServiceScan.SourceGenerator/Model/ServiceRegistrationModel.cs b/ServiceScan.SourceGenerator/Model/ServiceRegistrationModel.cs
index 5b7b3e2..ef98acf 100644
--- a/ServiceScan.SourceGenerator/Model/ServiceRegistrationModel.cs
+++ b/ServiceScan.SourceGenerator/Model/ServiceRegistrationModel.cs
@@ -6,8 +6,8 @@ record ServiceRegistrationModel(
string ImplementationTypeName,
bool ResolveImplementation,
bool IsOpenGeneric,
- string? KeySelectorMethodName,
- bool? KeySelectorMethodGeneric);
+ string? KeySelector,
+ KeySelectorType? KeySelectorType);
record CustomHandlerModel(
string HandlerMethodName,