@@ -552,6 +552,113 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
552552 }
553553 }
554554
555+ // path parameter - extract from RouteValues
556+ if ( parameter . Value is null && endpoint . HasPathParameters )
557+ {
558+ string ? matchedPathParam = null ;
559+ foreach ( var pathParam in endpoint . PathParameters ! )
560+ {
561+ if ( string . Equals ( pathParam , parameter . ConvertedName , StringComparison . OrdinalIgnoreCase ) ||
562+ string . Equals ( pathParam , parameter . ActualName , StringComparison . OrdinalIgnoreCase ) )
563+ {
564+ matchedPathParam = pathParam ;
565+ break ;
566+ }
567+ }
568+
569+ // Try to get route value using the path parameter name from the template
570+ if ( matchedPathParam is not null && context . Request . RouteValues . TryGetValue ( matchedPathParam , out var routeValue ) )
571+ {
572+ StringValues pathStringValues = routeValue ? . ToString ( ) ?? "" ;
573+ if ( TryParseParameter ( parameter , ref pathStringValues , endpoint . QueryStringNullHandling ) )
574+ {
575+ parameter . ParamType = ParamType . PathParam ;
576+ parameter . QueryStringValues = pathStringValues ;
577+
578+ if ( Options . ValidateParameters is not null )
579+ {
580+ Options . ValidateParameters ( new ParameterValidationValues (
581+ context ,
582+ routine ,
583+ parameter ) ) ;
584+ if ( context . Response . HasStarted || context . Response . StatusCode != ( int ) HttpStatusCode . OK )
585+ {
586+ return ;
587+ }
588+ }
589+ if ( Options . ValidateParametersAsync is not null )
590+ {
591+ await Options . ValidateParametersAsync ( new ParameterValidationValues (
592+ context ,
593+ routine ,
594+ parameter ) ) ;
595+ if ( context . Response . HasStarted || context . Response . StatusCode != ( int ) HttpStatusCode . OK )
596+ {
597+ return ;
598+ }
599+ }
600+ if ( endpoint . Cached is true && endpoint . CachedParams is not null )
601+ {
602+ if ( endpoint . CachedParams . Contains ( parameter . ConvertedName ) || endpoint . CachedParams . Contains ( parameter . ActualName ) )
603+ {
604+ cacheKeys ? . Append ( NpgsqlRestParameter . GetCacheKeySeparator ( ) ) ;
605+ cacheKeys ? . Append ( parameter . GetCacheStringValue ( ) ) ;
606+ }
607+ }
608+
609+ if ( Options . HttpClientOptions . Enabled )
610+ {
611+ if ( parameter . TypeDescriptor . CustomType is not null )
612+ {
613+ if ( HttpClientTypes . Definitions . ContainsKey ( parameter . TypeDescriptor . CustomType ) )
614+ {
615+ customHttpTypes . Add ( parameter . TypeDescriptor . CustomType ) ;
616+ }
617+ }
618+ }
619+ command . Parameters . Add ( parameter ) ;
620+
621+ if ( hasNulls is false && parameter . Value == DBNull . Value )
622+ {
623+ hasNulls = true ;
624+ }
625+
626+ if ( formatter . IsFormattable is false )
627+ {
628+ if ( formatter . RefContext )
629+ {
630+ commandText = string . Concat ( commandText ,
631+ formatter . AppendCommandParameter ( parameter , paramIndex , context ) ) ;
632+ if ( context . Response . HasStarted || context . Response . StatusCode != ( int ) HttpStatusCode . OK )
633+ {
634+ return ;
635+ }
636+ }
637+ else
638+ {
639+ commandText = string . Concat ( commandText ,
640+ formatter . AppendCommandParameter ( parameter , paramIndex ) ) ;
641+ }
642+ }
643+ paramIndex ++ ;
644+ if ( shouldLog && Options . LogCommandParameters )
645+ {
646+ var p = Options . AuthenticationOptions . ObfuscateAuthParameterLogValues && endpoint . IsAuth ?
647+ "***" :
648+ FormatParameterForLog ( parameter ) ;
649+ cmdLog ! . AppendLine ( string . Concat (
650+ "-- $" ,
651+ paramIndex . ToString ( ) ,
652+ " " , parameter . TypeDescriptor . OriginalType ,
653+ " = " ,
654+ p ) ) ;
655+ }
656+
657+ continue ;
658+ }
659+ }
660+ }
661+
555662 if ( queryDict . TryGetValue ( parameter . ConvertedName , out var qsValue ) is false )
556663 {
557664 if ( parameter . Value is null )
@@ -719,9 +826,12 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
719826 return ;
720827 }
721828
722- if ( bodyDict . Count != routine . ParamCount && overloads . Count > 0 )
829+ // Account for path parameters when counting body parameters
830+ var pathParamCount = endpoint . PathParameters ? . Length ?? 0 ;
831+ var expectedBodyParamCount = routine . ParamCount - pathParamCount ;
832+ if ( bodyDict . Count != expectedBodyParamCount && overloads . Count > 0 )
723833 {
724- if ( overloads . TryGetValue ( string . Concat ( entry . Key , bodyDict . Count ) , out var overload ) )
834+ if ( overloads . TryGetValue ( string . Concat ( entry . Key , bodyDict . Count + pathParamCount ) , out var overload ) )
725835 {
726836 routine = overload . Endpoint . Routine ;
727837 endpoint = overload . Endpoint ;
@@ -872,6 +982,113 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
872982 }
873983 }
874984
985+ // path parameter - extract from RouteValues (for JSON body mode)
986+ if ( parameter . Value is null && endpoint . HasPathParameters )
987+ {
988+ string ? matchedPathParam = null ;
989+ foreach ( var pathParam in endpoint . PathParameters ! )
990+ {
991+ if ( string . Equals ( pathParam , parameter . ConvertedName , StringComparison . OrdinalIgnoreCase ) ||
992+ string . Equals ( pathParam , parameter . ActualName , StringComparison . OrdinalIgnoreCase ) )
993+ {
994+ matchedPathParam = pathParam ;
995+ break ;
996+ }
997+ }
998+
999+ // Try to get route value using the path parameter name from the template
1000+ if ( matchedPathParam is not null && context . Request . RouteValues . TryGetValue ( matchedPathParam , out var routeValue ) )
1001+ {
1002+ StringValues pathStringValues = routeValue ? . ToString ( ) ?? "" ;
1003+ if ( TryParseParameter ( parameter , ref pathStringValues , endpoint . QueryStringNullHandling ) )
1004+ {
1005+ parameter . ParamType = ParamType . PathParam ;
1006+ parameter . QueryStringValues = pathStringValues ;
1007+
1008+ if ( Options . ValidateParameters is not null )
1009+ {
1010+ Options . ValidateParameters ( new ParameterValidationValues (
1011+ context ,
1012+ routine ,
1013+ parameter ) ) ;
1014+ if ( context . Response . HasStarted || context . Response . StatusCode != ( int ) HttpStatusCode . OK )
1015+ {
1016+ return ;
1017+ }
1018+ }
1019+ if ( Options . ValidateParametersAsync is not null )
1020+ {
1021+ await Options . ValidateParametersAsync ( new ParameterValidationValues (
1022+ context ,
1023+ routine ,
1024+ parameter ) ) ;
1025+ if ( context . Response . HasStarted || context . Response . StatusCode != ( int ) HttpStatusCode . OK )
1026+ {
1027+ return ;
1028+ }
1029+ }
1030+ if ( endpoint . Cached is true && endpoint . CachedParams is not null )
1031+ {
1032+ if ( endpoint . CachedParams . Contains ( parameter . ConvertedName ) || endpoint . CachedParams . Contains ( parameter . ActualName ) )
1033+ {
1034+ cacheKeys ? . Append ( NpgsqlRestParameter . GetCacheKeySeparator ( ) ) ;
1035+ cacheKeys ? . Append ( parameter . GetCacheStringValue ( ) ) ;
1036+ }
1037+ }
1038+
1039+ if ( Options . HttpClientOptions . Enabled )
1040+ {
1041+ if ( parameter . TypeDescriptor . CustomType is not null )
1042+ {
1043+ if ( HttpClientTypes . Definitions . ContainsKey ( parameter . TypeDescriptor . CustomType ) )
1044+ {
1045+ customHttpTypes . Add ( parameter . TypeDescriptor . CustomType ) ;
1046+ }
1047+ }
1048+ }
1049+ command . Parameters . Add ( parameter ) ;
1050+
1051+ if ( hasNulls is false && parameter . Value == DBNull . Value )
1052+ {
1053+ hasNulls = true ;
1054+ }
1055+
1056+ if ( formatter . IsFormattable is false )
1057+ {
1058+ if ( formatter . RefContext )
1059+ {
1060+ commandText = string . Concat ( commandText ,
1061+ formatter . AppendCommandParameter ( parameter , paramIndex , context ) ) ;
1062+ if ( context . Response . HasStarted || context . Response . StatusCode != ( int ) HttpStatusCode . OK )
1063+ {
1064+ return ;
1065+ }
1066+ }
1067+ else
1068+ {
1069+ commandText = string . Concat ( commandText ,
1070+ formatter . AppendCommandParameter ( parameter , paramIndex ) ) ;
1071+ }
1072+ }
1073+ paramIndex ++ ;
1074+ if ( shouldLog && Options . LogCommandParameters )
1075+ {
1076+ var p = Options . AuthenticationOptions . ObfuscateAuthParameterLogValues && endpoint . IsAuth ?
1077+ "***" :
1078+ FormatParameterForLog ( parameter ) ;
1079+ cmdLog ! . AppendLine ( string . Concat (
1080+ "-- $" ,
1081+ paramIndex . ToString ( ) ,
1082+ " " , parameter . TypeDescriptor . OriginalType ,
1083+ " = " ,
1084+ p ) ) ;
1085+ }
1086+
1087+ continue ;
1088+ }
1089+ }
1090+ }
1091+
8751092 if ( bodyDict . TryGetValue ( parameter . ConvertedName , out var value ) is false )
8761093 {
8771094 if ( parameter . Value is null )
0 commit comments