Skip to content
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

Merged
merged 10 commits into from
Sep 15, 2023

Conversation

captainsafia
Copy link
Member

Addresses #49526.

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Sep 6, 2023
@captainsafia captainsafia force-pushed the safia/formfile-fix branch 2 times, most recently from f050a94 to bd0082e Compare September 6, 2023 02:05
@captainsafia captainsafia marked this pull request as ready for review September 6, 2023 20:58
@javiercn
Copy link
Member

javiercn commented Sep 8, 2023

@captainsafia this looks great!

One last nit. Do you think we can make it support IBrowserFile.cs since that's the "Blazor" specific abstraction for it? I believe it should be pretty straightforwards, but if not, let me know.

return true;
}

var file = formFileCollection.GetFile(reader.CurrentPrefix.ToString());
Copy link
Member

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
    <input type="file" name="Resume" />
    <input type="file" name="Degree" />
    
    with a model like
    public class MyModel
    {
        public IFormFile Resume { get; set; }
        public IFormFile Degree { get; set; }
    }
  • Multiple file inputs binding to collections of files
    <input type="file" multiple name="Reports" />
    <input type="file" multiple name="OtherDocuments" />
    
    with a model like
    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>

Copy link
Member Author

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 IFormFiles with different names and erroring for multiple files with the same name makes sense though.

Copy link
Member

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.

Copy link
Member Author

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.

@javiercn javiercn mentioned this pull request Sep 11, 2023
1 task
@captainsafia captainsafia changed the title Support resolving IFormFile in complex form mapping Support resolving form files in complex form mapping Sep 14, 2023
{ typeof(IFormFile), new FileConverter<IFormFile>() },
{ typeof(IReadOnlyList<IFormFile>), new FileConverter<IReadOnlyList<IFormFile>>() },
#if COMPONENTS
{ typeof(IBrowserFile), new FileConverter<IBrowserFile>() }
Copy link
Member

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

Copy link
Member Author

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" />
Copy link
Member

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member

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

@captainsafia captainsafia requested review from wtgodbe and a team as code owners September 14, 2023 18:52
<div>
<label for="Model.Documents">
Header Photo:
<InputFile @bind-Value="Model.HeaderPhoto" name="Model.HeaderPhoto" />
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member

@javiercn javiercn left a 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants