Skip to content

Commit fab9e0e

Browse files
Refactor RequestDelegateGenerator to use partial class pattern (#64755)
* Initial plan * Refactor RequestDelegateGenerator to use partial class pattern like ValidationsGenerator and OpenApiGenerator Co-authored-by: captainsafia <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: captainsafia <[email protected]>
1 parent 7ecd485 commit fab9e0e

File tree

3 files changed

+294
-247
lines changed

3 files changed

+294
-247
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
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.Collections.Immutable;
5+
using System.Globalization;
6+
using System.IO;
7+
using System.Linq;
8+
using Microsoft.AspNetCore.Analyzers.Infrastructure;
9+
using Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel;
10+
using Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel.Emitters;
11+
using Microsoft.CodeAnalysis;
12+
using Microsoft.CodeAnalysis.CSharp;
13+
14+
namespace Microsoft.AspNetCore.Http.RequestDelegateGenerator;
15+
16+
public sealed partial class RequestDelegateGenerator : IIncrementalGenerator
17+
{
18+
internal static string EmitInterceptorDefinition((Endpoint Source, int Index, ImmutableArray<InterceptableLocation> Elements) endpointWithLocations)
19+
{
20+
var endpoint = endpointWithLocations.Source;
21+
using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
22+
using var codeWriter = new CodeWriter(stringWriter, baseIndent: 2);
23+
foreach (var location in endpointWithLocations.Elements)
24+
{
25+
#pragma warning disable RSEXPERIMENTAL002 // Experimental interceptable location API
26+
codeWriter.WriteLine(location.GetInterceptsLocationAttributeSyntax());
27+
#pragma warning restore RSEXPERIMENTAL002
28+
}
29+
codeWriter.WriteLine($"internal static RouteHandlerBuilder {endpoint.HttpMethod}{endpointWithLocations.Index}(");
30+
codeWriter.Indent++;
31+
codeWriter.WriteLine("this IEndpointRouteBuilder endpoints,");
32+
// MapFallback overloads that only take a delegate do not need a pattern argument
33+
if (endpoint.HttpMethod != "MapFallback" || endpoint.Operation.Arguments.Length != 2)
34+
{
35+
codeWriter.WriteLine(@"[StringSyntax(""Route"")] string pattern,");
36+
}
37+
// MapMethods overloads define an additional `httpMethods` parameter
38+
if (endpoint.HttpMethod == "MapMethods")
39+
{
40+
codeWriter.WriteLine("IEnumerable<string> httpMethods,");
41+
}
42+
codeWriter.WriteLine("Delegate handler)");
43+
codeWriter.Indent--;
44+
codeWriter.StartBlock();
45+
codeWriter.WriteLine("MetadataPopulator populateMetadata = (methodInfo, options) =>");
46+
codeWriter.StartBlock();
47+
codeWriter.WriteLine(@"Debug.Assert(options != null, ""RequestDelegateFactoryOptions not found."");");
48+
codeWriter.WriteLine(@"Debug.Assert(options.EndpointBuilder != null, ""EndpointBuilder not found."");");
49+
codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new {RequestDelegateGeneratorSources.GeneratedCodeConstructor});");
50+
endpoint.EmitEndpointMetadataPopulation(codeWriter);
51+
codeWriter.WriteLine("return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };");
52+
codeWriter.EndBlockWithSemicolon();
53+
codeWriter.WriteLine("RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) =>");
54+
codeWriter.StartBlock();
55+
codeWriter.WriteLine(@"Debug.Assert(options != null, ""RequestDelegateFactoryOptions not found."");");
56+
codeWriter.WriteLine(@"Debug.Assert(options.EndpointBuilder != null, ""EndpointBuilder not found."");");
57+
codeWriter.WriteLine(@"Debug.Assert(options.EndpointBuilder.ApplicationServices != null, ""ApplicationServices not found."");");
58+
codeWriter.WriteLine(@"Debug.Assert(options.EndpointBuilder.FilterFactories != null, ""FilterFactories not found."");");
59+
codeWriter.WriteLine($"var handler = Cast(del, {endpoint.EmitHandlerDelegateType()} => throw null!);");
60+
codeWriter.WriteLine("EndpointFilterDelegate? filteredInvocation = null;");
61+
codeWriter.WriteLine("var serviceProvider = options.ServiceProvider ?? options.EndpointBuilder.ApplicationServices;");
62+
endpoint.EmitLoggingPreamble(codeWriter);
63+
endpoint.EmitJsonPreparation(codeWriter);
64+
endpoint.EmitRouteOrQueryResolver(codeWriter);
65+
endpoint.EmitJsonBodyOrServiceResolver(codeWriter);
66+
if (endpoint.NeedsParameterArray)
67+
{
68+
codeWriter.WriteLine("var parameters = del.Method.GetParameters();");
69+
}
70+
codeWriter.WriteLineNoTabs(string.Empty);
71+
codeWriter.WriteLine("if (options.EndpointBuilder.FilterFactories.Count > 0)");
72+
codeWriter.StartBlock();
73+
codeWriter.WriteLine(endpoint.Response?.IsAwaitable == true
74+
? "filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(async ic =>"
75+
: "filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>");
76+
codeWriter.StartBlock();
77+
codeWriter.WriteLine("if (ic.HttpContext.Response.StatusCode == 400)");
78+
codeWriter.StartBlock();
79+
codeWriter.WriteLine(endpoint.Response?.IsAwaitable == true
80+
? "return (object?)Results.Empty;"
81+
: "return ValueTask.FromResult<object?>(Results.Empty);");
82+
codeWriter.EndBlock();
83+
endpoint.EmitFilteredInvocation(codeWriter);
84+
codeWriter.EndBlockWithComma();
85+
codeWriter.WriteLine("options.EndpointBuilder,");
86+
codeWriter.WriteLine("handler.Method);");
87+
codeWriter.EndBlock();
88+
codeWriter.WriteLineNoTabs(string.Empty);
89+
endpoint.EmitRequestHandler(codeWriter);
90+
codeWriter.WriteLineNoTabs(string.Empty);
91+
endpoint.EmitFilteredRequestHandler(codeWriter);
92+
codeWriter.WriteLineNoTabs(string.Empty);
93+
codeWriter.WriteLine("RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;");
94+
codeWriter.WriteLine("var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;");
95+
codeWriter.WriteLine("return new RequestDelegateResult(targetDelegate, metadata);");
96+
codeWriter.EndBlockWithSemicolon();
97+
codeWriter.WriteLine($"var castHandler = Cast(handler, {endpoint.EmitHandlerDelegateType()} => throw null!);");
98+
codeWriter.WriteLine("return MapCore(");
99+
codeWriter.Indent++;
100+
codeWriter.WriteLine("endpoints,");
101+
// For `MapFallback` overloads that only take a delegate, provide the assumed default
102+
// Otherwise, pass the pattern provided from the MapX invocation
103+
if (endpoint.HttpMethod != "MapFallback" && endpoint.Operation.Arguments.Length != 2)
104+
{
105+
codeWriter.WriteLine("pattern,");
106+
}
107+
else
108+
{
109+
codeWriter.WriteLine($"{SymbolDisplay.FormatLiteral("{*path:nonfile}", true)},");
110+
}
111+
codeWriter.WriteLine("handler,");
112+
codeWriter.WriteLine($"{endpoint.EmitVerb()},");
113+
codeWriter.WriteLine("populateMetadata,");
114+
codeWriter.WriteLine("createRequestDelegate,");
115+
codeWriter.WriteLine("castHandler.Method);");
116+
codeWriter.Indent--;
117+
codeWriter.EndBlock();
118+
return stringWriter.ToString();
119+
}
120+
121+
internal static ImmutableHashSet<string> EmitHttpVerbs(ImmutableArray<Endpoint> endpoints)
122+
{
123+
return endpoints
124+
.Distinct(EndpointHttpMethodComparer.Instance)
125+
.Select(endpoint => endpoint.EmitterContext.HttpMethod!)
126+
.Where(verb => verb is not null)
127+
.ToImmutableHashSet();
128+
}
129+
130+
internal static string EmitEndpointHelpers(ImmutableArray<Endpoint> endpoints)
131+
{
132+
var hasJsonBodyOrService = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonBodyOrService);
133+
var hasJsonBodyOrQuery = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonBodyOrQuery);
134+
var hasJsonBody = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonBody);
135+
var hasFormBody = endpoints.Any(endpoint => endpoint.EmitterContext.HasFormBody);
136+
var hasRouteOrQuery = endpoints.Any(endpoint => endpoint.EmitterContext.HasRouteOrQuery);
137+
var hasBindAsync = endpoints.Any(endpoint => endpoint.EmitterContext.HasBindAsync);
138+
var hasParsable = endpoints.Any(endpoint => endpoint.EmitterContext.HasParsable);
139+
var hasEndpointMetadataProvider = endpoints.Any(endpoint => endpoint.EmitterContext.HasEndpointMetadataProvider);
140+
var hasEndpointParameterMetadataProvider = endpoints.Any(endpoint => endpoint.EmitterContext.HasEndpointParameterMetadataProvider);
141+
var hasIResult = endpoints.Any(endpoint => endpoint.Response?.IsIResult == true);
142+
143+
using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
144+
using var codeWriter = new CodeWriter(stringWriter, baseIndent: 0);
145+
146+
if (hasRouteOrQuery)
147+
{
148+
codeWriter.WriteLine(RequestDelegateGeneratorSources.ResolveFromRouteOrQueryMethod);
149+
}
150+
151+
if (hasJsonBody || hasJsonBodyOrService || hasJsonBodyOrQuery)
152+
{
153+
codeWriter.WriteLine(RequestDelegateGeneratorSources.TryResolveBodyAsyncMethod);
154+
}
155+
156+
if (hasFormBody)
157+
{
158+
codeWriter.WriteLine(RequestDelegateGeneratorSources.TryResolveFormAsyncMethod);
159+
}
160+
161+
if (hasBindAsync)
162+
{
163+
codeWriter.WriteLine(RequestDelegateGeneratorSources.BindAsyncMethod);
164+
}
165+
166+
if (hasJsonBodyOrService)
167+
{
168+
codeWriter.WriteLine(RequestDelegateGeneratorSources.ResolveJsonBodyOrServiceMethod);
169+
}
170+
171+
if (hasParsable)
172+
{
173+
codeWriter.WriteLine(RequestDelegateGeneratorSources.TryParseExplicitMethod);
174+
}
175+
176+
if (hasIResult)
177+
{
178+
codeWriter.WriteLine(RequestDelegateGeneratorSources.ExecuteAsyncExplicitMethod);
179+
}
180+
181+
if (hasEndpointMetadataProvider)
182+
{
183+
codeWriter.WriteLine(RequestDelegateGeneratorSources.PopulateEndpointMetadataMethod);
184+
}
185+
186+
if (hasEndpointParameterMetadataProvider)
187+
{
188+
codeWriter.WriteLine(RequestDelegateGeneratorSources.PopulateEndpointParameterMetadataMethod);
189+
}
190+
191+
return stringWriter.ToString();
192+
}
193+
194+
internal static string EmitHelperTypes(ImmutableArray<Endpoint> endpoints)
195+
{
196+
var hasFormBody = endpoints.Any(endpoint => endpoint.EmitterContext.HasFormBody);
197+
var hasJsonBody = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonBody || endpoint.EmitterContext.HasJsonBodyOrService || endpoint.EmitterContext.HasJsonBodyOrQuery);
198+
var hasResponseMetadata = endpoints.Any(endpoint => endpoint.EmitterContext.HasResponseMetadata);
199+
var requiresPropertyAsParameterInfo = endpoints.Any(endpoint => endpoint.EmitterContext.RequiresPropertyAsParameterInfo);
200+
var requiresParameterBindingMetadataClass = endpoints.Any(endpoint => endpoint.EmitterContext.RequiresParameterBindingMetadataClass);
201+
202+
using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
203+
using var codeWriter = new CodeWriter(stringWriter, baseIndent: 0);
204+
205+
if (hasFormBody)
206+
{
207+
codeWriter.WriteLine(RequestDelegateGeneratorSources.AntiforgeryMetadataClass);
208+
}
209+
210+
if (hasJsonBody || hasResponseMetadata)
211+
{
212+
codeWriter.WriteLine(RequestDelegateGeneratorSources.DisableCookieRedirectMetadataClass);
213+
}
214+
215+
if (hasFormBody || hasJsonBody || hasResponseMetadata)
216+
{
217+
codeWriter.WriteLine(RequestDelegateGeneratorSources.ContentTypeConstantsType);
218+
}
219+
220+
if (requiresPropertyAsParameterInfo)
221+
{
222+
codeWriter.WriteLine(RequestDelegateGeneratorSources.PropertyAsParameterInfoClass);
223+
}
224+
225+
if (requiresParameterBindingMetadataClass)
226+
{
227+
codeWriter.WriteLine(RequestDelegateGeneratorSources.ParameterBindingMetadataClass);
228+
}
229+
230+
return stringWriter.ToString();
231+
}
232+
233+
internal static void Emit(
234+
SourceProductionContext context,
235+
ImmutableArray<string> endpointsCode,
236+
string helperMethods,
237+
ImmutableHashSet<string> httpVerbs,
238+
string helperTypes)
239+
{
240+
if (endpointsCode.IsDefaultOrEmpty)
241+
{
242+
return;
243+
}
244+
using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
245+
using var codeWriter = new CodeWriter(stringWriter, baseIndent: 0);
246+
foreach (var endpoint in endpointsCode)
247+
{
248+
codeWriter.WriteLine(endpoint);
249+
}
250+
var code = RequestDelegateGeneratorSources.GetGeneratedRouteBuilderExtensionsSource(
251+
endpoints: stringWriter.ToString(),
252+
helperMethods: helperMethods ?? string.Empty,
253+
helperTypes: helperTypes ?? string.Empty,
254+
verbs: httpVerbs);
255+
256+
context.AddSource("GeneratedRouteBuilderExtensions.g.cs", code);
257+
}
258+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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.Threading;
6+
using Microsoft.AspNetCore.Analyzers.Infrastructure;
7+
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
8+
using Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel;
9+
using Microsoft.CodeAnalysis;
10+
11+
namespace Microsoft.AspNetCore.Http.RequestDelegateGenerator;
12+
13+
public sealed partial class RequestDelegateGenerator : IIncrementalGenerator
14+
{
15+
internal static bool IsEndpointInvocation(SyntaxNode node, CancellationToken cancellationToken)
16+
=> node.TryGetMapMethodName(out var method) && InvocationOperationExtensions.KnownMethods.Contains(method);
17+
18+
internal static Endpoint? TransformEndpoint(GeneratorSyntaxContext context, CancellationToken cancellationToken)
19+
{
20+
var operation = context.SemanticModel.GetOperation(context.Node, cancellationToken);
21+
var wellKnownTypes = WellKnownTypes.GetOrCreate(context.SemanticModel.Compilation);
22+
if (operation.IsValidOperation(wellKnownTypes, out var invocationOperation))
23+
{
24+
return new Endpoint(invocationOperation, wellKnownTypes, context.SemanticModel);
25+
}
26+
return null;
27+
}
28+
}

0 commit comments

Comments
 (0)