@@ -39,7 +39,6 @@ namespace Microsoft.AspNetCore.Http;
3939public static partial class RequestDelegateFactory
4040{
4141 private static readonly ParameterBindingMethodCache ParameterBindingMethodCache = new ( ) ;
42- private static readonly FormDataMapperOptions FormDataMapperOptions = new ( ) ;
4342
4443 private static readonly MethodInfo ExecuteTaskWithEmptyResultMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( ExecuteTaskWithEmptyResult ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
4544 private static readonly MethodInfo ExecuteValueTaskWithEmptyResultMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( ExecuteValueTaskWithEmptyResult ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
@@ -333,6 +332,11 @@ private static IReadOnlyList<object> AsReadOnlyList(IList<object> metadata)
333332 // inference is skipped internally if necessary.
334333 factoryContext . ArgumentExpressions ??= CreateArgumentsAndInferMetadata ( methodInfo , factoryContext ) ;
335334
335+ // Although we can re-use the cached argument expressions for most cases, parameters that are bound
336+ // using the new form mapping logic are a special exception because we need to account for the `FormOptionsMetadata`
337+ // added to the builder _during_ the construction of the parameter binding.
338+ UpdateFormBindingArgumentExpressions ( factoryContext ) ;
339+
336340 factoryContext . MethodCall = CreateMethodCall ( methodInfo , targetExpression , factoryContext . ArgumentExpressions ) ;
337341 EndpointFilterDelegate ? filterPipeline = null ;
338342 var returnType = methodInfo . ReturnType ;
@@ -753,7 +757,7 @@ private static Expression CreateArgument(ParameterInfo parameter, RequestDelegat
753757 ( parameter . ParameterType . IsArray && ParameterBindingMethodCache . HasTryParseMethod ( parameter . ParameterType . GetElementType ( ) ! ) ) ;
754758 return useSimpleBinding
755759 ? BindParameterFromFormItem ( parameter , formAttribute . Name ?? parameter . Name , factoryContext )
756- : BindComplexParameterFromFormItem ( parameter , formAttribute . Name ?? parameter . Name , factoryContext ) ;
760+ : BindComplexParameterFromFormItem ( parameter , string . IsNullOrEmpty ( formAttribute . Name ) ? parameter . Name : formAttribute . Name , factoryContext ) ;
757761 }
758762 else if ( parameter . CustomAttributes . Any ( a => typeof ( IFromServiceMetadata ) . IsAssignableFrom ( a . AttributeType ) ) )
759763 {
@@ -1982,14 +1986,40 @@ private static Expression BindParameterFromFormItem(
19821986 "form" ) ;
19831987 }
19841988
1989+ private static void UpdateFormBindingArgumentExpressions ( RequestDelegateFactoryContext factoryContext )
1990+ {
1991+ if ( factoryContext . ArgumentExpressions == null || factoryContext . ArgumentExpressions . Length == 0 )
1992+ {
1993+ return ;
1994+ }
1995+
1996+ for ( var i = 0 ; i < factoryContext . ArgumentExpressions . Length ; i ++ )
1997+ {
1998+ var parameter = factoryContext . Parameters [ i ] ;
1999+ var key = parameter . Name ! ;
2000+ if ( factoryContext . TrackedParameters . TryGetValue ( key , out var trackedParameter ) && trackedParameter == RequestDelegateFactoryConstants . FormBindingAttribute )
2001+ {
2002+ factoryContext . ArgumentExpressions [ i ] = BindComplexParameterFromFormItem ( parameter , key , factoryContext ) ;
2003+ }
2004+ }
2005+ }
2006+
19852007 private static Expression BindComplexParameterFromFormItem (
19862008 ParameterInfo parameter ,
19872009 string key ,
19882010 RequestDelegateFactoryContext factoryContext )
19892011 {
19902012 factoryContext . FirstFormRequestBodyParameter ??= parameter ;
1991- factoryContext . TrackedParameters . Add ( key , RequestDelegateFactoryConstants . FormAttribute ) ;
2013+ factoryContext . TrackedParameters . TryAdd ( key , RequestDelegateFactoryConstants . FormBindingAttribute ) ;
19922014 factoryContext . ReadForm = true ;
2015+ var formDataMapperOptions = new FormDataMapperOptions ( ) ;
2016+ var formMappingOptionsMetadatas = factoryContext . EndpointBuilder . Metadata . OfType < FormMappingOptionsMetadata > ( ) ;
2017+ foreach ( var formMappingOptionsMetadata in formMappingOptionsMetadatas )
2018+ {
2019+ formDataMapperOptions . MaxRecursionDepth = formMappingOptionsMetadata . MaxRecursionDepth ?? formDataMapperOptions . MaxRecursionDepth ;
2020+ formDataMapperOptions . MaxCollectionSize = formMappingOptionsMetadata . MaxCollectionSize ?? formDataMapperOptions . MaxCollectionSize ;
2021+ formDataMapperOptions . MaxKeyBufferSize = formMappingOptionsMetadata . MaxKeySize ?? formDataMapperOptions . MaxKeyBufferSize ;
2022+ }
19932023
19942024 // var name_local;
19952025 // var name_reader;
@@ -2001,19 +2031,19 @@ private static Expression BindComplexParameterFromFormItem(
20012031 var formBuffer = Expression . Variable ( typeof ( char [ ] ) , "form_buffer" ) ;
20022032
20032033 // ProcessForm(context.Request.Form, form_dict, form_buffer);
2004- var processFormExpr = Expression . Call ( ProcessFormMethod , FormExpr , formDict , formBuffer ) ;
2034+ var processFormExpr = Expression . Call ( ProcessFormMethod , FormExpr , Expression . Constant ( formDataMapperOptions . MaxKeyBufferSize ) , formDict , formBuffer ) ;
20052035 // name_reader = new FormDataReader(form_dict, CultureInfo.InvariantCulture, form_buffer.AsMemory(0, FormDataMapperOptions.MaxKeyBufferSize));
20062036 var initializeReaderExpr = Expression . Assign (
20072037 formReader ,
20082038 Expression . New ( FormDataReaderConstructor ,
20092039 formDict ,
20102040 Expression . Constant ( CultureInfo . InvariantCulture ) ,
2011- Expression . Call ( AsMemoryMethod , formBuffer , Expression . Constant ( 0 ) , Expression . Constant ( FormDataMapperOptions . MaxKeyBufferSize ) ) ) ) ;
2041+ Expression . Call ( AsMemoryMethod , formBuffer , Expression . Constant ( 0 ) , Expression . Constant ( formDataMapperOptions . MaxKeyBufferSize ) ) ) ) ;
20122042 // FormDataMapper.Map<string>(name_reader, FormDataMapperOptions);
20132043 var invokeMapMethodExpr = Expression . Call (
20142044 FormDataMapperMapMethod . MakeGenericMethod ( parameter . ParameterType ) ,
20152045 formReader ,
2016- Expression . Constant ( FormDataMapperOptions ) ) ;
2046+ Expression . Constant ( formDataMapperOptions ) ) ;
20172047 // if (form_buffer != null)
20182048 // {
20192049 // ArrayPool<char>.Shared.Return(form_buffer, false);
@@ -2039,15 +2069,15 @@ private static Expression BindComplexParameterFromFormItem(
20392069 ) ;
20402070 }
20412071
2042- private static void ProcessForm ( IFormCollection form , ref IReadOnlyDictionary < FormKey , StringValues > formDictionary , ref char [ ] buffer )
2072+ private static void ProcessForm ( IFormCollection form , int maxKeyBufferSize , ref IReadOnlyDictionary < FormKey , StringValues > formDictionary , ref char [ ] buffer )
20432073 {
20442074 var dictionary = new Dictionary < FormKey , StringValues > ( ) ;
20452075 foreach ( var ( key , value ) in form )
20462076 {
20472077 dictionary . Add ( new FormKey ( key . AsMemory ( ) ) , value ) ;
20482078 }
20492079 formDictionary = dictionary . AsReadOnly ( ) ;
2050- buffer = ArrayPool < char > . Shared . Rent ( FormDataMapperOptions . MaxKeyBufferSize ) ;
2080+ buffer = ArrayPool < char > . Shared . Rent ( maxKeyBufferSize ) ;
20512081 }
20522082
20532083 private static Expression BindParameterFromFormFiles (
@@ -2447,6 +2477,7 @@ private static class RequestDelegateFactoryConstants
24472477 public const string ServiceAttribute = "Service (Attribute)" ;
24482478 public const string FormFileAttribute = "Form File (Attribute)" ;
24492479 public const string FormAttribute = "Form (Attribute)" ;
2480+ public const string FormBindingAttribute = "Form Binding (Attribute)" ;
24502481 public const string RouteParameter = "Route (Inferred)" ;
24512482 public const string QueryStringParameter = "Query String (Inferred)" ;
24522483 public const string ServiceParameter = "Services (Inferred)" ;
0 commit comments