-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
Support resolving form files in complex form mapping #50537
Conversation
f050a94
to
bd0082e
Compare
src/Components/Endpoints/src/FormMapping/Converters/FileConverter.cs
Outdated
Show resolved
Hide resolved
src/Components/Endpoints/src/FormMapping/Factories/FileConverterFactory.cs
Outdated
Show resolved
Hide resolved
src/Components/Endpoints/src/FormMapping/FormDataMapperOptions.cs
Outdated
Show resolved
Hide resolved
@captainsafia this looks great! One last nit. Do you think we can make it support |
return true; | ||
} | ||
|
||
var file = formFileCollection.GetFile(reader.CurrentPrefix.ToString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took another look, and this might be a bit more nuanced than we think.
For reference, MVC seems to only support binding files as a Top-level property/object, (which is fine, we can decide to do something different here), but I believe we need to be careful and make sure that the following scenarios work:
- Multiple file inputs binding to a single file
with a model like
<input type="file" name="Resume" /> <input type="file" name="Degree" />
public class MyModel { public IFormFile Resume { get; set; } public IFormFile Degree { get; set; } }
- Multiple file inputs binding to collections of files
with a model like
<input type="file" multiple name="Reports" /> <input type="file" multiple name="OtherDocuments" />
public class MyModel { public IFormFileCollection Reports { get; set; } public IFormFile OtherDocuments { get; set; } }
- We should also probably produce an error if there are many files with the same name and we are trying to bind to an
IFormFile
This is a sample payload that chrome will send for input and input multiple:
------WebKitFormBoundaryTxMDCYZlym9qHpwC
Content-Disposition: form-data; name="SampleFile"; filename="MySampleFile.txt"
Content-Type: text/plain
------WebKitFormBoundaryTxMDCYZlym9qHpwC
Content-Disposition: form-data; name="OtherFile"; filename="MyOtherFile.txt"
Content-Type: text/plain
------WebKitFormBoundaryTxMDCYZlym9qHpwC
Content-Disposition: form-data; name="Reports"; filename="MyReports2.txt"
Content-Type: text/plain
------WebKitFormBoundaryTxMDCYZlym9qHpwC
Content-Disposition: form-data; name="Reports"; filename="MyReports.txt"
Content-Type: text/plain
------WebKitFormBoundaryTxMDCYZlym9qHpwC
Content-Disposition: form-data; name="OtherDocuments"; filename="MyOtherDocuments2.txt"
Content-Type: text/plain
------WebKitFormBoundaryTxMDCYZlym9qHpwC
Content-Disposition: form-data; name="OtherDocuments"; filename="MyOtherDocuments.txt"
Content-Type: text/plain
------WebKitFormBoundaryTxMDCYZlym9qHpwC
Content-Disposition: form-data; name="__RequestVerificationToken"
CfDJ8NQYaZebqUlGr0rlYIlnIDg5dKVRjgYAN_qNiT2n1Fw0M3XRiweKM4YcI2rzoBE9BBzidDyoPzG7YumuVUkV5qNUz0GIEpBGlNb2muhD-OoFPhKZoHLXKzQV0StDU0_iVSDXivsQu2RVtcOBPOdA-lg
------WebKitFormBoundaryTxMDCYZlym9qHpwC--
The contents of the files are not shown because the chrome dev tools scraps them. The form that I used is like this:
<form method="post" enctype="multipart/form-data">
<input type="file" name="SampleFile" />
<input type="file" name="OtherFile" />
<input type="file" multiple name="Reports" />
<input type="file" multiple name="OtherDocuments" />
<input type="submit" value="Upload" />
</form>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public IFormFileCollection Reports { get; set; }
This one is interesting. At the moment, minimal APIs assumes that a top-level IFormFileCollection
type refers to HttpContext.Request.Form.Files
in all occasions. We don't use the parameter name to extract a subset of files by their name. IMO, it probably makes more sense to support that by supporting an IReadOnlyList<IFormFile>
type similar to what IFormFIleCollection.GetFiles
returns.
Binding for multiple IFormFile
s with different names and erroring for multiple files with the same name makes sense though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@captainsafia I'm ok if we keep IFormFileCollection
binding the entire set of files independent of the prefix (the type acts as the signal/discriminator) of what you want, and it's a well-defined behavior.
What I am interested in making sure that we do correctly is handling input type=file
and input type=file multiple
in a way that people can either use the inputs directly or rely on InputFile
to author the UI.
We need to get this right because whatever we bake in now, will become a breaking change in the future otherwise. So I think your proposal of having IFormFileCollection bind to the entire thing and have <<(Collection|Dictionary)Type>><IFormFile>
bind to the set that match the proposed subset makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I've updated the PR to accommodate some of the functionality here. InputFile works with:
- IBrowserFile
- IFormFile
- IReadOnlyList
- IFormFileCollection
as types backing the property.
Binding for multiple IFormFiles with different names and erroring for multiple files with the same name makes sense though.
I take this back. It appears the current behavior for IFormFileCollection
is to return the first file with a given name if they are multiple so I kept this pattern in the implementation.
6b3209d
to
3a2c2a8
Compare
{ typeof(IFormFile), new FileConverter<IFormFile>() }, | ||
{ typeof(IReadOnlyList<IFormFile>), new FileConverter<IReadOnlyList<IFormFile>>() }, | ||
#if COMPONENTS | ||
{ typeof(IBrowserFile), new FileConverter<IBrowserFile>() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can we also do IReadOnlyList<IBrowserFile>
? Otherwise it can't be used on RCLs if they want to support wasm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
<div> | ||
<label for="Model.ProfilePicture"> | ||
Profile Picture: | ||
<InputFile @bind-Value="Model.ProfilePicture" name="Model.ProfilePicture" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you copy the logic from https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/Forms/InputBase.cs#L199-L221 on to InputFile so that it doesn't require specifying the name manually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up keeping this change out of this PR to keep the scope smaller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair. I'll file an issue for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed #50728 to track it
<div> | ||
<label for="Model.Documents"> | ||
Header Photo: | ||
<InputFile @bind-Value="Model.HeaderPhoto" name="Model.HeaderPhoto" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InputFile
doesn't have any Value
you can bind to, so this looks like a mistake. I was surprised at first that the compiler even allowed this, but it must follow from the fact that we don't restrict passing parameters to ones that actually exist. It's just unfortunate that the editor highlights @bind-Anything
in purple as if it's definitely correct.
For these E2E tests, the right move is probably to replace <InputFile @bind-Value>
with <input type="file" name="Model.HeaderPhoto">
etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InputFile doesn't have any Value you can bind to, so this looks like a mistake. I was surprised at first that the compiler even allowed this, but it must follow from the fact that we don't restrict passing parameters to ones that actually exist.
Yep, this is correct. The name
attribute is the only reason this works correctly for posted forms.
For these E2E tests, the right move is probably to replace <InputFile @bind-Value> with etc.
I'm fine with this. I assume we'll use #50728 to track whether InputFile
should be updated to more closely match InputBase
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This was a small oversight on my part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
Signing-off to make sure you aren't blocked on me.
Addresses #49526.