Skip to content

Commit 99a1d2c

Browse files
authored
Add LookupName to EndpointParameter. (#47272)
Separates the ```Name``` property on ```EndpointParameter``` into two properties, ```SymbolName``` and ```LookupName```. The ```SymbolName``` field is used for actual C# identifiers in code generation and is taken directly from the parameter name on the handler delegate. The ```LookupName``` field can either be the same as ```SymbolName```, or when an overriding attribute is provided (e.g. ```[FromQuery(Name="blah")]```), the overridden value is used. The name has some escaping provided to it since it's a string literal being injected into the compiled code.
1 parent 2a7dc90 commit 99a1d2c

11 files changed

+202
-57
lines changed

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static void EmitRouteOrQueryResolver(this Endpoint endpoint, CodeWriter c
5454
{
5555
if (parameter.Source == EndpointParameterSource.RouteOrQuery)
5656
{
57-
var parameterName = parameter.Name;
57+
var parameterName = parameter.SymbolName;
5858
codeWriter.Write($@"var {parameterName}_RouteOrQueryResolver = ");
5959
codeWriter.WriteLine($@"GeneratedRouteBuilderExtensionsCore.ResolveFromRouteOrQuery(""{parameterName}"", options?.RouteParameterNames);");
6060
}
@@ -73,7 +73,7 @@ public static void EmitJsonBodyOrServiceResolver(this Endpoint endpoint, CodeWri
7373
codeWriter.WriteLine("var serviceProviderIsService = options?.ServiceProvider?.GetService<IServiceProviderIsService>();");
7474
serviceProviderEmitted = true;
7575
}
76-
codeWriter.Write($@"var {parameter.Name}_JsonBodyOrServiceResolver = ");
76+
codeWriter.Write($@"var {parameter.SymbolName}_JsonBodyOrServiceResolver = ");
7777
codeWriter.WriteLine($"ResolveJsonBodyOrService<{parameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}>(serviceProviderIsService);");
7878
}
7979
}

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ internal static void EmitQueryOrHeaderParameterPreparation(this EndpointParamete
1919
codeWriter.WriteLine(endpointParameter.EmitParameterDiagnosticComment());
2020

2121
var assigningCode = endpointParameter.Source is EndpointParameterSource.Header
22-
? $"httpContext.Request.Headers[\"{endpointParameter.Name}\"]"
23-
: $"httpContext.Request.Query[\"{endpointParameter.Name}\"]";
22+
? $"httpContext.Request.Headers[\"{endpointParameter.LookupName}\"]"
23+
: $"httpContext.Request.Query[\"{endpointParameter.LookupName}\"]";
2424
codeWriter.WriteLine($"var {endpointParameter.EmitAssigningCodeResult()} = {assigningCode};");
2525

2626
// If we are not optional, then at this point we can just assign the string value to the handler argument,
@@ -94,12 +94,12 @@ internal static void EmitRouteParameterPreparation(this EndpointParameter endpoi
9494

9595
// Throw an exception of if the route parameter name that was specific in the `FromRoute`
9696
// attribute or in the parameter name does not appear in the actual route.
97-
codeWriter.WriteLine($"""if (options?.RouteParameterNames?.Contains("{endpointParameter.Name}", StringComparer.OrdinalIgnoreCase) != true)""");
97+
codeWriter.WriteLine($"""if (options?.RouteParameterNames?.Contains("{endpointParameter.LookupName}", StringComparer.OrdinalIgnoreCase) != true)""");
9898
codeWriter.StartBlock();
99-
codeWriter.WriteLine($$"""throw new InvalidOperationException($"'{{endpointParameter.Name}}' is not a route parameter.");""");
99+
codeWriter.WriteLine($$"""throw new InvalidOperationException($"'{{endpointParameter.LookupName}}' is not a route parameter.");""");
100100
codeWriter.EndBlock();
101101

102-
var assigningCode = $"(string?)httpContext.Request.RouteValues[\"{endpointParameter.Name}\"]";
102+
var assigningCode = $"(string?)httpContext.Request.RouteValues[\"{endpointParameter.LookupName}\"]";
103103
codeWriter.WriteLine($"var {endpointParameter.EmitAssigningCodeResult()} = {assigningCode};");
104104

105105
if (!endpointParameter.IsOptional)
@@ -118,7 +118,7 @@ internal static void EmitRouteOrQueryParameterPreparation(this EndpointParameter
118118
{
119119
codeWriter.WriteLine(endpointParameter.EmitParameterDiagnosticComment());
120120

121-
var parameterName = endpointParameter.Name;
121+
var parameterName = endpointParameter.SymbolName;
122122
codeWriter.WriteLine($"var {endpointParameter.EmitAssigningCodeResult()} = {parameterName}_RouteOrQueryResolver(httpContext);");
123123

124124
if (endpointParameter.IsArray)
@@ -149,7 +149,7 @@ internal static void EmitJsonBodyParameterPreparationString(this EndpointParamet
149149
// Invoke TryResolveBody method to parse JSON and set
150150
// status codes on exceptions.
151151
var assigningCode = $"await GeneratedRouteBuilderExtensionsCore.TryResolveBodyAsync<{endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}>(httpContext, {(endpointParameter.IsOptional ? "true" : "false")})";
152-
var resolveBodyResult = $"{endpointParameter.Name}_resolveBodyResult";
152+
var resolveBodyResult = $"{endpointParameter.SymbolName}_resolveBodyResult";
153153
codeWriter.WriteLine($"var {resolveBodyResult} = {assigningCode};");
154154
codeWriter.WriteLine($"var {endpointParameter.EmitHandlerArgument()} = {resolveBodyResult}.Item2;");
155155

@@ -170,8 +170,8 @@ internal static void EmitJsonBodyOrServiceParameterPreparationString(this Endpoi
170170
// Invoke ResolveJsonBodyOrService method to resolve the
171171
// type from DI if it exists. Otherwise, resolve the parameter
172172
// as a body parameter.
173-
var assigningCode = $"await {endpointParameter.Name}_JsonBodyOrServiceResolver(httpContext, {(endpointParameter.IsOptional ? "true" : "false")})";
174-
var resolveJsonBodyOrServiceResult = $"{endpointParameter.Name}_resolveJsonBodyOrServiceResult";
173+
var assigningCode = $"await {endpointParameter.SymbolName}_JsonBodyOrServiceResolver(httpContext, {(endpointParameter.IsOptional ? "true" : "false")})";
174+
var resolveJsonBodyOrServiceResult = $"{endpointParameter.SymbolName}_resolveJsonBodyOrServiceResult";
175175
codeWriter.WriteLine($"var {resolveJsonBodyOrServiceResult} = {assigningCode};");
176176
codeWriter.WriteLine($"var {endpointParameter.EmitHandlerArgument()} = {resolveJsonBodyOrServiceResult}.Item2;");
177177

@@ -238,12 +238,12 @@ internal static void EmitServiceParameterPreparation(this EndpointParameter endp
238238
codeWriter.WriteLine($"var {endpointParameter.EmitHandlerArgument()} = {assigningCode};");
239239
}
240240

241-
private static string EmitParameterDiagnosticComment(this EndpointParameter endpointParameter) => $"// Endpoint Parameter: {endpointParameter.Name} (Type = {endpointParameter.Type}, IsOptional = {endpointParameter.IsOptional}, IsParsable = {endpointParameter.IsParsable}, IsArray = {endpointParameter.IsArray}, Source = {endpointParameter.Source})";
242-
private static string EmitHandlerArgument(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_local";
243-
private static string EmitTempArgument(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_temp";
241+
private static string EmitParameterDiagnosticComment(this EndpointParameter endpointParameter) => $"// Endpoint Parameter: {endpointParameter.SymbolName} (Type = {endpointParameter.Type}, IsOptional = {endpointParameter.IsOptional}, IsParsable = {endpointParameter.IsParsable}, IsArray = {endpointParameter.IsArray}, Source = {endpointParameter.Source})";
242+
private static string EmitHandlerArgument(this EndpointParameter endpointParameter) => $"{endpointParameter.SymbolName}_local";
243+
private static string EmitTempArgument(this EndpointParameter endpointParameter) => $"{endpointParameter.SymbolName}_temp";
244244

245-
private static string EmitParsedTempArgument(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_parsed_temp";
246-
private static string EmitAssigningCodeResult(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_raw";
245+
private static string EmitParsedTempArgument(this EndpointParameter endpointParameter) => $"{endpointParameter.SymbolName}_parsed_temp";
246+
private static string EmitAssigningCodeResult(this EndpointParameter endpointParameter) => $"{endpointParameter.SymbolName}_raw";
247247

248248
public static string EmitArgument(this EndpointParameter endpointParameter) => endpointParameter.Source switch
249249
{

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public Endpoint(IInvocationOperation operation, WellKnownTypes wellKnownTypes, S
7777
Diagnostics.Add(Diagnostic.Create(
7878
DiagnosticDescriptors.UnableToResolveParameterDescriptor,
7979
Operation.Syntax.GetLocation(),
80-
parameter.Name));
80+
parameter.SymbolName));
8181
break;
8282
}
8383

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Diagnostics.CodeAnalysis;
6+
using System.Text;
67
using Microsoft.AspNetCore.Analyzers.Infrastructure;
78
using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
89
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
@@ -16,7 +17,8 @@ internal class EndpointParameter
1617
public EndpointParameter(Endpoint endpoint, IParameterSymbol parameter, WellKnownTypes wellKnownTypes)
1718
{
1819
Type = parameter.Type;
19-
Name = parameter.Name;
20+
SymbolName = parameter.Name;
21+
LookupName = parameter.Name; // Default lookup name is same as parameter name (which is a valid C# identifier).
2022
Ordinal = parameter.Ordinal;
2123
Source = EndpointParameterSource.Unknown;
2224
IsOptional = parameter.IsOptional();
@@ -26,21 +28,21 @@ public EndpointParameter(Endpoint endpoint, IParameterSymbol parameter, WellKnow
2628
if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata), out var fromRouteAttribute))
2729
{
2830
Source = EndpointParameterSource.Route;
29-
Name = GetParameterName(fromRouteAttribute, parameter.Name);
31+
LookupName = GetEscapedParameterName(fromRouteAttribute, parameter.Name);
3032
IsParsable = TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter);
3133
ParsingBlockEmitter = parsingBlockEmitter;
3234
}
3335
else if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata), out var fromQueryAttribute))
3436
{
3537
Source = EndpointParameterSource.Query;
36-
Name = GetParameterName(fromQueryAttribute, parameter.Name);
38+
LookupName = GetEscapedParameterName(fromQueryAttribute, parameter.Name);
3739
IsParsable = TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter);
3840
ParsingBlockEmitter = parsingBlockEmitter;
3941
}
4042
else if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromHeaderMetadata), out var fromHeaderAttribute))
4143
{
4244
Source = EndpointParameterSource.Header;
43-
Name = GetParameterName(fromHeaderAttribute, parameter.Name);
45+
LookupName = GetEscapedParameterName(fromHeaderAttribute, parameter.Name);
4446
IsParsable = TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter);
4547
ParsingBlockEmitter = parsingBlockEmitter;
4648
}
@@ -130,7 +132,8 @@ private static bool ShouldDisableInferredBodyParameters(string httpMethod)
130132
public ITypeSymbol Type { get; }
131133
public ITypeSymbol ElementType { get; }
132134

133-
public string Name { get; }
135+
public string SymbolName { get; }
136+
public string LookupName { get; }
134137
public int Ordinal { get; }
135138
public bool IsOptional { get; }
136139
public bool IsArray { get; set; }
@@ -327,15 +330,79 @@ private static bool TryGetExplicitFromJsonBody(IParameterSymbol parameter,
327330
return true;
328331
}
329332

330-
private static string GetParameterName(AttributeData attribute, string parameterName) =>
331-
attribute.TryGetNamedArgumentValue<string>("Name", out var fromSourceName)
332-
? (fromSourceName ?? parameterName)
333-
: parameterName;
333+
private static string GetEscapedParameterName(AttributeData attribute, string parameterName)
334+
{
335+
if (attribute.TryGetNamedArgumentValue<string>("Name", out var fromSourceName) && fromSourceName is not null)
336+
{
337+
return ConvertEndOfLineAndQuotationCharactersToEscapeForm(fromSourceName);
338+
}
339+
else
340+
{
341+
return parameterName;
342+
}
343+
}
344+
345+
// Lifted from:
346+
// https://github.com/dotnet/runtime/blob/dc5a6c8be1644915c14c4a464447b0d54e223a46/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs#L562
347+
private static string ConvertEndOfLineAndQuotationCharactersToEscapeForm(string s)
348+
{
349+
var index = 0;
350+
while (index < s.Length)
351+
{
352+
if (s[index] is '\n' or '\r' or '"' or '\\')
353+
{
354+
break;
355+
}
356+
index++;
357+
}
358+
359+
if (index >= s.Length)
360+
{
361+
return s;
362+
}
363+
364+
var sb = new StringBuilder(s.Length);
365+
sb.Append(s, 0, index);
366+
367+
while (index < s.Length)
368+
{
369+
switch (s[index])
370+
{
371+
case '\n':
372+
sb.Append('\\');
373+
sb.Append('n');
374+
break;
375+
376+
case '\r':
377+
sb.Append('\\');
378+
sb.Append('r');
379+
break;
380+
381+
case '"':
382+
sb.Append('\\');
383+
sb.Append('"');
384+
break;
385+
386+
case '\\':
387+
sb.Append('\\');
388+
sb.Append('\\');
389+
break;
390+
391+
default:
392+
sb.Append(s[index]);
393+
break;
394+
}
395+
396+
index++;
397+
}
398+
399+
return sb.ToString();
400+
}
334401

335402
public override bool Equals(object obj) =>
336403
obj is EndpointParameter other &&
337404
other.Source == Source &&
338-
other.Name == Name &&
405+
other.SymbolName == SymbolName &&
339406
other.Ordinal == Ordinal &&
340407
other.IsOptional == IsOptional &&
341408
SymbolEqualityComparer.Default.Equals(other.Type, Type);
@@ -347,7 +414,7 @@ obj is EndpointParameter other &&
347414
public override int GetHashCode()
348415
{
349416
var hashCode = new HashCode();
350-
hashCode.Add(Name);
417+
hashCode.Add(SymbolName);
351418
hashCode.Add(Type, SymbolEqualityComparer.Default);
352419
return hashCode.ToHashCode();
353420
}

src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_JsonBodyOrService_HandlesBothJsonAndService.generated.txt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated
9090

9191
private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
9292
{
93-
[(@"TestMapActions.cs", 24)] = (
93+
[(@"TestMapActions.cs", 25)] = (
9494
(methodInfo, options) =>
9595
{
9696
Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
97-
options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
97+
options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
9898
return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
9999
},
100100
(del, options, inferredMetadataResult) =>
@@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Http.Generated
113113
{
114114
return ValueTask.FromResult<object?>(Results.Empty);
115115
}
116-
return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>(0), ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.TestService>(1)));
116+
return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>(0)!, ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.TestService>(1)!));
117117
},
118118
options.EndpointBuilder,
119119
handler.Method);
@@ -122,14 +122,14 @@ namespace Microsoft.AspNetCore.Http.Generated
122122
async Task RequestHandler(HttpContext httpContext)
123123
{
124124
var wasParamCheckFailure = false;
125-
// Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = False, Source = JsonBodyOrService)
125+
// Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = False, IsArray = False, Source = JsonBodyOrService)
126126
var todo_resolveJsonBodyOrServiceResult = await todo_JsonBodyOrServiceResolver(httpContext, false);
127127
var todo_local = todo_resolveJsonBodyOrServiceResult.Item2;
128128
if (!todo_resolveJsonBodyOrServiceResult.Item1)
129129
{
130130
return;
131131
}
132-
// Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService, IsOptional = False, IsParsable = False, Source = JsonBodyOrService)
132+
// Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService, IsOptional = False, IsParsable = False, IsArray = False, Source = JsonBodyOrService)
133133
var svc_resolveJsonBodyOrServiceResult = await svc_JsonBodyOrServiceResolver(httpContext, false);
134134
var svc_local = svc_resolveJsonBodyOrServiceResult.Item2;
135135
if (!svc_resolveJsonBodyOrServiceResult.Item1)
@@ -150,14 +150,14 @@ namespace Microsoft.AspNetCore.Http.Generated
150150
async Task RequestHandlerFiltered(HttpContext httpContext)
151151
{
152152
var wasParamCheckFailure = false;
153-
// Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = False, Source = JsonBodyOrService)
153+
// Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = False, IsArray = False, Source = JsonBodyOrService)
154154
var todo_resolveJsonBodyOrServiceResult = await todo_JsonBodyOrServiceResolver(httpContext, false);
155155
var todo_local = todo_resolveJsonBodyOrServiceResult.Item2;
156156
if (!todo_resolveJsonBodyOrServiceResult.Item1)
157157
{
158158
return;
159159
}
160-
// Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService, IsOptional = False, IsParsable = False, Source = JsonBodyOrService)
160+
// Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService, IsOptional = False, IsParsable = False, IsArray = False, Source = JsonBodyOrService)
161161
var svc_resolveJsonBodyOrServiceResult = await svc_JsonBodyOrServiceResolver(httpContext, false);
162162
var svc_local = svc_resolveJsonBodyOrServiceResult.Item2;
163163
if (!svc_resolveJsonBodyOrServiceResult.Item1)

0 commit comments

Comments
 (0)