Skip to content

Commit 7fd4b30

Browse files
authored
feat: Resolve additional CustomHandler type arguments from matching type (#39)
1 parent a103969 commit 7fd4b30

File tree

8 files changed

+149
-6
lines changed

8 files changed

+149
-6
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,24 @@ public static partial class ServiceCollectionExtensions
133133
}
134134
```
135135

136+
### Apply EF Core IEntityTypeConfiguration types
137+
138+
```csharp
139+
public static partial class ModelBuilderExtensions
140+
{
141+
[GenerateServiceRegistrations(AssignableTo = typeof(IEntityTypeConfiguration<>), CustomHandler = nameof(ApplyConfiguration))]
142+
public static partial ModelBuilder ApplyEntityConfigurations(this ModelBuilder modelBuilder);
143+
144+
private static void ApplyConfiguration<T, TEntity>(ModelBuilder modelBuilder)
145+
where T : IEntityTypeConfiguration<TEntity>, new()
146+
where TEntity : class
147+
{
148+
modelBuilder.ApplyConfiguration(new T());
149+
}
150+
}
151+
```
152+
153+
136154

137155
## Parameters
138156

ServiceScan.SourceGenerator.Tests/CustomHandlerTests.cs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,6 @@ public static partial void ProcessServices()
268268
Assert.Equal(expected, results.GeneratedTrees[1].ToString());
269269
}
270270

271-
272271
[Fact]
273272
public void AddMultipleCustomHandlerAttributesWithSameCustomHandler()
274273
{
@@ -319,6 +318,85 @@ public static partial void ProcessServices()
319318
Assert.Equal(expected, results.GeneratedTrees[1].ToString());
320319
}
321320

321+
[Fact]
322+
public void ResolveCustomHandlerGenericArguments()
323+
{
324+
var source = $$"""
325+
using ServiceScan.SourceGenerator;
326+
327+
namespace GeneratorTests;
328+
329+
public static partial class ModelBuilderExtensions
330+
{
331+
[GenerateServiceRegistrations(AssignableTo = typeof(IEntityTypeConfiguration<>), CustomHandler = nameof(ApplyConfiguration))]
332+
public static partial ModelBuilder ApplyEntityConfigurations(this ModelBuilder modelBuilder);
333+
334+
private static void ApplyConfiguration<T, TEntity>(ModelBuilder modelBuilder)
335+
where T : IEntityTypeConfiguration<TEntity>, new()
336+
where TEntity : class
337+
{
338+
modelBuilder.ApplyConfiguration(new T());
339+
}
340+
}
341+
""";
342+
343+
var infra = """
344+
public interface IEntityTypeConfiguration<TEntity> where TEntity : class
345+
{
346+
void Configure(EntityTypeBuilder<TEntity> builder);
347+
}
348+
349+
public class EntityTypeBuilder<TEntity> where TEntity : class;
350+
351+
public class ModelBuilder
352+
{
353+
public ModelBuilder ApplyConfiguration<TEntity>(IEntityTypeConfiguration<TEntity> configuration) where TEntity : class
354+
{
355+
return this;
356+
}
357+
}
358+
""";
359+
360+
var configurations = """
361+
namespace GeneratorTests;
362+
363+
public class EntityA;
364+
public class EntityB;
365+
366+
public class EntityAConfiguration : IEntityTypeConfiguration<EntityA>
367+
{
368+
public void Configure(EntityTypeBuilder<EntityA> builder) { }
369+
}
370+
371+
public class EntityBConfiguration : IEntityTypeConfiguration<EntityB>
372+
{
373+
public void Configure(EntityTypeBuilder<EntityB> builder) { }
374+
}
375+
""";
376+
377+
var compilation = CreateCompilation(source, infra, configurations);
378+
379+
var results = CSharpGeneratorDriver
380+
.Create(_generator)
381+
.RunGenerators(compilation)
382+
.GetRunResult();
383+
384+
var expected = $$"""
385+
namespace GeneratorTests;
386+
387+
public static partial class ModelBuilderExtensions
388+
{
389+
public static partial global::ModelBuilder ApplyEntityConfigurations(this global::ModelBuilder modelBuilder)
390+
{
391+
ApplyConfiguration<global::GeneratorTests.EntityAConfiguration, global::GeneratorTests.EntityA>(modelBuilder);
392+
ApplyConfiguration<global::GeneratorTests.EntityBConfiguration, global::GeneratorTests.EntityB>(modelBuilder);
393+
return modelBuilder;
394+
}
395+
}
396+
""";
397+
Assert.Equal(expected, results.GeneratedTrees[1].ToString());
398+
}
399+
322400

323401
private static Compilation CreateCompilation(params string[] source)
324402
{

ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,31 @@ private static DiagnosticModel<MethodImplementationModel> FindServicesToRegister
3737

3838
if (attribute.CustomHandler != null)
3939
{
40-
customHandlers.Add(new CustomHandlerModel(attribute.CustomHandler, implementationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)));
40+
// If CustomHandler method has multiple type parameters, which are resolvable from the first one - we try to provide them.
41+
// e.g. ApplyConfiguration<T, TEntity>(ModelBuilder modelBuilder) where T : IEntityTypeConfiguration<TEntity>
42+
if (attribute.CustomHandlerTypeParametersCount > 1 && matchedTypes != null)
43+
{
44+
foreach (var matchedType in matchedTypes)
45+
{
46+
EquatableArray<string> typeArguments =
47+
[
48+
implementationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
49+
.. matchedType.TypeArguments.Select(a => a.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
50+
];
51+
52+
customHandlers.Add(new CustomHandlerModel(attribute.CustomHandler, typeArguments));
53+
}
54+
}
55+
else
56+
{
57+
customHandlers.Add(new CustomHandlerModel(attribute.CustomHandler, [implementationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)]));
58+
}
4159
}
4260
else
4361
{
4462
var serviceTypes = (attribute.AsSelf, attribute.AsImplementedInterfaces) switch
4563
{
46-
(true, true) => new[] { implementationType }.Concat(GetSuitableInterfaces(implementationType)),
64+
(true, true) => [implementationType, .. GetSuitableInterfaces(implementationType)],
4765
(false, true) => GetSuitableInterfaces(implementationType),
4866
(true, false) => [implementationType],
4967
_ => matchedTypes ?? [implementationType]

ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ public partial class DependencyInjectionGenerator
6969

7070
if (!typesMatch)
7171
return Diagnostic.Create(CustomHandlerMethodHasIncorrectSignature, attribute.Location);
72+
73+
// If CustomHandler has more than 1 type parameters, we try to resolve them from
74+
// matched assignableTo type arguments.
75+
// e.g. ApplyConfiguration<T, TEntity>(ModelBuilder modelBuilder) where T : IEntityTypeConfiguration<TEntity>
76+
if (customHandlerMethod.TypeParameters.Length > 1
77+
&& customHandlerMethod.TypeParameters.Length != attribute.AssignableToTypeParametersCount + 1)
78+
{
79+
return Diagnostic.Create(CustomHandlerMethodHasIncorrectSignature, attribute.Location);
80+
}
7281
}
7382

7483
if (attributeData[i].HasErrors)

ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ private static string GenerateRegistrationsSource(MethodModel method, EquatableA
108108
private static string GenerateCustomHandlingSource(MethodModel method, EquatableArray<CustomHandlerModel> customHandlers)
109109
{
110110
var invocations = string.Join("\n", customHandlers.Select(h =>
111-
$" {h.HandlerMethodName}<{h.TypeName}>({string.Join(", ", method.Parameters.Select(p => p.Name))});"));
111+
{
112+
var genericArguments = string.Join(", ", h.TypeArguments);
113+
var arguments = string.Join(", ", method.Parameters.Select(p => p.Name));
114+
return $" {h.HandlerMethodName}<{genericArguments}>({arguments});";
115+
}));
112116

113117
var namespaceDeclaration = method.Namespace is null ? "" : $"namespace {method.Namespace};";
114118
var parameters = string.Join(",", method.Parameters.Select((p, i) =>

ServiceScan.SourceGenerator/Model/AttributeModel.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ enum KeySelectorType { Method, GenericMethod, TypeMember };
77

88
record AttributeModel(
99
string? AssignableToTypeName,
10+
int AssignableToTypeParametersCount,
1011
string? AssemblyNameFilter,
1112
EquatableArray<string>? AssignableToGenericArguments,
1213
string? AssemblyOfTypeName,
@@ -20,6 +21,7 @@ record AttributeModel(
2021
string? KeySelector,
2122
KeySelectorType? KeySelectorType,
2223
string? CustomHandler,
24+
int CustomHandlerTypeParametersCount,
2325
bool AsImplementedInterfaces,
2426
bool AsSelf,
2527
Location Location,
@@ -42,6 +44,8 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho
4244
var keySelector = attribute.NamedArguments.FirstOrDefault(a => a.Key == "KeySelector").Value.Value as string;
4345
var customHandler = attribute.NamedArguments.FirstOrDefault(a => a.Key == "CustomHandler").Value.Value as string;
4446

47+
var assignableToTypeParametersCount = assignableTo?.TypeParameters.Length ?? 0;
48+
4549
KeySelectorType? keySelectorType = null;
4650
if (keySelector != null)
4751
{
@@ -59,6 +63,16 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho
5963
}
6064
}
6165

66+
var customHandlerGenericParameters = 0;
67+
if (customHandler != null)
68+
{
69+
var customHandlerMethod = method.ContainingType.GetMembers()
70+
.OfType<IMethodSymbol>()
71+
.FirstOrDefault(m => m.IsStatic && m.Name == customHandler);
72+
73+
customHandlerGenericParameters = customHandlerMethod?.TypeParameters.Length ?? 0;
74+
}
75+
6276
if (string.IsNullOrWhiteSpace(typeNameFilter))
6377
typeNameFilter = null;
6478

@@ -97,6 +111,7 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho
97111

98112
return new(
99113
assignableToTypeName,
114+
assignableToTypeParametersCount,
100115
assemblyNameFilter,
101116
assignableToGenericArguments,
102117
assemblyOfTypeName,
@@ -110,6 +125,7 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho
110125
keySelector,
111126
keySelectorType,
112127
customHandler,
128+
customHandlerGenericParameters,
113129
asImplementedInterfaces,
114130
asSelf,
115131
location,

ServiceScan.SourceGenerator/Model/ServiceRegistrationModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ record ServiceRegistrationModel(
1111

1212
record CustomHandlerModel(
1313
string HandlerMethodName,
14-
string TypeName);
14+
EquatableArray<string> TypeArguments);

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "2.1",
3+
"version": "2.2",
44
"publicReleaseRefSpec": [
55
"^refs/heads/main",
66
"^refs/heads/v\\d+(?:\\.\\d+)?$"

0 commit comments

Comments
 (0)