File: Forms\InputFile.cs
Web Access
Project: src\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));
    }
 
    void IDisposable.Dispose()
    {
        _jsCallbacksRelay?.Dispose();
    }
}