@@ -18,33 +18,20 @@ namespace Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerM
18
18
19
19
internal class EndpointParameter
20
20
{
21
- public EndpointParameter ( Endpoint endpoint , IParameterSymbol parameter , WellKnownTypes wellKnownTypes )
21
+ public EndpointParameter ( Endpoint endpoint , IParameterSymbol parameter , WellKnownTypes wellKnownTypes ) : this ( endpoint , parameter . Type , parameter . Name , wellKnownTypes )
22
22
{
23
- Type = parameter . Type ;
24
- SymbolName = parameter . Name ;
25
- LookupName = parameter . Name ; // Default lookup name is same as parameter name (which is a valid C# identifier).
26
23
Ordinal = parameter . Ordinal ;
27
- Source = EndpointParameterSource . Unknown ;
28
24
IsOptional = parameter . IsOptional ( ) ;
29
25
DefaultValue = parameter . GetDefaultValueString ( ) ;
30
- IsArray = TryGetArrayElementType ( Type , out var elementType ) ;
31
- ElementType = elementType ;
32
- IsEndpointMetadataProvider = ImplementsIEndpointMetadataProvider ( parameter , wellKnownTypes ) ;
33
- IsEndpointParameterMetadataProvider = ImplementsIEndpointParameterMetadataProvider ( parameter , wellKnownTypes ) ;
34
26
ProcessEndpointParameterSource ( endpoint , parameter , parameter . GetAttributes ( ) , wellKnownTypes ) ;
35
27
}
36
28
37
- public EndpointParameter ( Endpoint endpoint , IPropertySymbol property , IParameterSymbol ? parameter , WellKnownTypes wellKnownTypes )
29
+ private EndpointParameter ( Endpoint endpoint , IPropertySymbol property , IParameterSymbol ? parameter , WellKnownTypes wellKnownTypes ) : this ( endpoint , property . Type , property . Name , wellKnownTypes )
38
30
{
39
- Type = property . Type ;
40
- SymbolName = property . Name ;
41
- LookupName = property . Name ;
42
31
Ordinal = parameter ? . Ordinal ?? 0 ;
43
- Source = EndpointParameterSource . Unknown ;
44
32
IsOptional = property . IsOptional ( ) || parameter ? . IsOptional ( ) == true ;
45
33
DefaultValue = parameter ? . GetDefaultValueString ( ) ?? "null" ;
46
- IsArray = TryGetArrayElementType ( Type , out var elementType ) ;
47
- ElementType = elementType ;
34
+ // Coalesce attributes on the property and attributes on the matching parameter
48
35
var attributeBuilder = ImmutableArray . CreateBuilder < AttributeData > ( ) ;
49
36
attributeBuilder . AddRange ( property . GetAttributes ( ) ) ;
50
37
if ( parameter is not null )
@@ -54,31 +41,45 @@ public EndpointParameter(Endpoint endpoint, IPropertySymbol property, IParameter
54
41
ProcessEndpointParameterSource ( endpoint , property , attributeBuilder . ToImmutable ( ) , wellKnownTypes ) ;
55
42
}
56
43
44
+ private EndpointParameter ( Endpoint endpoint , ITypeSymbol typeSymbol , string parameterName , WellKnownTypes wellKnownTypes )
45
+ {
46
+ Type = typeSymbol ;
47
+ SymbolName = parameterName ;
48
+ LookupName = parameterName ;
49
+ Source = EndpointParameterSource . Unknown ;
50
+ IsArray = TryGetArrayElementType ( typeSymbol , out var elementType ) ;
51
+ ElementType = elementType ;
52
+ IsEndpointMetadataProvider = ImplementsIEndpointMetadataProvider ( typeSymbol , wellKnownTypes ) ;
53
+ IsEndpointParameterMetadataProvider = ImplementsIEndpointParameterMetadataProvider ( typeSymbol , wellKnownTypes ) ;
54
+ endpoint . EmitterContext . HasEndpointParameterMetadataProvider = IsEndpointParameterMetadataProvider ;
55
+ }
56
+
57
57
private void ProcessEndpointParameterSource ( Endpoint endpoint , ISymbol symbol , ImmutableArray < AttributeData > attributes , WellKnownTypes wellKnownTypes )
58
58
{
59
- if ( attributes . HasAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata ) , out var fromRouteAttribute ) )
59
+ if ( attributes . TryGetAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata ) , out var fromRouteAttribute ) )
60
60
{
61
61
Source = EndpointParameterSource . Route ;
62
62
LookupName = GetEscapedParameterName ( fromRouteAttribute , symbol . Name ) ;
63
63
IsParsable = TryGetParsability ( Type , wellKnownTypes , out var parsingBlockEmitter ) ;
64
64
ParsingBlockEmitter = parsingBlockEmitter ;
65
65
}
66
- else if ( attributes . HasAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata ) , out var fromQueryAttribute ) )
66
+ else if ( attributes . TryGetAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata ) , out var fromQueryAttribute ) )
67
67
{
68
68
Source = EndpointParameterSource . Query ;
69
69
LookupName = GetEscapedParameterName ( fromQueryAttribute , symbol . Name ) ;
70
70
IsParsable = TryGetParsability ( Type , wellKnownTypes , out var parsingBlockEmitter ) ;
71
71
ParsingBlockEmitter = parsingBlockEmitter ;
72
72
}
73
- else if ( attributes . HasAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromHeaderMetadata ) , out var fromHeaderAttribute ) )
73
+ else if ( attributes . TryGetAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromHeaderMetadata ) , out var fromHeaderAttribute ) )
74
74
{
75
75
Source = EndpointParameterSource . Header ;
76
76
LookupName = GetEscapedParameterName ( fromHeaderAttribute , symbol . Name ) ;
77
77
IsParsable = TryGetParsability ( Type , wellKnownTypes , out var parsingBlockEmitter ) ;
78
78
ParsingBlockEmitter = parsingBlockEmitter ;
79
79
}
80
- else if ( attributes . HasAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromFormMetadata ) , out var fromFormAttribute ) )
80
+ else if ( attributes . TryGetAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromFormMetadata ) , out var fromFormAttribute ) )
81
81
{
82
+ endpoint . IsAwaitable = true ;
82
83
Source = EndpointParameterSource . FormBody ;
83
84
LookupName = GetEscapedParameterName ( fromFormAttribute , symbol . Name ) ;
84
85
if ( SymbolEqualityComparer . Default . Equals ( Type , wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_IFormFileCollection ) ) )
@@ -114,11 +115,12 @@ private void ProcessEndpointParameterSource(Endpoint endpoint, ISymbol symbol, I
114
115
}
115
116
else
116
117
{
118
+ endpoint . IsAwaitable = true ;
117
119
Source = EndpointParameterSource . JsonBody ;
118
120
}
119
121
IsOptional = isOptional ;
120
122
}
121
- else if ( attributes . HasAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata ) ) )
123
+ else if ( attributes . TryGetAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata ) ) )
122
124
{
123
125
Source = EndpointParameterSource . Service ;
124
126
}
@@ -151,24 +153,28 @@ Type is not INamedTypeSymbol namedTypeSymbol ||
151
153
}
152
154
else if ( SymbolEqualityComparer . Default . Equals ( Type , wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_IFormFileCollection ) ) )
153
155
{
156
+ endpoint . IsAwaitable = true ;
154
157
Source = EndpointParameterSource . FormBody ;
155
158
LookupName = symbol . Name ;
156
159
AssigningCode = "httpContext.Request.Form.Files" ;
157
160
}
158
161
else if ( SymbolEqualityComparer . Default . Equals ( Type , wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_IFormFile ) ) )
159
162
{
163
+ endpoint . IsAwaitable = true ;
160
164
Source = EndpointParameterSource . FormBody ;
161
165
LookupName = symbol . Name ;
162
166
AssigningCode = $ "httpContext.Request.Form.Files[{ SymbolDisplay . FormatLiteral ( LookupName , true ) } ]";
163
167
}
164
168
else if ( SymbolEqualityComparer . Default . Equals ( Type , wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_IFormCollection ) ) )
165
169
{
170
+ endpoint . IsAwaitable = true ;
166
171
Source = EndpointParameterSource . FormBody ;
167
172
LookupName = symbol . Name ;
168
173
AssigningCode = "httpContext.Request.Form" ;
169
174
}
170
175
else if ( HasBindAsync ( Type , wellKnownTypes , out var bindMethod ) )
171
176
{
177
+ endpoint . IsAwaitable = true ;
172
178
Source = EndpointParameterSource . BindAsync ;
173
179
BindMethod = bindMethod ;
174
180
}
@@ -189,20 +195,21 @@ Type is not INamedTypeSymbol namedTypeSymbol ||
189
195
{
190
196
Source = EndpointParameterSource . RouteOrQuery ;
191
197
IsParsable = true ;
192
- endpoint . EmitterContext . HasParsable = true ;
193
198
ParsingBlockEmitter = parsingBlockEmitter ;
194
199
}
195
200
else
196
201
{
202
+ endpoint . IsAwaitable = true ;
197
203
Source = EndpointParameterSource . JsonBodyOrService ;
198
204
}
205
+ endpoint . EmitterContext . HasParsable |= IsParsable ;
199
206
}
200
207
201
- private static bool ImplementsIEndpointMetadataProvider ( IParameterSymbol parameter , WellKnownTypes wellKnownTypes )
202
- => parameter . Type . Implements ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IEndpointMetadataProvider ) ) ;
208
+ private static bool ImplementsIEndpointMetadataProvider ( ITypeSymbol type , WellKnownTypes wellKnownTypes )
209
+ => type . Implements ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IEndpointMetadataProvider ) ) ;
203
210
204
- private static bool ImplementsIEndpointParameterMetadataProvider ( IParameterSymbol parameter , WellKnownTypes wellKnownTypes )
205
- => parameter . Type . Implements ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IEndpointParameterMetadataProvider ) ) ;
211
+ private static bool ImplementsIEndpointParameterMetadataProvider ( ITypeSymbol type , WellKnownTypes wellKnownTypes )
212
+ => type . Implements ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IEndpointParameterMetadataProvider ) ) ;
206
213
207
214
private static bool ShouldDisableInferredBodyParameters ( string httpMethod )
208
215
{
@@ -220,11 +227,11 @@ private static bool ShouldDisableInferredBodyParameters(string httpMethod)
220
227
public bool IsEndpointMetadataProvider { get ; }
221
228
public bool IsEndpointParameterMetadataProvider { get ; }
222
229
public string SymbolName { get ; }
223
- public string LookupName { get ; set ; }
230
+ public string LookupName { get ; set ; }
224
231
public int Ordinal { get ; }
225
232
public bool IsOptional { get ; set ; }
226
233
public bool IsArray { get ; set ; }
227
- public string DefaultValue { get ; set ; }
234
+ public string DefaultValue { get ; set ; } = "null" ;
228
235
229
236
public EndpointParameterSource Source { get ; set ; }
230
237
@@ -415,7 +422,7 @@ private static bool TryGetExplicitFromJsonBody(ISymbol typeSymbol,
415
422
out bool isOptional )
416
423
{
417
424
isOptional = false ;
418
- if ( ! attributes . HasAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromBodyMetadata ) , out var fromBodyAttribute ) )
425
+ if ( ! attributes . TryGetAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromBodyMetadata ) , out var fromBodyAttribute ) )
419
426
{
420
427
return false ;
421
428
}
@@ -455,11 +462,14 @@ private static bool TryGetAsParametersConstructor(INamedTypeSymbol type, out boo
455
462
456
463
var constructors = type . Constructors . Where ( constructor => constructor . DeclaredAccessibility == Accessibility . Public && ! constructor . IsStatic ) ;
457
464
var numOfConstructors = constructors . Count ( ) ;
465
+ // When leveraging parameterless constructors, we want to ensure we only emit for writable
466
+ // properties. We do not have this constraint if we are leveraging a parameterized constructor.
467
+ var properties = type . GetMembers ( ) . OfType < IPropertySymbol > ( ) . Where ( property => property . DeclaredAccessibility == Accessibility . Public ) ;
468
+ var writableProperties = properties . Where ( property => ! property . IsReadOnly ) ;
458
469
459
470
if ( numOfConstructors == 1 )
460
471
{
461
472
var targetConstructor = constructors . SingleOrDefault ( ) ;
462
- var properties = type . GetMembers ( ) . OfType < IPropertySymbol > ( ) . Where ( property => property . DeclaredAccessibility == Accessibility . Public ) ;
463
473
var lookupTable = new Dictionary < ParameterLookupKey , IPropertySymbol > ( ) ;
464
474
foreach ( var property in properties )
465
475
{
@@ -472,7 +482,7 @@ private static bool TryGetAsParametersConstructor(INamedTypeSymbol type, out boo
472
482
if ( parameters . Length == 0 )
473
483
{
474
484
isParameterlessConstructor = true ;
475
- matchedParameters = properties . Select ( property => new ConstructorParameter ( property , null ) ) ;
485
+ matchedParameters = writableProperties . Select ( property => new ConstructorParameter ( property , null ) ) ;
476
486
return true ;
477
487
}
478
488
@@ -497,7 +507,7 @@ private static bool TryGetAsParametersConstructor(INamedTypeSymbol type, out boo
497
507
if ( type . IsValueType )
498
508
{
499
509
isParameterlessConstructor = true ;
500
- matchedParameters = type . GetMembers ( ) . OfType < IPropertySymbol > ( ) . Select ( property => new ConstructorParameter ( property , null ) ) ;
510
+ matchedParameters = writableProperties . Select ( property => new ConstructorParameter ( property , null ) ) ;
501
511
return true ;
502
512
}
503
513
0 commit comments