5
5
using System . Linq . Expressions ;
6
6
using System . Reflection ;
7
7
using System . Security . Claims ;
8
+ using System . Text ;
8
9
using Microsoft . AspNetCore . Http . Features ;
9
10
using Microsoft . AspNetCore . Http . Metadata ;
10
11
using Microsoft . Extensions . DependencyInjection ;
@@ -189,6 +190,13 @@ private static Expression[] CreateArguments(ParameterInfo[]? parameters, Factory
189
190
args [ i ] = CreateArgument ( parameters [ i ] , factoryContext ) ;
190
191
}
191
192
193
+ if ( factoryContext . HasMultipleBodyParameters )
194
+ {
195
+ var errorMessage = BuildErrorMessageForMultipleBodyParameters ( factoryContext ) ;
196
+ throw new InvalidOperationException ( errorMessage ) ;
197
+
198
+ }
199
+
192
200
return args ;
193
201
}
194
202
@@ -203,6 +211,7 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
203
211
204
212
if ( parameterCustomAttributes . OfType < IFromRouteMetadata > ( ) . FirstOrDefault ( ) is { } routeAttribute )
205
213
{
214
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . RouteAttribue ) ;
206
215
if ( factoryContext . RouteParameters is { } routeParams && ! routeParams . Contains ( parameter . Name , StringComparer . OrdinalIgnoreCase ) )
207
216
{
208
217
throw new InvalidOperationException ( $ "{ parameter . Name } is not a route paramter.") ;
@@ -212,18 +221,22 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
212
221
}
213
222
else if ( parameterCustomAttributes . OfType < IFromQueryMetadata > ( ) . FirstOrDefault ( ) is { } queryAttribute )
214
223
{
224
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . QueryAttribue ) ;
215
225
return BindParameterFromProperty ( parameter , QueryExpr , queryAttribute . Name ?? parameter . Name , factoryContext ) ;
216
226
}
217
227
else if ( parameterCustomAttributes . OfType < IFromHeaderMetadata > ( ) . FirstOrDefault ( ) is { } headerAttribute )
218
228
{
229
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . HeaderAttribue ) ;
219
230
return BindParameterFromProperty ( parameter , HeadersExpr , headerAttribute . Name ?? parameter . Name , factoryContext ) ;
220
231
}
221
232
else if ( parameterCustomAttributes . OfType < IFromBodyMetadata > ( ) . FirstOrDefault ( ) is { } bodyAttribute )
222
233
{
234
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . BodyAttribue ) ;
223
235
return BindParameterFromBody ( parameter , bodyAttribute . AllowEmpty , factoryContext ) ;
224
236
}
225
237
else if ( parameter . CustomAttributes . Any ( a => typeof ( IFromServiceMetadata ) . IsAssignableFrom ( a . AttributeType ) ) )
226
238
{
239
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . ServiceAttribue ) ;
227
240
return BindParameterFromService ( parameter ) ;
228
241
}
229
242
else if ( parameter . ParameterType == typeof ( HttpContext ) )
@@ -254,18 +267,22 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
254
267
// when RDF.Create is manually invoked.
255
268
if ( factoryContext . RouteParameters is { } routeParams )
256
269
{
270
+
257
271
if ( routeParams . Contains ( parameter . Name , StringComparer . OrdinalIgnoreCase ) )
258
272
{
259
273
// We're in the fallback case and we have a parameter and route parameter match so don't fallback
260
274
// to query string in this case
275
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . RouteParameter ) ;
261
276
return BindParameterFromProperty ( parameter , RouteValuesExpr , parameter . Name , factoryContext ) ;
262
277
}
263
278
else
264
279
{
280
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . QueryStringParameter ) ;
265
281
return BindParameterFromProperty ( parameter , QueryExpr , parameter . Name , factoryContext ) ;
266
282
}
267
283
}
268
284
285
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . RouteOrQueryStringParameter ) ;
269
286
return BindParameterFromRouteValueOrQueryString ( parameter , parameter . Name , factoryContext ) ;
270
287
}
271
288
else
@@ -274,10 +291,12 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
274
291
{
275
292
if ( serviceProviderIsService . IsService ( parameter . ParameterType ) )
276
293
{
294
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . ServiceParameter ) ;
277
295
return Expression . Call ( GetRequiredServiceMethod . MakeGenericMethod ( parameter . ParameterType ) , RequestServicesExpr ) ;
278
296
}
279
297
}
280
298
299
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . BodyParameter ) ;
281
300
return BindParameterFromBody ( parameter , allowEmpty : false , factoryContext ) ;
282
301
}
283
302
}
@@ -500,7 +519,6 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
500
519
return async ( target , httpContext ) =>
501
520
{
502
521
object ? bodyValue = defaultBodyValue ;
503
-
504
522
var feature = httpContext . Features . Get < IHttpRequestBodyDetectionFeature > ( ) ;
505
523
if ( feature ? . CanHaveBody == true )
506
524
{
@@ -515,12 +533,12 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
515
533
}
516
534
catch ( InvalidDataException ex )
517
535
{
536
+
518
537
Log . RequestBodyInvalidDataException ( httpContext , ex ) ;
519
538
httpContext . Response . StatusCode = 400 ;
520
539
return ;
521
540
}
522
541
}
523
-
524
542
await invoker ( target , httpContext , bodyValue ) ;
525
543
} ;
526
544
}
@@ -725,7 +743,14 @@ private static Expression BindParameterFromBody(ParameterInfo parameter, bool al
725
743
{
726
744
if ( factoryContext . JsonRequestBodyType is not null )
727
745
{
728
- throw new InvalidOperationException ( "Action cannot have more than one FromBody attribute." ) ;
746
+ factoryContext . HasMultipleBodyParameters = true ;
747
+ var parameterName = parameter . Name ;
748
+ if ( parameterName is not null && factoryContext . TrackedParameters . ContainsKey ( parameterName ) )
749
+ {
750
+ factoryContext . TrackedParameters . Remove ( parameterName ) ;
751
+ factoryContext . TrackedParameters . Add ( parameterName , "UNKNOWN" ) ;
752
+
753
+ }
729
754
}
730
755
731
756
var nullability = NullabilityContext . Create ( parameter ) ;
@@ -963,6 +988,24 @@ private class FactoryContext
963
988
public bool UsingTempSourceString { get ; set ; }
964
989
public List < ParameterExpression > ExtraLocals { get ; } = new ( ) ;
965
990
public List < Expression > ParamCheckExpressions { get ; } = new ( ) ;
991
+
992
+ public Dictionary < string , string > TrackedParameters { get ; } = new ( ) ;
993
+ public bool HasMultipleBodyParameters { get ; set ; }
994
+ }
995
+
996
+ private static class RequestDelegateFactoryConstants
997
+ {
998
+ public const string RouteAttribue = "Route (Attribute)" ;
999
+ public const string QueryAttribue = "Query (Attribute)" ;
1000
+ public const string HeaderAttribue = "Header (Attribute)" ;
1001
+ public const string BodyAttribue = "Body (Attribute)" ;
1002
+ public const string ServiceAttribue = "Service (Attribute)" ;
1003
+ public const string RouteParameter = "Route (Inferred)" ;
1004
+ public const string QueryStringParameter = "Query String (Inferred)" ;
1005
+ public const string ServiceParameter = "Services (Inferred)" ;
1006
+ public const string BodyParameter = "Body (Inferred)" ;
1007
+ public const string RouteOrQueryStringParameter = "Route or Query String (Inferred)" ;
1008
+
966
1009
}
967
1010
968
1011
private static partial class Log
@@ -1032,5 +1075,22 @@ private static void SetPlaintextContentType(HttpContext httpContext)
1032
1075
{
1033
1076
httpContext . Response . ContentType ??= "text/plain; charset=utf-8" ;
1034
1077
}
1078
+
1079
+ private static string BuildErrorMessageForMultipleBodyParameters ( FactoryContext factoryContext )
1080
+ {
1081
+ var errorMessage = new StringBuilder ( ) ;
1082
+ errorMessage . Append ( $ "Failure to infer one or more parameters.\n ") ;
1083
+ errorMessage . Append ( "Below is the list of parameters that we found: \n \n " ) ;
1084
+ errorMessage . Append ( $ "{ "Parameter" , - 20 } |{ "Source" , - 30 } \n ") ;
1085
+ errorMessage . Append ( "---------------------------------------------------------------------------------\n " ) ;
1086
+
1087
+ foreach ( var kv in factoryContext . TrackedParameters )
1088
+ {
1089
+ errorMessage . Append ( $ "{ kv . Key , - 19 } | { kv . Value , - 15 } \n ") ;
1090
+ }
1091
+ errorMessage . Append ( "\n \n " ) ;
1092
+ errorMessage . Append ( "Did you mean to register the \" UNKNOWN\" parameters as a Service?\n \n " ) ;
1093
+ return errorMessage . ToString ( ) ;
1094
+ }
1035
1095
}
1036
1096
}
0 commit comments