Skip to content

Conversation

@guardrex
Copy link
Collaborator

@guardrex guardrex commented Sep 23, 2024

Fixes #33692
Fixes #33850 ... Adds a Security considerations section with two subsections: One is on avoiding IBrowserFile.Size for file size limits. The other is on not using a client-supplied file name for saving a file to physical storage.

Notes ...

  • Tested working 🎉🍻 with ...
    • BWA (Interactive WebAssembly rendered component to server project)
    • Standalone Blazor WebAssembly app to a web API.
  • Eventually, a fully working demo for both BWA and WASM/web API can be placed into sample apps for easy demo by users. I've made a tracking note to address it later.

BIG ❓...

My understanding is that request streaming gracefully degrades when any of the following conditions fail:

  • Chromium-based browser
  • HTTP/2
  • CORS (preflight must succeed)
  • HTTPS

However ...

For a Safari/FF user and a large file upload, this current guidance is going to 💥. We could take any of the following approaches ...

  1. Do nothing outside of just letting readers know that this feature is only supported for Chromium browsers at this time.
  2. Add code: If the user agent is a non-Chromium browser, prevent the user from uploading a large file.
  3. Add code: If the user agent is a non-Chromium browser, don't post with multipart body. Take Javier's advice and set up the component to use HTTP Ranges if the browser is detected as non-Chromium. We've said in the article to use Ranges in large file upload scenarios but never actually showed how to do it. I checked the Web for good examples, and I didn't find any ... just bits of code and passing remarks.

For 2 or 3 ☝️, tell me if checking the user agent via JS interop is the right way to go. I mean there's nothing else available via API that I can check to determine if it's a non-Chromium browser, correct?

If we're going with showing an HTTP Ranges fallback, my 🦖 hacks with MultipartContent("byteranges", "..."); didn't really result in a working demo. It is best if a PU engineer provide a fully-working client-server example of the HTTP Ranges approach.

... and one more related ❓ ...

This is CSR right now for the BWA side ... Interactive WASM ... no IsBrowser check and no alternative processing required in this section/demo. Up to this point, I was just trying to get it working in the CSR case for the CSR section. I haven't delved into Auto rendering yet. Do you want to get into the weeds with the Auto scenario (i.e., alternative services for server versus client), or do you want to wait and think about that later?


Internal previews

📄 File 🔗 Preview link
aspnetcore/blazor/call-web-api.md aspnetcore/blazor/call-web-api
aspnetcore/blazor/file-uploads.md aspnetcore/blazor/file-uploads

@guardrex guardrex self-assigned this Sep 23, 2024
@guardrex
Copy link
Collaborator Author

@pavelsavara ... Do you know if dotnet/runtime#91699 has made it into the latest SDK that I think I can get, which is 9.0.100-rc.2.24474.4?

I'm still hitting the out of memory error with large file uploads using our published example code under the public RC1 release (9.0.0-rc.1.24431.7), which I think will work (in Edge) if I get the SDK that has dotnet/runtime#91699 in it.

@pavelsavara
Copy link
Member

It should be there. Which sample code ?

@guardrex
Copy link
Collaborator Author

This ...

https://learn.microsoft.com/en-us/aspnet/core/blazor/file-uploads?view=aspnetcore-8.0#upload-files-to-a-server-with-client-side-rendering-csr

There are two spots that we'll need coverage ...

  • In the File Uploads article, which is on the PR in draft form. The question is if request streaming will ✨ Just Work!™ ✨ without code changes to the FileUpload2 component IF they're using a Chromium browser? If request streaming isn't available (Safari/FF), doesn't request streaming gracefully fall back? ... noting that we never maintained content on Javier's suggestion to developers looking for client-side large file uploads to create a custom HTTP Ranges-based file upload component. We say that's what to do, but we never showed an example of it. I wonder if we need to show how to use HTTP Ranges in case request streaming isn't available?
  • We also need coverage in the Call web API article, which I'm working on now locally (i.e., it isn't on the PR yet). There, I'll be adding a section and describing the SetBrowserRequestStreamingEnabled extension method for HttpRequestMessage.

@guardrex
Copy link
Collaborator Author

guardrex commented Sep 24, 2024

If the FileUpload2 component IS going to change, then I'll need the updated component to place in versioned content. Can you (or someone) send me the new component to replace it for >=9.0?

UPDATE: Nevermind on that! I have the code generally working, so I'll float what I have on the PR for review.

I also have a side question WRT the LazyBrowserFileStream part that @MackinnonBuck provided ... Mackinnon ... is that to be versioned OUT 🔪 at >=9.0? IIRC, it was a temporary workaround that can go away at 9.0. That's what the comments that I left for myself in the article seem to indicate. It looks like we'll carry the LazyBrowserFileStream approach <9.0 and version it out >=9.0 in favor of just a stream.

@pavelsavara
Copy link
Member

Something like below will enable that for Chrome/Edge.

var webAssemblyEnableStreamingRequestKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingRequest");
var req = new HttpRequestMessage(HttpMethod.Post, url);
req.Options.Set(webAssemblyEnableStreamingRequestKey, true);

@guardrex
Copy link
Collaborator Author

guardrex commented Sep 24, 2024

So, you're saying that we'll have to move away from an HttpClient.PostAsync(string, HttpContent) to a HttpClient.SendAsync(HttpRequestMessage) ... that's going to be a firm requirement for it to work?

The reason that I thought the code wouldn't need to change is that I thought the inner workings of HttpClient.PostAsync would perform the checks for HTTP/2 and IsBrowser and automatically set the streaming request key ... that it would do internal magic ✨ to make streaming requests work. I guess you're saying that's not the case.

I have a local test environment here for this. I'll refactor it into an HttpClient.SendAsync(HttpRequestMessage) approach to see if I can get it working. I'll report back in a bit how it went.

@guardrex
Copy link
Collaborator Author

Can't I just do it this way? ...

var req = new HttpRequestMessage(HttpMethod.Post, "/Filesave");
req.SetBrowserRequestStreamingEnabled(true);

@pavelsavara
Copy link
Member

My point was to use the WebAssemblyEnableStreamingRequest option, not how to set it.

@guardrex
Copy link
Collaborator Author

guardrex commented Sep 24, 2024

Latest code is ...

var req = new HttpRequestMessage(HttpMethod.Post, "/Filesave");
req.SetBrowserRequestStreamingEnabled(true);
req.Version = HttpVersion.Version20;
req.Content = content;
var response = await Http.SendAsync(req);

Works for small files, but for a 1 GB file, I'm getting a Kestrel(?) exception ...

"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
  "": [
    "Failed to read the request form. Request body too large. The max request body size is 30000000 bytes."
  ]

@pavelsavara
Copy link
Member

"Failed to read the request form. Request body too large. The max request body size is 30000000 bytes."

That's actually server side error. Try setting MaxRequestBodySize on your server

@pavelsavara
Copy link
Member

req.Version = HttpVersion.Version20; is not necessary, doesn't do anything.

@guardrex
Copy link
Collaborator Author

I hit another limit on the server ...

"Failed to read the request form. Multipart body length limit 134217728 exceeded."

I'll see about extending that one.

@guardrex
Copy link
Collaborator Author

Lick'd IT! 🍻🕺💃🎉

Ok ... I'll get this all into the PR. I'll ping for reviews Wednesday when it's all in place.

image

@guardrex
Copy link
Collaborator Author

guardrex commented Sep 25, 2024

@danroth27 @mkArtakMSFT @pavelsavara @maraf ... See the NOTES that I just added to the opening comment ☝️.

This is ready for a look and discussion on next steps.

Copy link
Member

@danroth27 danroth27 left a comment

Choose a reason for hiding this comment

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

I'm going make some executive decisions to get this PR unblocked:

  • For non-Chromium browsers let's go with option 1, which is to do nothing outside of just letting readers know that this feature is only supported for Chromium browsers at this time.
  • Let's drop the recommendation to use HTTP range requests as a fallback behavior given that we haven't actually proven that approach with a sample.
  • Let's not bother with trying to document how to deal specifically with the auto render mode.

@lewing @pavelsavara @javiercn If you disagree with any of these decisions, then please open a separate doc issue with how you'd like them handled instead.

@guardrex
Copy link
Collaborator Author

guardrex commented Mar 4, 2025

@danroth27 ... I'll react to your decisions in the morning.

@lewing @pavelsavara @javiercn ... If you need to open an issue, please open it from the bottom of this article ...

https://learn.microsoft.com/en-us/aspnet/core/blazor/file-uploads?view=aspnetcore-9.0#additional-resources

@guardrex
Copy link
Collaborator Author

guardrex commented Mar 4, 2025

@danroth27 ... Based on your remarks earlier about not cross-linking the PU discussions on this, I simplified the File size read and upload limits section down to just ...

:::moniker range=">= aspnetcore-9.0"

For Chromium-based browsers (for example, Google Chrome and Microsoft Edge) using the HTTP/2 protocol, HTTPS, and [CORS](xref:security/cors), client-side Blazor supports using the [Streams API](https://developer.mozilla.org/docs/Web/API/Streams_API) to permit uploading large files with [request streaming](xref:blazor/call-web-api#client-side-request-streaming).

Without a Chromium browser, HTTP/2 protocol, or HTTPS, client-side Blazor reads the file's bytes into a single JavaScript array buffer when marshaling the data from JavaScript to C#, which is limited to 2 GB or to the device's available memory. Large file uploads may fail for client-side uploads using the <xref:Microsoft.AspNetCore.Components.Forms.InputFile> component.

:::moniker-end

:::moniker range="< aspnetcore-9.0"

Client-side Blazor reads the file's bytes into a single JavaScript array buffer when marshaling the data from JavaScript to C#, which is limited to 2 GB or to the device's available memory. Large file uploads may fail for client-side uploads using the <xref:Microsoft.AspNetCore.Components.Forms.InputFile> component. We recommend adopting [request streaming](xref:blazor/call-web-api?view=aspnetcore-9.0&preserve-view=true#client-side-request-streaming) with ASP.NET Core 9.0 or later.

:::moniker-end

Otherwise, there are no changes for the other two items ...

  • 'do nothing outside of just letting readers know that this feature is only supported for Chromium browsers at this time'
  • 'trying to document how to deal specifically with the auto render mode'

@danroth27
Copy link
Member

:shipit:

@guardrex guardrex merged commit b83f57d into main Mar 4, 2025
3 checks passed
@guardrex guardrex deleted the guardrex/blazor-request-streaming-upload branch March 4, 2025 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Additional security considerations for the input file component Request Streaming upload for client-side Blazor 9.0

5 participants