@@ -61,6 +61,7 @@ public static partial class RequestDelegateFactory
6161 private static readonly PropertyInfo RouteValuesIndexerProperty = typeof ( RouteValueDictionary ) . GetProperty ( "Item" ) ! ;
6262 private static readonly PropertyInfo HeaderIndexerProperty = typeof ( IHeaderDictionary ) . GetProperty ( "Item" ) ! ;
6363 private static readonly PropertyInfo FormFilesIndexerProperty = typeof ( IFormFileCollection ) . GetProperty ( "Item" ) ! ;
64+ private static readonly PropertyInfo FormIndexerProperty = typeof ( IFormCollection ) . GetProperty ( "Item" ) ! ;
6465
6566 private static readonly MethodInfo JsonResultWriteResponseAsyncMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( WriteJsonResponse ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
6667
@@ -110,6 +111,7 @@ public static partial class RequestDelegateFactory
110111
111112 private static readonly string [ ] DefaultAcceptsAndProducesContentType = new [ ] { JsonConstants . JsonContentType } ;
112113 private static readonly string [ ] FormFileContentType = new [ ] { "multipart/form-data" } ;
114+ private static readonly string [ ] FormContentType = new [ ] { "multipart/form-data" , "application/x-www-form-urlencoded" } ;
113115 private static readonly string [ ] PlaintextContentType = new [ ] { "text/plain" } ;
114116
115117 /// <summary>
@@ -377,6 +379,12 @@ private static Expression[] CreateArgumentsAndInferMetadata(MethodInfo methodInf
377379
378380 if ( ! factoryContext . MetadataAlreadyInferred )
379381 {
382+ if ( factoryContext . ReadForm )
383+ {
384+ // Add the Accepts metadata when reading from FORM.
385+ InferFormAcceptsMetadata ( factoryContext ) ;
386+ }
387+
380388 PopulateBuiltInResponseTypeMetadata ( methodInfo . ReturnType , factoryContext . EndpointBuilder ) ;
381389
382390 // Add metadata provided by the delegate return type and parameter types next, this will be more specific than inferred metadata from above
@@ -710,13 +718,22 @@ private static Expression CreateArgument(ParameterInfo parameter, RequestDelegat
710718
711719 return BindParameterFromFormFiles ( parameter , factoryContext ) ;
712720 }
713- else if ( parameter . ParameterType ! = typeof ( IFormFile ) )
721+ else if ( parameter . ParameterType = = typeof ( IFormFile ) )
714722 {
715- throw new NotSupportedException (
716- $ "{ nameof ( IFromFormMetadata ) } is only supported for parameters of type { nameof ( IFormFileCollection ) } and { nameof ( IFormFile ) } .") ;
723+ return BindParameterFromFormFile ( parameter , formAttribute . Name ?? parameter . Name , factoryContext , RequestDelegateFactoryConstants . FormFileAttribute ) ;
724+ }
725+ else if ( parameter . ParameterType == typeof ( IFormCollection ) )
726+ {
727+ if ( ! string . IsNullOrEmpty ( formAttribute . Name ) )
728+ {
729+ throw new NotSupportedException (
730+ $ "Assigning a value to the { nameof ( IFromFormMetadata ) } .{ nameof ( IFromFormMetadata . Name ) } property is not supported for parameters of type { nameof ( IFormCollection ) } .") ;
731+
732+ }
733+ return BindParameterFromFormCollection ( parameter , factoryContext ) ;
717734 }
718735
719- return BindParameterFromFormFile ( parameter , formAttribute . Name ?? parameter . Name , factoryContext , RequestDelegateFactoryConstants . FormFileAttribute ) ;
736+ return BindParameterFromFormItem ( parameter , formAttribute . Name ?? parameter . Name , factoryContext ) ;
720737 }
721738 else if ( parameter . CustomAttributes . Any ( a => typeof ( IFromServiceMetadata ) . IsAssignableFrom ( a . AttributeType ) ) )
722739 {
@@ -753,6 +770,10 @@ private static Expression CreateArgument(ParameterInfo parameter, RequestDelegat
753770 {
754771 return RequestAbortedExpr ;
755772 }
773+ else if ( parameter . ParameterType == typeof ( IFormCollection ) )
774+ {
775+ return BindParameterFromFormCollection ( parameter , factoryContext ) ;
776+ }
756777 else if ( parameter . ParameterType == typeof ( IFormFileCollection ) )
757778 {
758779 return BindParameterFromFormFiles ( parameter , factoryContext ) ;
@@ -1820,52 +1841,85 @@ private static void AddInferredAcceptsMetadata(RequestDelegateFactoryContext fac
18201841 factoryContext . EndpointBuilder . Metadata . Add ( new AcceptsMetadata ( type , factoryContext . AllowEmptyRequestBody , contentTypes ) ) ;
18211842 }
18221843
1823- private static Expression BindParameterFromFormFiles (
1824- ParameterInfo parameter ,
1825- RequestDelegateFactoryContext factoryContext )
1844+ private static void InferFormAcceptsMetadata ( RequestDelegateFactoryContext factoryContext )
18261845 {
1827- if ( factoryContext . FirstFormRequestBodyParameter is null )
1846+ if ( factoryContext . ReadFormFile )
18281847 {
1829- factoryContext . FirstFormRequestBodyParameter = parameter ;
1848+ AddInferredAcceptsMetadata ( factoryContext , factoryContext . FirstFormRequestBodyParameter ! . ParameterType , FormFileContentType ) ;
18301849 }
1831-
1832- factoryContext . TrackedParameters . Add ( parameter . Name ! , RequestDelegateFactoryConstants . FormFileParameter ) ;
1833-
1834- // Do not duplicate the metadata if there are multiple form parameters
1835- if ( ! factoryContext . ReadForm )
1850+ else
18361851 {
1837- AddInferredAcceptsMetadata ( factoryContext , parameter . ParameterType , FormFileContentType ) ;
1852+ AddInferredAcceptsMetadata ( factoryContext , factoryContext . FirstFormRequestBodyParameter ! . ParameterType , FormContentType ) ;
18381853 }
1854+ }
18391855
1856+ private static Expression BindParameterFromFormCollection (
1857+ ParameterInfo parameter ,
1858+ RequestDelegateFactoryContext factoryContext )
1859+ {
1860+ factoryContext . FirstFormRequestBodyParameter ??= parameter ;
1861+ factoryContext . TrackedParameters . Add ( parameter . Name ! , RequestDelegateFactoryConstants . FormCollectionParameter ) ;
18401862 factoryContext . ReadForm = true ;
18411863
1842- return BindParameterFromExpression ( parameter , FormFilesExpr , factoryContext , "body" ) ;
1864+ return BindParameterFromExpression (
1865+ parameter ,
1866+ FormExpr ,
1867+ factoryContext ,
1868+ "body" ) ;
18431869 }
18441870
1845- private static Expression BindParameterFromFormFile (
1871+ private static Expression BindParameterFromFormItem (
18461872 ParameterInfo parameter ,
18471873 string key ,
1848- RequestDelegateFactoryContext factoryContext ,
1849- string trackedParameterSource )
1874+ RequestDelegateFactoryContext factoryContext )
18501875 {
1851- if ( factoryContext . FirstFormRequestBodyParameter is null )
1852- {
1853- factoryContext . FirstFormRequestBodyParameter = parameter ;
1854- }
1876+ var valueExpression = GetValueFromProperty ( FormExpr , FormIndexerProperty , key , GetExpressionType ( parameter . ParameterType ) ) ;
18551877
1856- factoryContext . TrackedParameters . Add ( key , trackedParameterSource ) ;
1878+ factoryContext . FirstFormRequestBodyParameter ??= parameter ;
1879+ factoryContext . TrackedParameters . Add ( key , RequestDelegateFactoryConstants . FormAttribute ) ;
1880+ factoryContext . ReadForm = true ;
18571881
1858- // Do not duplicate the metadata if there are multiple form parameters
1859- if ( ! factoryContext . ReadForm )
1860- {
1861- AddInferredAcceptsMetadata ( factoryContext , parameter . ParameterType , FormFileContentType ) ;
1862- }
1882+ return BindParameterFromValue (
1883+ parameter ,
1884+ valueExpression ,
1885+ factoryContext ,
1886+ "form" ) ;
1887+ }
18631888
1889+ private static Expression BindParameterFromFormFiles (
1890+ ParameterInfo parameter ,
1891+ RequestDelegateFactoryContext factoryContext )
1892+ {
1893+ factoryContext . FirstFormRequestBodyParameter ??= parameter ;
1894+ factoryContext . TrackedParameters . Add ( parameter . Name ! , RequestDelegateFactoryConstants . FormFileParameter ) ;
18641895 factoryContext . ReadForm = true ;
1896+ factoryContext . ReadFormFile = true ;
1897+
1898+ return BindParameterFromExpression (
1899+ parameter ,
1900+ FormFilesExpr ,
1901+ factoryContext ,
1902+ "body" ) ;
1903+ }
18651904
1905+ private static Expression BindParameterFromFormFile (
1906+ ParameterInfo parameter ,
1907+ string key ,
1908+ RequestDelegateFactoryContext factoryContext ,
1909+ string trackedParameterSource )
1910+ {
18661911 var valueExpression = GetValueFromProperty ( FormFilesExpr , FormFilesIndexerProperty , key , typeof ( IFormFile ) ) ;
18671912
1868- return BindParameterFromExpression ( parameter , valueExpression , factoryContext , "form file" ) ;
1913+ factoryContext . FirstFormRequestBodyParameter ??= parameter ;
1914+ factoryContext . TrackedParameters . Add ( key , trackedParameterSource ) ;
1915+ factoryContext . ReadForm = true ;
1916+ factoryContext . ReadFormFile = true ;
1917+
1918+ return BindParameterFromExpression (
1919+ parameter ,
1920+ valueExpression ,
1921+ factoryContext ,
1922+ "form file" ) ;
18691923 }
18701924
18711925 private static Expression BindParameterFromBody ( ParameterInfo parameter , bool allowEmpty , RequestDelegateFactoryContext factoryContext )
@@ -2210,12 +2264,14 @@ private static class RequestDelegateFactoryConstants
22102264 public const string BodyAttribute = "Body (Attribute)" ;
22112265 public const string ServiceAttribute = "Service (Attribute)" ;
22122266 public const string FormFileAttribute = "Form File (Attribute)" ;
2267+ public const string FormAttribute = "Form (Attribute)" ;
22132268 public const string RouteParameter = "Route (Inferred)" ;
22142269 public const string QueryStringParameter = "Query String (Inferred)" ;
22152270 public const string ServiceParameter = "Services (Inferred)" ;
22162271 public const string BodyParameter = "Body (Inferred)" ;
22172272 public const string RouteOrQueryStringParameter = "Route or Query String (Inferred)" ;
22182273 public const string FormFileParameter = "Form File (Inferred)" ;
2274+ public const string FormCollectionParameter = "Form Collection (Inferred)" ;
22192275 public const string PropertyAsParameter = "As Parameter (Attribute)" ;
22202276 }
22212277
0 commit comments