Skip to content

Commit b110c98

Browse files
authored
Add infrastructure for RequestDelegateGenerator (#45924)
* Add infrastructure for RequestDelegateGenerator * Opt for disabling generator via MSBuild * Address feedback from review * Don't emit sources if no endpoints found * Address feedback from review * Address feedback and consolidate emitted source * Support named MapAction method parameters * Fix up indentation * Rename project and add GeneratedCodeAttributes * Implement IEquatable on static model elements * Use records for model * Remove RoutePatternParameter list
1 parent c4cb55c commit b110c98

12 files changed

+1097
-5
lines changed

AspNetCore.sln

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1762,6 +1762,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server
17621762
EndProject
17631763
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests", "src\Servers\Kestrel\Transport.NamedPipes\test\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests.csproj", "{97C7D2A4-87E5-4A4A-A170-D736427D5C21}"
17641764
EndProject
1765+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Generators", "src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.Generators.csproj", "{4730F56D-24EF-4BB2-AA75-862E31205F3A}"
1766+
EndProject
17651767
Global
17661768
GlobalSection(SolutionConfigurationPlatforms) = preSolution
17671769
Debug|Any CPU = Debug|Any CPU
@@ -10571,6 +10573,22 @@ Global
1057110573
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x64.Build.0 = Release|Any CPU
1057210574
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x86.ActiveCfg = Release|Any CPU
1057310575
{97C7D2A4-87E5-4A4A-A170-D736427D5C21}.Release|x86.Build.0 = Release|Any CPU
10576+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10577+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
10578+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|arm64.ActiveCfg = Debug|Any CPU
10579+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|arm64.Build.0 = Debug|Any CPU
10580+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|x64.ActiveCfg = Debug|Any CPU
10581+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|x64.Build.0 = Debug|Any CPU
10582+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|x86.ActiveCfg = Debug|Any CPU
10583+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Debug|x86.Build.0 = Debug|Any CPU
10584+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
10585+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|Any CPU.Build.0 = Release|Any CPU
10586+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|arm64.ActiveCfg = Release|Any CPU
10587+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|arm64.Build.0 = Release|Any CPU
10588+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x64.ActiveCfg = Release|Any CPU
10589+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x64.Build.0 = Release|Any CPU
10590+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x86.ActiveCfg = Release|Any CPU
10591+
{4730F56D-24EF-4BB2-AA75-862E31205F3A}.Release|x86.Build.0 = Release|Any CPU
1057410592
EndGlobalSection
1057510593
GlobalSection(SolutionProperties) = preSolution
1057610594
HideSolutionNode = FALSE
@@ -11441,6 +11459,7 @@ Global
1144111459
{F057512B-55BF-4A8B-A027-A0505F8BA10C} = {4FDDC525-4E60-4CAF-83A3-261C5B43721F}
1144211460
{10173568-A65E-44E5-8C6F-4AA49D0577A1} = {F057512B-55BF-4A8B-A027-A0505F8BA10C}
1144311461
{97C7D2A4-87E5-4A4A-A170-D736427D5C21} = {F057512B-55BF-4A8B-A027-A0505F8BA10C}
11462+
{4730F56D-24EF-4BB2-AA75-862E31205F3A} = {225AEDCF-7162-4A86-AC74-06B84660B379}
1144411463
EndGlobalSection
1144511464
GlobalSection(ExtensibilityGlobals) = postSolution
1144611465
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

src/Framework/App.Ref/src/Microsoft.AspNetCore.App.Ref.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ This package is an internal implementation of the .NET Core SDK and is not meant
7777
Private="false"
7878
ReferenceOutputAssembly="false" />
7979

80+
<ProjectReference Include="$(RepoRoot)src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.Generators.csproj"
81+
Private="false"
82+
ReferenceOutputAssembly="false" />
83+
8084
<!-- Enforce build order. Targeting pack needs to bundle analyzers and information about the runtime. -->
8185
<ProjectReference Include="..\..\App.Runtime\src\Microsoft.AspNetCore.App.Runtime.csproj"
8286
Private="false"
@@ -176,6 +180,7 @@ This package is an internal implementation of the .NET Core SDK and is not meant
176180
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.App.Analyzers\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.App.Analyzers.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
177181
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.Components.Analyzers\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.Components.Analyzers.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
178182
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.App.CodeFixes\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.App.CodeFixes.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
183+
<_InitialRefPackContent Include="$(ArtifactsDir)bin\Microsoft.AspNetCore.Http.Generators\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.Http.Generators.dll" PackagePath="$(AnalyzersPackagePath)dotnet/cs/" />
179184

180185
<_InitialRefPackContent Include="@(AspNetCoreReferenceAssemblyPath)" PackagePath="$(RefAssemblyPackagePath)" />
181186
<_InitialRefPackContent Include="@(AspNetCoreReferenceDocXml)" PackagePath="$(RefAssemblyPackagePath)" />
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
6+
<IsPackable>false</IsPackable>
7+
<IsAnalyzersProject>true</IsAnalyzersProject>
8+
<AddPublicApiAnalyzers>false</AddPublicApiAnalyzers>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<Reference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
13+
<Reference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Http.Extensions.Tests" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<Compile Include="$(SharedSourceRoot)IsExternalInit.cs" LinkBase="Shared" />
26+
</ItemGroup>
27+
28+
</Project>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Linq;
5+
using System.Text;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
using Microsoft.CodeAnalysis.Operations;
9+
using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel;
10+
11+
namespace Microsoft.AspNetCore.Http.Generators;
12+
13+
[Generator]
14+
public sealed class RequestDelegateGenerator : IIncrementalGenerator
15+
{
16+
private static readonly string[] _knownMethods =
17+
{
18+
"MapGet",
19+
"MapPost",
20+
"MapPut",
21+
"MapDelete",
22+
"MapPatch",
23+
"Map",
24+
};
25+
26+
public void Initialize(IncrementalGeneratorInitializationContext context)
27+
{
28+
var endpoints = context.SyntaxProvider.CreateSyntaxProvider(
29+
predicate: (node, _) => node is InvocationExpressionSyntax
30+
{
31+
Expression: MemberAccessExpressionSyntax
32+
{
33+
Name: IdentifierNameSyntax
34+
{
35+
Identifier: { ValueText: var method }
36+
}
37+
},
38+
ArgumentList: { Arguments: { Count: 2 } args }
39+
} && _knownMethods.Contains(method),
40+
transform: (context, token) =>
41+
{
42+
var operation = context.SemanticModel.GetOperation(context.Node, token) as IInvocationOperation;
43+
return StaticRouteHandlerModelParser.GetEndpointFromOperation(operation);
44+
})
45+
.Where(endpoint => endpoint.Response.ResponseType == "string")
46+
.WithTrackingName("EndpointModel");
47+
48+
var thunks = endpoints.Select((endpoint, _) => $$"""
49+
[{{StaticRouteHandlerModelEmitter.EmitSourceKey(endpoint)}}] = (
50+
(del, builder) =>
51+
{
52+
builder.Metadata.Add(new SourceKey{{StaticRouteHandlerModelEmitter.EmitSourceKey(endpoint)}});
53+
},
54+
(del, builder) =>
55+
{
56+
var handler = ({{StaticRouteHandlerModelEmitter.EmitHandlerDelegateType(endpoint)}})del;
57+
EndpointFilterDelegate? filteredInvocation = null;
58+
59+
if (builder.FilterFactories.Count > 0)
60+
{
61+
filteredInvocation = BuildFilterDelegate(ic =>
62+
{
63+
if (ic.HttpContext.Response.StatusCode == 400)
64+
{
65+
return System.Threading.Tasks.ValueTask.FromResult<object?>(Results.Empty);
66+
}
67+
{{StaticRouteHandlerModelEmitter.EmitFilteredInvocation()}}
68+
},
69+
builder,
70+
handler.Method);
71+
}
72+
73+
{{StaticRouteHandlerModelEmitter.EmitRequestHandler()}}
74+
{{StaticRouteHandlerModelEmitter.EmitFilteredRequestHandler()}}
75+
76+
return filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
77+
}),
78+
""");
79+
80+
var stronglyTypedEndpointDefinitions = endpoints.Select((endpoint, _) => $$"""
81+
{{RequestDelegateGeneratorSources.GeneratedCodeAttribute}}
82+
internal static Microsoft.AspNetCore.Builder.RouteHandlerBuilder {{endpoint.HttpMethod}}(
83+
this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
84+
[System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
85+
{{StaticRouteHandlerModelEmitter.EmitHandlerDelegateType(endpoint)}} handler,
86+
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
87+
[System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
88+
{
89+
return MapCore(endpoints, pattern, handler, GetVerb, filePath, lineNumber);
90+
}
91+
""");
92+
93+
var thunksAndEndpoints = thunks.Collect().Combine(stronglyTypedEndpointDefinitions.Collect());
94+
95+
context.RegisterSourceOutput(thunksAndEndpoints, (context, sources) =>
96+
{
97+
var (thunks, endpoints) = sources;
98+
99+
var endpointsCode = new StringBuilder();
100+
var thunksCode = new StringBuilder();
101+
foreach (var endpoint in endpoints)
102+
{
103+
endpointsCode.AppendLine(endpoint);
104+
}
105+
foreach (var thunk in thunks)
106+
{
107+
thunksCode.AppendLine(thunk);
108+
}
109+
110+
var code = RequestDelegateGeneratorSources.GetGeneratedRouteBuilderExtensionsSource(
111+
genericThunks: string.Empty,
112+
thunks: thunksCode.ToString(),
113+
endpoints: endpointsCode.ToString());
114+
context.AddSource("GeneratedRouteBuilderExtensions.g.cs", code);
115+
});
116+
}
117+
}

0 commit comments

Comments
 (0)