-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Initial Form-binding support #44653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
brunolins16
merged 9 commits into
dotnet:main
from
brunolins16:brunolins/minimal-apis/form-support-v1
Nov 8, 2022
Merged
Initial Form-binding support #44653
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
dd6fabb
Adding initial Form-support
brunolins16 833ce38
clean up
brunolins16 c931b8e
PR feedback
brunolins16 65745d3
PR feedback
brunolins16 dadca52
Fix unit test
brunolins16 5c892fe
Adding Form Accepts Metadata later
brunolins16 455735e
clean up
brunolins16 a8c18b3
Fix warnings
brunolins16 1b4a884
Adding a test for InferMetadata
brunolins16 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,7 @@ public static partial class RequestDelegateFactory | |
private static readonly PropertyInfo RouteValuesIndexerProperty = typeof(RouteValueDictionary).GetProperty("Item")!; | ||
private static readonly PropertyInfo HeaderIndexerProperty = typeof(IHeaderDictionary).GetProperty("Item")!; | ||
private static readonly PropertyInfo FormFilesIndexerProperty = typeof(IFormFileCollection).GetProperty("Item")!; | ||
private static readonly PropertyInfo FormIndexerProperty = typeof(IFormCollection).GetProperty("Item")!; | ||
|
||
private static readonly MethodInfo JsonResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(WriteJsonResponse), BindingFlags.NonPublic | BindingFlags.Static)!; | ||
|
||
|
@@ -110,6 +111,7 @@ public static partial class RequestDelegateFactory | |
|
||
private static readonly string[] DefaultAcceptsAndProducesContentType = new[] { JsonConstants.JsonContentType }; | ||
private static readonly string[] FormFileContentType = new[] { "multipart/form-data" }; | ||
private static readonly string[] FormContentType = new[] { "multipart/form-data", "application/x-www-form-urlencoded" }; | ||
private static readonly string[] PlaintextContentType = new[] { "text/plain" }; | ||
|
||
/// <summary> | ||
|
@@ -377,6 +379,12 @@ private static Expression[] CreateArgumentsAndInferMetadata(MethodInfo methodInf | |
|
||
if (!factoryContext.MetadataAlreadyInferred) | ||
{ | ||
if (factoryContext.ReadForm) | ||
{ | ||
// Add the Accepts metadata when reading from FORM. | ||
InferFormAcceptsMetadata(factoryContext); | ||
} | ||
|
||
PopulateBuiltInResponseTypeMetadata(methodInfo.ReturnType, factoryContext.EndpointBuilder); | ||
|
||
// 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 | |
|
||
return BindParameterFromFormFiles(parameter, factoryContext); | ||
} | ||
else if (parameter.ParameterType != typeof(IFormFile)) | ||
else if (parameter.ParameterType == typeof(IFormFile)) | ||
{ | ||
throw new NotSupportedException( | ||
$"{nameof(IFromFormMetadata)} is only supported for parameters of type {nameof(IFormFileCollection)} and {nameof(IFormFile)}."); | ||
return BindParameterFromFormFile(parameter, formAttribute.Name ?? parameter.Name, factoryContext, RequestDelegateFactoryConstants.FormFileAttribute); | ||
} | ||
else if (parameter.ParameterType == typeof(IFormCollection)) | ||
{ | ||
if (!string.IsNullOrEmpty(formAttribute.Name)) | ||
{ | ||
throw new NotSupportedException( | ||
$"Assigning a value to the {nameof(IFromFormMetadata)}.{nameof(IFromFormMetadata.Name)} property is not supported for parameters of type {nameof(IFormCollection)}."); | ||
|
||
} | ||
return BindParameterFromFormCollection(parameter, factoryContext); | ||
} | ||
|
||
return BindParameterFromFormFile(parameter, formAttribute.Name ?? parameter.Name, factoryContext, RequestDelegateFactoryConstants.FormFileAttribute); | ||
return BindParameterFromFormItem(parameter, formAttribute.Name ?? parameter.Name, factoryContext); | ||
} | ||
else if (parameter.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType))) | ||
{ | ||
|
@@ -753,6 +770,10 @@ private static Expression CreateArgument(ParameterInfo parameter, RequestDelegat | |
{ | ||
return RequestAbortedExpr; | ||
} | ||
else if (parameter.ParameterType == typeof(IFormCollection)) | ||
{ | ||
return BindParameterFromFormCollection(parameter, factoryContext); | ||
} | ||
else if (parameter.ParameterType == typeof(IFormFileCollection)) | ||
{ | ||
return BindParameterFromFormFiles(parameter, factoryContext); | ||
|
@@ -1820,52 +1841,85 @@ private static void AddInferredAcceptsMetadata(RequestDelegateFactoryContext fac | |
factoryContext.EndpointBuilder.Metadata.Add(new AcceptsMetadata(type, factoryContext.AllowEmptyRequestBody, contentTypes)); | ||
} | ||
|
||
private static Expression BindParameterFromFormFiles( | ||
ParameterInfo parameter, | ||
RequestDelegateFactoryContext factoryContext) | ||
private static void InferFormAcceptsMetadata(RequestDelegateFactoryContext factoryContext) | ||
{ | ||
if (factoryContext.FirstFormRequestBodyParameter is null) | ||
if (factoryContext.ReadFormFile) | ||
{ | ||
factoryContext.FirstFormRequestBodyParameter = parameter; | ||
AddInferredAcceptsMetadata(factoryContext, factoryContext.FirstFormRequestBodyParameter!.ParameterType, FormFileContentType); | ||
} | ||
|
||
factoryContext.TrackedParameters.Add(parameter.Name!, RequestDelegateFactoryConstants.FormFileParameter); | ||
|
||
// Do not duplicate the metadata if there are multiple form parameters | ||
if (!factoryContext.ReadForm) | ||
else | ||
{ | ||
AddInferredAcceptsMetadata(factoryContext, parameter.ParameterType, FormFileContentType); | ||
AddInferredAcceptsMetadata(factoryContext, factoryContext.FirstFormRequestBodyParameter!.ParameterType, FormContentType); | ||
} | ||
} | ||
|
||
private static Expression BindParameterFromFormCollection( | ||
ParameterInfo parameter, | ||
RequestDelegateFactoryContext factoryContext) | ||
{ | ||
factoryContext.FirstFormRequestBodyParameter ??= parameter; | ||
factoryContext.TrackedParameters.Add(parameter.Name!, RequestDelegateFactoryConstants.FormCollectionParameter); | ||
factoryContext.ReadForm = true; | ||
|
||
return BindParameterFromExpression(parameter, FormFilesExpr, factoryContext, "body"); | ||
return BindParameterFromExpression( | ||
parameter, | ||
FormExpr, | ||
factoryContext, | ||
"body"); | ||
} | ||
|
||
private static Expression BindParameterFromFormFile( | ||
private static Expression BindParameterFromFormItem( | ||
ParameterInfo parameter, | ||
string key, | ||
RequestDelegateFactoryContext factoryContext, | ||
string trackedParameterSource) | ||
RequestDelegateFactoryContext factoryContext) | ||
{ | ||
if (factoryContext.FirstFormRequestBodyParameter is null) | ||
{ | ||
factoryContext.FirstFormRequestBodyParameter = parameter; | ||
} | ||
var valueExpression = GetValueFromProperty(FormExpr, FormIndexerProperty, key, GetExpressionType(parameter.ParameterType)); | ||
|
||
factoryContext.TrackedParameters.Add(key, trackedParameterSource); | ||
factoryContext.FirstFormRequestBodyParameter ??= parameter; | ||
factoryContext.TrackedParameters.Add(key, RequestDelegateFactoryConstants.FormAttribute); | ||
factoryContext.ReadForm = true; | ||
|
||
// Do not duplicate the metadata if there are multiple form parameters | ||
if (!factoryContext.ReadForm) | ||
{ | ||
AddInferredAcceptsMetadata(factoryContext, parameter.ParameterType, FormFileContentType); | ||
} | ||
return BindParameterFromValue( | ||
parameter, | ||
valueExpression, | ||
factoryContext, | ||
"form"); | ||
} | ||
|
||
private static Expression BindParameterFromFormFiles( | ||
ParameterInfo parameter, | ||
RequestDelegateFactoryContext factoryContext) | ||
{ | ||
factoryContext.FirstFormRequestBodyParameter ??= parameter; | ||
factoryContext.TrackedParameters.Add(parameter.Name!, RequestDelegateFactoryConstants.FormFileParameter); | ||
factoryContext.ReadForm = true; | ||
factoryContext.ReadFormFile = true; | ||
|
||
return BindParameterFromExpression( | ||
parameter, | ||
FormFilesExpr, | ||
factoryContext, | ||
"body"); | ||
} | ||
|
||
private static Expression BindParameterFromFormFile( | ||
ParameterInfo parameter, | ||
string key, | ||
RequestDelegateFactoryContext factoryContext, | ||
string trackedParameterSource) | ||
{ | ||
var valueExpression = GetValueFromProperty(FormFilesExpr, FormFilesIndexerProperty, key, typeof(IFormFile)); | ||
|
||
return BindParameterFromExpression(parameter, valueExpression, factoryContext, "form file"); | ||
factoryContext.FirstFormRequestBodyParameter ??= parameter; | ||
factoryContext.TrackedParameters.Add(key, trackedParameterSource); | ||
factoryContext.ReadForm = true; | ||
factoryContext.ReadFormFile = true; | ||
|
||
return BindParameterFromExpression( | ||
parameter, | ||
valueExpression, | ||
factoryContext, | ||
"form file"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: make this a constant? |
||
} | ||
|
||
private static Expression BindParameterFromBody(ParameterInfo parameter, bool allowEmpty, RequestDelegateFactoryContext factoryContext) | ||
|
@@ -2210,12 +2264,14 @@ private static class RequestDelegateFactoryConstants | |
public const string BodyAttribute = "Body (Attribute)"; | ||
public const string ServiceAttribute = "Service (Attribute)"; | ||
public const string FormFileAttribute = "Form File (Attribute)"; | ||
public const string FormAttribute = "Form (Attribute)"; | ||
public const string RouteParameter = "Route (Inferred)"; | ||
public const string QueryStringParameter = "Query String (Inferred)"; | ||
public const string ServiceParameter = "Services (Inferred)"; | ||
public const string BodyParameter = "Body (Inferred)"; | ||
public const string RouteOrQueryStringParameter = "Route or Query String (Inferred)"; | ||
public const string FormFileParameter = "Form File (Inferred)"; | ||
public const string FormCollectionParameter = "Form Collection (Inferred)"; | ||
brunolins16 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public const string PropertyAsParameter = "As Parameter (Attribute)"; | ||
} | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.