Skip to content

Commit 2ce229f

Browse files
authored
Improved performance. (#71)
1 parent 2a18f7e commit 2ce229f

File tree

47 files changed

+3189
-3282
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+3189
-3282
lines changed

README.md

Lines changed: 38 additions & 38 deletions
Large diffs are not rendered by default.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System.Text;
2+
using GeneratedEndpoints.Common;
3+
using Microsoft.CodeAnalysis;
4+
using Microsoft.CodeAnalysis.Text;
5+
using static GeneratedEndpoints.Common.Constants;
6+
7+
namespace GeneratedEndpoints;
8+
9+
// ReSharper disable ForCanBeConvertedToForeach
10+
// ReSharper disable LoopCanBeConvertedToQuery
11+
// Do not refactor, use for loop to avoid allocations.
12+
13+
internal static class AddEndpointHandlersGenerator
14+
{
15+
public static void GenerateSource(SourceProductionContext context, EquatableImmutableArray<RequestHandler> requestHandlers)
16+
{
17+
context.CancellationToken.ThrowIfCancellationRequested();
18+
19+
var nonStaticClassNames = GetDistinctNonStaticClassNames(requestHandlers);
20+
var source = GetAddEndpointHandlersStringBuilder(nonStaticClassNames);
21+
source.AppendLine(FileHeader);
22+
23+
source.AppendLine();
24+
25+
source.AppendLine("using Microsoft.Extensions.DependencyInjection;");
26+
source.AppendLine("using Microsoft.Extensions.DependencyInjection.Extensions;");
27+
source.AppendLine();
28+
29+
source.Append("namespace ");
30+
source.Append(RoutingNamespace);
31+
source.AppendLine(";");
32+
33+
source.AppendLine();
34+
35+
source.Append("internal static class ");
36+
source.Append(AddEndpointHandlersClassName);
37+
source.AppendLine();
38+
39+
source.AppendLine("{");
40+
41+
source.Append(" internal static void ");
42+
source.Append(AddEndpointHandlersMethodName);
43+
source.AppendLine("(this IServiceCollection services)");
44+
45+
source.AppendLine(" {");
46+
47+
foreach (var className in nonStaticClassNames)
48+
{
49+
source.Append(" services.TryAddScoped<");
50+
source.Append(className);
51+
source.Append(">();");
52+
source.AppendLine();
53+
}
54+
55+
source.AppendLine("""
56+
}
57+
}
58+
"""
59+
);
60+
61+
var sourceText = StringBuilderPool.ToStringAndReturn(source);
62+
context.AddSource(AddEndpointHandlersMethodHint, SourceText.From(sourceText, Encoding.UTF8));
63+
}
64+
65+
private static List<string> GetDistinctNonStaticClassNames(EquatableImmutableArray<RequestHandler> requestHandlers)
66+
{
67+
var classNames = new List<string>();
68+
if (requestHandlers.Count == 0)
69+
return classNames;
70+
71+
var seen = new HashSet<string>(StringComparer.Ordinal);
72+
for (var index = 0; index < requestHandlers.Count; index++)
73+
{
74+
var requestHandler = requestHandlers[index];
75+
if (requestHandler.Class.IsStatic)
76+
continue;
77+
78+
var className = requestHandler.Class.Name;
79+
if (seen.Add(className))
80+
classNames.Add(className);
81+
}
82+
83+
return classNames;
84+
}
85+
86+
private static StringBuilder GetAddEndpointHandlersStringBuilder(List<string> nonStaticClassNames)
87+
{
88+
var estimate = 512L;
89+
for (var index = 0; index < nonStaticClassNames.Count; index++)
90+
{
91+
var className = nonStaticClassNames[index];
92+
estimate += 36 + className.Length;
93+
}
94+
95+
estimate += Math.Max(256, nonStaticClassNames.Count * 12);
96+
estimate = (long)(estimate * 1.10);
97+
98+
if (estimate < 512)
99+
estimate = 512;
100+
else if (estimate > int.MaxValue)
101+
estimate = int.MaxValue;
102+
103+
return StringBuilderPool.Get((int)estimate);
104+
}
105+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace GeneratedEndpoints.Common;
2+
3+
internal readonly record struct AcceptsMetadata(
4+
string RequestType,
5+
string ContentType,
6+
EquatableImmutableArray<string>? AdditionalContentTypes,
7+
bool IsOptional
8+
);
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace GeneratedEndpoints.Common;
4+
5+
internal static class AttributeDataExtensions
6+
{
7+
public static string? GetConstructorStringValue(this AttributeData attribute, int position = 0)
8+
{
9+
if (attribute.ConstructorArguments.Length > position)
10+
return (attribute.ConstructorArguments[position].Value as string).NormalizeOptionalString();
11+
12+
return null;
13+
}
14+
15+
public static EquatableImmutableArray<string>? GetConstructorStringArray(this AttributeData attribute, int position = 0)
16+
{
17+
if (attribute.ConstructorArguments.Length <= position)
18+
return null;
19+
20+
var arg = attribute.ConstructorArguments[position];
21+
if (arg.Kind == TypedConstantKind.Array)
22+
{
23+
if (arg.Values.Length == 0)
24+
return null;
25+
26+
List<string>? normalized = null;
27+
foreach (var value in arg.Values)
28+
{
29+
if (value.Value is not string stringValue)
30+
continue;
31+
32+
var trimmed = stringValue.NormalizeOptionalString();
33+
if (trimmed is not { Length: > 0 })
34+
continue;
35+
36+
normalized ??= new List<string>(arg.Values.Length);
37+
normalized.Add(trimmed);
38+
}
39+
40+
if (normalized is { Count: > 0 })
41+
return normalized.ToEquatableImmutableArray();
42+
}
43+
else if (arg.Value is string singleHost && !string.IsNullOrWhiteSpace(singleHost))
44+
{
45+
return new[] { singleHost.Trim() }.ToEquatableImmutableArray();
46+
}
47+
48+
return null;
49+
}
50+
51+
public static int? GetConstructorIntValue(this AttributeData attribute, int position = 0)
52+
{
53+
if (attribute.ConstructorArguments.Length > position && attribute.ConstructorArguments[position].Value is int value)
54+
return value;
55+
56+
return null;
57+
}
58+
59+
public static ITypeSymbol? GetNamedTypeSymbol(this AttributeData attribute, string namedParameter)
60+
{
61+
foreach (var namedArg in attribute.NamedArguments)
62+
{
63+
if (namedArg.Key == namedParameter && namedArg.Value.Value is ITypeSymbol typeSymbol)
64+
return typeSymbol;
65+
}
66+
67+
return null;
68+
}
69+
70+
public static bool GetNamedBoolValue(this AttributeData attribute, string namedParameter, bool defaultValue = false)
71+
{
72+
foreach (var namedArg in attribute.NamedArguments)
73+
{
74+
if (namedArg.Key == namedParameter && namedArg.Value.Value is bool boolValue)
75+
return boolValue;
76+
}
77+
78+
return defaultValue;
79+
}
80+
81+
public static string? GetNamedStringValue(this AttributeData attribute, string namedParameter)
82+
{
83+
foreach (var namedArg in attribute.NamedArguments)
84+
{
85+
if (namedArg.Key == namedParameter && namedArg.Value.Value is string stringValue)
86+
return stringValue.NormalizeOptionalString();
87+
}
88+
89+
return null;
90+
}
91+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace GeneratedEndpoints.Common;
4+
5+
internal static class AttributeSymbolMatcher
6+
{
7+
public static bool IsAttribute(INamedTypeSymbol attributeClass, string attributeName, string[] namespaceParts)
8+
{
9+
var definition = attributeClass.OriginalDefinition;
10+
return definition.Name == attributeName && IsInNamespace(definition.ContainingNamespace, namespaceParts);
11+
}
12+
13+
public static bool IsInNamespace(INamespaceSymbol? namespaceSymbol, string[] namespaceParts)
14+
{
15+
for (var i = namespaceParts.Length - 1; i >= 0; i--)
16+
{
17+
if (namespaceSymbol is null || namespaceSymbol.Name != namespaceParts[i])
18+
return false;
19+
20+
namespaceSymbol = namespaceSymbol.ContainingNamespace;
21+
}
22+
23+
return namespaceSymbol is null || namespaceSymbol.IsGlobalNamespace;
24+
}
25+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace GeneratedEndpoints.Common;
2+
3+
internal enum BindingSource
4+
{
5+
None = 0,
6+
FromRoute = 1,
7+
FromQuery = 2,
8+
FromHeader = 3,
9+
FromBody = 4,
10+
FromForm = 5,
11+
FromServices = 6,
12+
FromKeyedServices = 7,
13+
AsParameters = 8,
14+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace GeneratedEndpoints.Common;
2+
3+
internal readonly record struct ConfigureMethodDetails(bool HasConfigureMethod, bool ConfigureMethodAcceptsServiceProvider);

0 commit comments

Comments
 (0)