55using System . Linq . Expressions ;
66using System . Reflection ;
77using System . Security . Claims ;
8+ using System . Text ;
89using Microsoft . AspNetCore . Http . Features ;
910using Microsoft . AspNetCore . Http . Metadata ;
1011using Microsoft . Extensions . DependencyInjection ;
@@ -189,6 +190,13 @@ private static Expression[] CreateArguments(ParameterInfo[]? parameters, Factory
189190 args [ i ] = CreateArgument ( parameters [ i ] , factoryContext ) ;
190191 }
191192
193+ if ( factoryContext . HasMultipleBodyParameters )
194+ {
195+ var errorMessage = BuildErrorMessageForMultipleBodyParameters ( factoryContext ) ;
196+ throw new InvalidOperationException ( errorMessage ) ;
197+
198+ }
199+
192200 return args ;
193201 }
194202
@@ -203,6 +211,7 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
203211
204212 if ( parameterCustomAttributes . OfType < IFromRouteMetadata > ( ) . FirstOrDefault ( ) is { } routeAttribute )
205213 {
214+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . RouteAttribue ) ;
206215 if ( factoryContext . RouteParameters is { } routeParams && ! routeParams . Contains ( parameter . Name , StringComparer . OrdinalIgnoreCase ) )
207216 {
208217 throw new InvalidOperationException ( $ "{ parameter . Name } is not a route paramter.") ;
@@ -212,18 +221,22 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
212221 }
213222 else if ( parameterCustomAttributes . OfType < IFromQueryMetadata > ( ) . FirstOrDefault ( ) is { } queryAttribute )
214223 {
224+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . QueryAttribue ) ;
215225 return BindParameterFromProperty ( parameter , QueryExpr , queryAttribute . Name ?? parameter . Name , factoryContext ) ;
216226 }
217227 else if ( parameterCustomAttributes . OfType < IFromHeaderMetadata > ( ) . FirstOrDefault ( ) is { } headerAttribute )
218228 {
229+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . HeaderAttribue ) ;
219230 return BindParameterFromProperty ( parameter , HeadersExpr , headerAttribute . Name ?? parameter . Name , factoryContext ) ;
220231 }
221232 else if ( parameterCustomAttributes . OfType < IFromBodyMetadata > ( ) . FirstOrDefault ( ) is { } bodyAttribute )
222233 {
234+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . BodyAttribue ) ;
223235 return BindParameterFromBody ( parameter , bodyAttribute . AllowEmpty , factoryContext ) ;
224236 }
225237 else if ( parameter . CustomAttributes . Any ( a => typeof ( IFromServiceMetadata ) . IsAssignableFrom ( a . AttributeType ) ) )
226238 {
239+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . ServiceAttribue ) ;
227240 return BindParameterFromService ( parameter ) ;
228241 }
229242 else if ( parameter . ParameterType == typeof ( HttpContext ) )
@@ -254,18 +267,22 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
254267 // when RDF.Create is manually invoked.
255268 if ( factoryContext . RouteParameters is { } routeParams )
256269 {
270+
257271 if ( routeParams . Contains ( parameter . Name , StringComparer . OrdinalIgnoreCase ) )
258272 {
259273 // We're in the fallback case and we have a parameter and route parameter match so don't fallback
260274 // to query string in this case
275+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . RouteParameter ) ;
261276 return BindParameterFromProperty ( parameter , RouteValuesExpr , parameter . Name , factoryContext ) ;
262277 }
263278 else
264279 {
280+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . QueryStringParameter ) ;
265281 return BindParameterFromProperty ( parameter , QueryExpr , parameter . Name , factoryContext ) ;
266282 }
267283 }
268284
285+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . RouteOrQueryStringParameter ) ;
269286 return BindParameterFromRouteValueOrQueryString ( parameter , parameter . Name , factoryContext ) ;
270287 }
271288 else
@@ -274,10 +291,12 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
274291 {
275292 if ( serviceProviderIsService . IsService ( parameter . ParameterType ) )
276293 {
294+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . ServiceParameter ) ;
277295 return Expression . Call ( GetRequiredServiceMethod . MakeGenericMethod ( parameter . ParameterType ) , RequestServicesExpr ) ;
278296 }
279297 }
280298
299+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . BodyParameter ) ;
281300 return BindParameterFromBody ( parameter , allowEmpty : false , factoryContext ) ;
282301 }
283302 }
@@ -500,7 +519,6 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
500519 return async ( target , httpContext ) =>
501520 {
502521 object ? bodyValue = defaultBodyValue ;
503-
504522 var feature = httpContext . Features . Get < IHttpRequestBodyDetectionFeature > ( ) ;
505523 if ( feature ? . CanHaveBody == true )
506524 {
@@ -515,12 +533,12 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
515533 }
516534 catch ( InvalidDataException ex )
517535 {
536+
518537 Log . RequestBodyInvalidDataException ( httpContext , ex ) ;
519538 httpContext . Response . StatusCode = 400 ;
520539 return ;
521540 }
522541 }
523-
524542 await invoker ( target , httpContext , bodyValue ) ;
525543 } ;
526544 }
@@ -725,7 +743,14 @@ private static Expression BindParameterFromBody(ParameterInfo parameter, bool al
725743 {
726744 if ( factoryContext . JsonRequestBodyType is not null )
727745 {
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+ }
729754 }
730755
731756 var nullability = NullabilityContext . Create ( parameter ) ;
@@ -963,6 +988,24 @@ private class FactoryContext
963988 public bool UsingTempSourceString { get ; set ; }
964989 public List < ParameterExpression > ExtraLocals { get ; } = new ( ) ;
965990 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+
9661009 }
9671010
9681011 private static partial class Log
@@ -1032,5 +1075,22 @@ private static void SetPlaintextContentType(HttpContext httpContext)
10321075 {
10331076 httpContext . Response . ContentType ??= "text/plain; charset=utf-8" ;
10341077 }
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+ }
10351095 }
10361096}
0 commit comments