-
Notifications
You must be signed in to change notification settings - Fork 10.5k
InputFile Component #24640
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
Merged
InputFile Component #24640
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
d477b92
Started on general InputFile design.
MackinnonBuck 95cf907
Started work on data streaming.
MackinnonBuck 58760ba
WebAssembly file upload is functional.
MackinnonBuck 6ae119c
Blazor Server file upload is functional.
MackinnonBuck d744e58
Image file support + documentation.
MackinnonBuck 8e81b4e
Addressed CR feedback.
MackinnonBuck b6fdcf7
CR feedback
MackinnonBuck f273793
Update PrerenderedStartup.cs
MackinnonBuck 29a6b9b
Merge branch 'master' of https://github.com/dotnet/aspnetcore into t-…
MackinnonBuck bf81ddf
Added E2E tests
MackinnonBuck d079804
Added Memory<byte> ReadAsync overload in BrowserFileStream
MackinnonBuck 9eacd9a
Update PrerenderedStartup.cs
MackinnonBuck 6e944be
Update AspNetCore.sln
MackinnonBuck eb7bbe1
CR feedback + integrated System.IO.Pipelines
MackinnonBuck 6aced8a
Missing files from last commit.
MackinnonBuck d5bcd9d
Delete EncodedFileChunk.cs
MackinnonBuck 624e62c
Removed unnecessary chunk buffer.
MackinnonBuck e7ece33
Addressed some CR feedback
MackinnonBuck 751a0ae
More CR feedback
MackinnonBuck cc41b25
CR feedback
MackinnonBuck 313a70e
Update RemoteBrowserFileStreamOptions.cs
MackinnonBuck 3f4dfbf
Merge branch 'release/5.0' into t-mabuc/input-file
MackinnonBuck 35a3042
Update blazor.webassembly.js
MackinnonBuck 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
33 changes: 33 additions & 0 deletions
33
src/Components/Web.Extensions/src/InputFile/BrowserFile.cs
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 |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
| using System.IO; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Web.Extensions | ||
| { | ||
| internal class BrowserFile : IBrowserFile | ||
| { | ||
| internal InputFile Owner { get; set; } = default!; | ||
|
|
||
| public int Id { get; set; } | ||
|
|
||
| public string Name { get; set; } = string.Empty; | ||
|
|
||
| public DateTime LastModified { get; set; } | ||
|
|
||
| public long Size { get; set; } | ||
|
|
||
| public string Type { get; set; } = string.Empty; | ||
|
|
||
| public string? RelativePath { get; set; } | ||
|
|
||
| public Stream OpenReadStream(CancellationToken cancellationToken = default) | ||
| => Owner.OpenReadStream(this, cancellationToken); | ||
|
|
||
| public Task<IBrowserFile> ToImageFileAsync(string format, int maxWidth, int maxHeight) | ||
| => Owner.ConvertToImageFileAsync(this, format, maxWidth, maxHeight); | ||
| } | ||
| } |
77 changes: 77 additions & 0 deletions
77
src/Components/Web.Extensions/src/InputFile/BrowserFileStream.cs
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 |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
| using System.IO; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Web.Extensions | ||
| { | ||
| internal abstract class BrowserFileStream : Stream | ||
| { | ||
| private long _position; | ||
|
|
||
| protected BrowserFile File { get; } | ||
|
|
||
| protected BrowserFileStream(BrowserFile file) | ||
| { | ||
| File = file; | ||
| } | ||
|
|
||
| public override bool CanRead => true; | ||
|
|
||
| public override bool CanSeek => false; | ||
|
|
||
| public override bool CanWrite => false; | ||
|
|
||
| public override long Length => File.Size; | ||
|
|
||
| public override long Position | ||
| { | ||
| get => _position; | ||
| set => throw new NotSupportedException(); | ||
| } | ||
|
|
||
| public override void Flush() | ||
| => throw new NotSupportedException(); | ||
|
|
||
| public override int Read(byte[] buffer, int offset, int count) | ||
| => throw new NotSupportedException("Synchronous reads are not supported."); | ||
|
|
||
| public override long Seek(long offset, SeekOrigin origin) | ||
| => throw new NotSupportedException(); | ||
|
|
||
| public override void SetLength(long value) | ||
| => throw new NotSupportedException(); | ||
|
|
||
| public override void Write(byte[] buffer, int offset, int count) | ||
| => throw new NotSupportedException(); | ||
|
|
||
| public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) | ||
| => ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask(); | ||
|
|
||
| public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) | ||
| { | ||
| int maxBytesToRead = (int)(Length - Position); | ||
|
|
||
| if (maxBytesToRead > buffer.Length) | ||
| { | ||
| maxBytesToRead = buffer.Length; | ||
| } | ||
|
|
||
| if (maxBytesToRead <= 0) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| var bytesRead = await CopyFileDataIntoBuffer(_position, buffer.Slice(0, maxBytesToRead), cancellationToken); | ||
|
|
||
| _position += bytesRead; | ||
|
|
||
| return bytesRead; | ||
| } | ||
|
|
||
| protected abstract ValueTask<int> CopyFileDataIntoBuffer(long sourceOffset, Memory<byte> destination, CancellationToken cancellationToken); | ||
| } | ||
| } |
54 changes: 54 additions & 0 deletions
54
src/Components/Web.Extensions/src/InputFile/IBrowserFile.cs
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 |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
| using System.IO; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Web.Extensions | ||
| { | ||
| /// <summary> | ||
| /// Represents the data of a file selected from an <see cref="InputFile"/> component. | ||
| /// </summary> | ||
| public interface IBrowserFile | ||
| { | ||
| /// <summary> | ||
| /// Gets the name of the file. | ||
| /// </summary> | ||
| string Name { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the last modified date. | ||
| /// </summary> | ||
| DateTime LastModified { get; } | ||
MackinnonBuck marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// Gets the size of the file in bytes. | ||
| /// </summary> | ||
| long Size { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the MIME type of the file. | ||
| /// </summary> | ||
| string Type { get; } | ||
MackinnonBuck marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// Opens the stream for reading the uploaded file. | ||
| /// </summary> | ||
| /// <param name="cancellationToken">A cancellation token to signal the cancellation of streaming file data.</param> | ||
| Stream OpenReadStream(CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Converts the current image file to a new one of the specified file type and maximum file dimensions. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The image will be scaled to fit the specified dimensions while preserving the original aspect ratio. | ||
| /// </remarks> | ||
| /// <param name="format">The new image format.</param> | ||
| /// <param name="maxWith">The maximum image width.</param> | ||
| /// <param name="maxHeight">The maximum image height</param> | ||
| /// <returns>A <see cref="Task"/> representing the completion of the operation.</returns> | ||
| Task<IBrowserFile> ToImageFileAsync(string format, int maxWith, int maxHeight); | ||
pranavkm marked this conversation as resolved.
Show resolved
Hide resolved
MackinnonBuck marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
12 changes: 12 additions & 0 deletions
12
src/Components/Web.Extensions/src/InputFile/IInputFileJsCallbacks.cs
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 |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Web.Extensions | ||
| { | ||
| internal interface IInputFileJsCallbacks | ||
| { | ||
| Task NotifyChange(BrowserFile[] files); | ||
| } | ||
| } |
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 |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.AspNetCore.Components.Rendering; | ||
| using Microsoft.Extensions.Options; | ||
| using Microsoft.JSInterop; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Web.Extensions | ||
| { | ||
| /// <summary> | ||
| /// A component that wraps the HTML file input element and exposes a <see cref="Stream"/> for each file's contents. | ||
| /// </summary> | ||
| public class InputFile : ComponentBase, IInputFileJsCallbacks, IDisposable | ||
MackinnonBuck marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| private ElementReference _inputFileElement; | ||
|
|
||
| private IJSUnmarshalledRuntime? _jsUnmarshalledRuntime; | ||
|
|
||
| private InputFileJsCallbacksRelay? _jsCallbacksRelay; | ||
|
|
||
| [Inject] | ||
| private IJSRuntime JSRuntime { get; set; } = default!; | ||
|
|
||
| [Inject] | ||
| private IOptions<RemoteBrowserFileStreamOptions> Options { get; set; } = default!; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the event callback that will be invoked when the collection of selected files changes. | ||
| /// </summary> | ||
| [Parameter] | ||
| public EventCallback<InputFileChangeEventArgs> OnChange { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a collection of additional attributes that will be applied to the input element. | ||
| /// </summary> | ||
| [Parameter(CaptureUnmatchedValues = true)] | ||
| public IDictionary<string, object>? AdditionalAttributes { get; set; } | ||
|
|
||
| protected override void OnInitialized() | ||
| { | ||
| _jsUnmarshalledRuntime = JSRuntime as IJSUnmarshalledRuntime; | ||
| } | ||
|
|
||
| protected override async Task OnAfterRenderAsync(bool firstRender) | ||
| { | ||
| if (firstRender) | ||
| { | ||
| _jsCallbacksRelay = new InputFileJsCallbacksRelay(this); | ||
| await JSRuntime.InvokeVoidAsync(InputFileInterop.Init, _jsCallbacksRelay.DotNetReference, _inputFileElement); | ||
| } | ||
| } | ||
|
|
||
| protected override void BuildRenderTree(RenderTreeBuilder builder) | ||
| { | ||
| builder.OpenElement(0, "input"); | ||
| builder.AddMultipleAttributes(1, AdditionalAttributes); | ||
| builder.AddAttribute(2, "type", "file"); | ||
| builder.AddElementReferenceCapture(3, elementReference => _inputFileElement = elementReference); | ||
| builder.CloseElement(); | ||
| } | ||
|
|
||
| internal Stream OpenReadStream(BrowserFile file, CancellationToken cancellationToken) | ||
| => _jsUnmarshalledRuntime != null ? | ||
| (Stream)new SharedBrowserFileStream(JSRuntime, _jsUnmarshalledRuntime, _inputFileElement, file) : | ||
| new RemoteBrowserFileStream(JSRuntime, _inputFileElement, file, Options.Value, cancellationToken); | ||
|
|
||
| internal async Task<IBrowserFile> ConvertToImageFileAsync(BrowserFile file, string format, int maxWidth, int maxHeight) | ||
| { | ||
| var imageFile = await JSRuntime.InvokeAsync<BrowserFile>(InputFileInterop.ToImageFile, _inputFileElement, file.Id, format, maxWidth, maxHeight); | ||
|
|
||
| imageFile.Owner = this; | ||
|
|
||
| return imageFile; | ||
| } | ||
|
|
||
| Task IInputFileJsCallbacks.NotifyChange(BrowserFile[] files) | ||
| { | ||
| foreach (var file in files) | ||
| { | ||
| file.Owner = this; | ||
| } | ||
|
|
||
| return OnChange.InvokeAsync(new InputFileChangeEventArgs(files)); | ||
| } | ||
|
|
||
| void IDisposable.Dispose() | ||
| { | ||
| _jsCallbacksRelay?.Dispose(); | ||
| } | ||
| } | ||
| } | ||
28 changes: 28 additions & 0 deletions
28
src/Components/Web.Extensions/src/InputFile/InputFileChangeEventArgs.cs
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 |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Web.Extensions | ||
| { | ||
| /// <summary> | ||
| /// Supplies information about an <see cref="InputFile.OnChange"/> event being raised. | ||
| /// </summary> | ||
| public class InputFileChangeEventArgs : EventArgs | ||
| { | ||
| /// <summary> | ||
| /// The updated file entries list. | ||
| /// </summary> | ||
| public IReadOnlyList<IBrowserFile> Files { get; } | ||
|
|
||
| /// <summary> | ||
| /// Constructs a new <see cref="InputFileChangeEventArgs"/> instance. | ||
| /// </summary> | ||
| /// <param name="files">The updated file entries list.</param> | ||
| public InputFileChangeEventArgs(IReadOnlyList<IBrowserFile> files) | ||
| { | ||
| Files = files; | ||
| } | ||
| } | ||
| } |
20 changes: 20 additions & 0 deletions
20
src/Components/Web.Extensions/src/InputFile/InputFileInterop.cs
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 |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Web.Extensions | ||
| { | ||
| internal static class InputFileInterop | ||
| { | ||
| private const string JsFunctionsPrefix = "_blazorInputFile."; | ||
|
|
||
| public const string Init = JsFunctionsPrefix + "init"; | ||
|
|
||
| public const string EnsureArrayBufferReadyForSharedMemoryInterop = JsFunctionsPrefix + "ensureArrayBufferReadyForSharedMemoryInterop"; | ||
|
|
||
| public const string ReadFileData = JsFunctionsPrefix + "readFileData"; | ||
|
|
||
| public const string ReadFileDataSharedMemory = JsFunctionsPrefix + "readFileDataSharedMemory"; | ||
|
|
||
| public const string ToImageFile = JsFunctionsPrefix + "toImageFile"; | ||
| } | ||
| } |
32 changes: 32 additions & 0 deletions
32
src/Components/Web.Extensions/src/InputFile/InputFileJsCallbacksRelay.cs
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 |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.JSInterop; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Web.Extensions | ||
| { | ||
| internal class InputFileJsCallbacksRelay : IDisposable | ||
| { | ||
| private readonly IInputFileJsCallbacks _callbacks; | ||
|
|
||
| public IDisposable DotNetReference { get; } | ||
|
|
||
| public InputFileJsCallbacksRelay(IInputFileJsCallbacks callbacks) | ||
| { | ||
| _callbacks = callbacks; | ||
|
|
||
| DotNetReference = DotNetObjectReference.Create(this); | ||
| } | ||
|
|
||
| [JSInvokable] | ||
| public Task NotifyChange(BrowserFile[] files) | ||
| => _callbacks.NotifyChange(files); | ||
|
|
||
| public void Dispose() | ||
| { | ||
| DotNetReference.Dispose(); | ||
| } | ||
| } | ||
| } |
29 changes: 29 additions & 0 deletions
29
src/Components/Web.Extensions/src/InputFile/ReadRequest.cs
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 |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System.Runtime.InteropServices; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Web.Extensions | ||
| { | ||
| [StructLayout(LayoutKind.Explicit)] | ||
| internal struct ReadRequest | ||
| { | ||
| [FieldOffset(0)] | ||
| public string InputFileElementReferenceId; | ||
|
|
||
| [FieldOffset(4)] | ||
| public int FileId; | ||
|
|
||
| [FieldOffset(8)] | ||
| public long SourceOffset; | ||
|
|
||
| [FieldOffset(16)] | ||
| public byte[] Destination; | ||
|
|
||
| [FieldOffset(20)] | ||
| public int DestinationOffset; | ||
|
|
||
| [FieldOffset(24)] | ||
| public int MaxBytes; | ||
| } | ||
| } |
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.