| File: Forms\InputFile.cs | Web Access |
| Project: src\aspnetcore\src\Components\Web\src\Microsoft.AspNetCore.Components.Web.csproj (Microsoft.AspNetCore.Components.Web) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.JSInterop; namespace Microsoft.AspNetCore.Components.Forms; /// <summary> /// A component that wraps the HTML file input element and supplies a <see cref="Stream"/> for each file's contents. /// </summary> public class InputFile : ComponentBase, IInputFileJsCallbacks, IDisposable { private ElementReference _inputFileElement; private InputFileJsCallbacksRelay? _jsCallbacksRelay; [Inject] internal IJSRuntime JSRuntime { get; set; } = default!; // Internal for testing /// <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; } /// <summary> /// Gets or sets the associated <see cref="ElementReference"/>. /// <para> /// May be <see langword="null"/> if accessed before the component is rendered. /// </para> /// </summary> [DisallowNull] public ElementReference? Element { get => _inputFileElement; protected set => _inputFileElement = value!.Value; } /// <inheritdoc/> protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _jsCallbacksRelay = new InputFileJsCallbacksRelay(this); await JSRuntime.InvokeVoidAsync(InputFileInterop.Init, _jsCallbacksRelay.DotNetReference, _inputFileElement); } } /// <inheritdoc/> 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, long maxAllowedSize, CancellationToken cancellationToken) => new BrowserFileStream( JSRuntime, _inputFileElement, file, maxAllowedSize, cancellationToken); internal async ValueTask<IBrowserFile> ConvertToImageFileAsync(BrowserFile file, string format, int maxWidth, int maxHeight) { var imageFile = await JSRuntime.InvokeAsync<BrowserFile>(InputFileInterop.ToImageFile, _inputFileElement, file.Id, format, maxWidth, maxHeight); if (imageFile is null) { throw new InvalidOperationException("ToImageFile returned an unexpected null result."); } imageFile.Owner = this; return imageFile; } Task IInputFileJsCallbacks.NotifyChange(BrowserFile[] files) { foreach (var file in files) { file.Owner = this; } return OnChange.InvokeAsync(new InputFileChangeEventArgs(files)); } /// <summary> /// Releases the resources used by the <see cref="InputFile"/> component. /// </summary> /// <param name="disposing"><see langword="true"/> if called within <see cref="IDisposable.Dispose"/>.</param> protected virtual void Dispose(bool disposing) { if (disposing) { _jsCallbacksRelay?.Dispose(); } } void IDisposable.Dispose() { Dispose(disposing: true); } }