Bagikan melalui


unggahan file ASP.NET Core Blazor

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Peringatan

Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Artikel ini menjelaskan cara mengunggah file dengan BlazorInputFile komponen.

Unggahan file

Peringatan

Selalu ikuti praktik terbaik keamanan saat mengizinkan pengguna mengunggah file. Untuk informasi selengkapnya, lihat Mengunggah file di ASP.NET Core.

InputFile Gunakan komponen untuk membaca data file browser ke dalam kode .NET. Komponen InputFile merender elemen HTML <input> jenis file. Secara default, pengguna memilih satu file. multiple Tambahkan atribut untuk mengizinkan pengguna mengunggah beberapa file sekaligus.

Pemilihan file tidak kumulatif saat menggunakan InputFile komponen atau HTML <input type="file">yang mendasarnya, sehingga Anda tidak dapat menambahkan file ke pilihan file yang ada. Komponen selalu menggantikan pilihan file awal pengguna, sehingga referensi file dari pilihan sebelumnya tidak tersedia.

Komponen berikut InputFile menjalankan LoadFiles metode ketika OnChange peristiwa (change) terjadi. Menyediakan InputFileChangeEventArgs akses ke daftar file yang dipilih dan detail tentang setiap file:

<InputFile OnChange="LoadFiles" multiple />

@code {
    private void LoadFiles(InputFileChangeEventArgs e)
    {
        ...
    }
}

HTML yang dirender:

<input multiple="" type="file" _bl_2="">

Catatan

Dalam contoh sebelumnya, <input> atribut elemen _bl_2 digunakan untuk Blazorpemrosesan internal.

Untuk membaca data dari file yang dipilih pengguna, panggil IBrowserFile.OpenReadStream pada file dan baca dari aliran yang dikembalikan. Untuk informasi selengkapnya, lihat bagian Aliran file.

OpenReadStream memberlakukan ukuran maksimum dalam byte dari Stream. Membaca satu file atau beberapa file yang lebih besar dari 500 KB menghasilkan pengecualian. Batas ini mencegah pengembang membaca file besar secara tidak sengaja ke dalam memori. Parameter maxAllowedSizeOpenReadStream dapat digunakan untuk menentukan ukuran yang lebih besar jika diperlukan.

Jika Anda memerlukan akses ke Stream yang mewakili byte file, gunakan IBrowserFile.OpenReadStream. Hindari membaca aliran file masuk langsung ke memori sekaligus. Misalnya, jangan menyalin semua byte file ke dalam MemoryStream atau membaca seluruh aliran ke dalam array byte sekaligus. Pendekatan ini dapat mengakibatkan masalah performa dan keamanan, terutama untuk komponen sisi server. Sebagai gantinya, pertimbangkan untuk mengadopsi salah satu pendekatan berikut:

  • Salin aliran langsung ke file pada disk tanpa membacanya ke dalam memori. Perhatikan bahwa aplikasi yang Blazor menjalankan kode di server tidak dapat mengakses sistem file klien secara langsung.
  • Unggah file dari klien langsung ke layanan eksternal. Untuk informasi selengkapnya, lihat bagian Mengunggah file ke layanan eksternal.

Dalam contoh berikut, browserFile mewakili file yang diunggah dan mengimplementasikan IBrowserFile. Implementasi kerja untuk IBrowserFile ditampilkan dalam komponen unggahan file nanti di artikel ini.

Tidak didukung: Pendekatan berikut TIDAK disarankan karena konten file Stream dibaca ke dalam String memori (reader):

var reader = 
    await new StreamReader(browserFile.OpenReadStream()).ReadToEndAsync();

Tidak didukung: Pendekatan berikut TIDAK disarankan untuk Microsoft Azure Blob Storage karena konten file Stream disalin ke dalam MemoryStream memori (memoryStream) sebelum memanggil UploadBlobAsync:

var memoryStream = new MemoryStream();
await browserFile.OpenReadStream().CopyToAsync(memoryStream);
await blobContainerClient.UploadBlobAsync(
    trustedFileName, memoryStream));

Pendekatan berikut direkomendasikan karena file Stream disediakan langsung kepada konsumen, FileStream yang membuat file di jalur yang disediakan:

await using FileStream fs = new(path, FileMode.Create);
await browserFile.OpenReadStream().CopyToAsync(fs);

Didukung: Pendekatan berikut direkomendasikan untuk Microsoft Azure Blob Storage karena file Stream disediakan langsung ke UploadBlobAsync:

await blobContainerClient.UploadBlobAsync(
    trustedFileName, browserFile.OpenReadStream());

Komponen yang menerima file gambar dapat memanggil BrowserFileExtensions.RequestImageFileAsync metode kenyamanan pada file untuk mengubah ukuran data gambar dalam runtime JavaScript browser sebelum gambar dialirkan ke aplikasi. Kasus penggunaan untuk panggilan RequestImageFileAsync paling tepat untuk Blazor WebAssembly aplikasi.

Batas baca dan unggahan ukuran file

Sisi server atau sisi klien, tidak ada batas ukuran baca atau unggahan file khusus untuk InputFile komponen. Namun, sisi Blazor klien membaca byte file ke dalam satu buffer array JavaScript saat menghidupkan data dari JavaScript ke C#, yang dibatasi hingga 2 GB atau ke memori perangkat yang tersedia. Unggahan file besar (> 250 MB) mungkin gagal untuk unggahan sisi klien menggunakan InputFile komponen . Untuk informasi selengkapnya, lihat diskusi berikut ini:

Ukuran file maksimum yang InputFile didukung untuk komponen adalah 2 GB. Selain itu, sisi Blazor klien membaca byte file ke dalam satu buffer array JavaScript saat menghidupkan data dari JavaScript ke C#, yang dibatasi hingga 2 GB atau ke memori perangkat yang tersedia. Unggahan file besar (> 250 MB) mungkin gagal untuk unggahan sisi klien menggunakan InputFile komponen . Untuk informasi selengkapnya, lihat diskusi berikut ini:

Untuk unggahan file sisi klien besar yang gagal saat mencoba menggunakan InputFile komponen, kami sarankan untuk memotong file besar dengan komponen kustom menggunakan beberapa permintaan rentang HTTP alih-alih menggunakan InputFile komponen.

Pekerjaan saat ini dijadwalkan untuk .NET 9 (akhir 2024) untuk mengatasi batasan unggahan ukuran file sisi klien.

Contoh

Contoh berikut menunjukkan beberapa unggahan file dalam komponen. InputFileChangeEventArgs.GetMultipleFiles memungkinkan membaca beberapa file. Tentukan jumlah maksimum file untuk mencegah pengguna berbahaya mengunggah jumlah file yang lebih besar dari yang diharapkan aplikasi. InputFileChangeEventArgs.File memungkinkan membaca file pertama dan satu-satunya jika unggahan file tidak mendukung beberapa file.

InputFileChangeEventArgs berada di Microsoft.AspNetCore.Components.Forms namespace layanan, yang biasanya merupakan salah satu namespace dalam file aplikasi _Imports.razor . Saat namespace ada dalam _Imports.razor file, namespace menyediakan akses anggota API ke komponen aplikasi.

Namespace dalam _Imports.razor file tidak diterapkan ke file C# (.cs). File C# memerlukan arahan eksplisit using di bagian atas file kelas:

using Microsoft.AspNetCore.Components.Forms;

Untuk menguji komponen pengunggahan file, Anda dapat membuat file pengujian dengan ukuran apa pun dengan PowerShell:

$out = new-object byte[] {SIZE}; (new-object Random).NextBytes($out); [IO.File]::WriteAllBytes('{PATH}', $out)

Pada perintah sebelumnya:

  • Tempat {SIZE} penampung adalah ukuran file dalam byte (misalnya, 2097152 untuk file 2 MB).
  • Tempat {PATH} penampung adalah jalur dan file dengan ekstensi file (misalnya, D:/test_files/testfile2MB.txt).

Contoh unggahan file sisi server

Untuk menggunakan kode berikut, buat Development/unsafe_uploads folder di akar aplikasi yang berjalan di Development lingkungan.

Karena contoh menggunakan lingkungan aplikasi sebagai bagian dari jalur tempat file disimpan, folder tambahan diperlukan jika lingkungan lain digunakan dalam pengujian dan produksi. Misalnya, buat Staging/unsafe_uploads folder untuk Staging lingkungan. Buat Production/unsafe_uploads folder untuk Production lingkungan.

Peringatan

Contoh menyimpan file tanpa memindai kontennya, dan panduan dalam artikel ini tidak memperhitungkan praktik terbaik keamanan tambahan untuk file yang diunggah. Pada sistem penahapan dan produksi, nonaktifkan izin eksekusi pada folder unggah dan pindai file dengan API pemindai anti-virus/anti-malware segera setelah diunggah. Untuk informasi selengkapnya, lihat Mengunggah file di ASP.NET Core.

FileUpload1.razor:

@page "/file-upload-1"
@using System 
@using System.IO
@using Microsoft.AspNetCore.Hosting
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment

<PageTitle>File Upload 1</PageTitle>

<h1>File Upload Example 1</h1>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Uploading...</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;

    private async Task LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                var trustedFileName = Path.GetRandomFileName();
                var path = Path.Combine(Environment.ContentRootPath,
                    Environment.EnvironmentName, "unsafe_uploads",
                    trustedFileName);

                await using FileStream fs = new(path, FileMode.Create);
                await file.OpenReadStream(maxFileSize).CopyToAsync(fs);

                loadedFiles.Add(file);

                Logger.LogInformation(
                    "Unsafe Filename: {UnsafeFilename} File saved: {Filename}",
                    file.Name, trustedFileName);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {Filename} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}
@page "/file-upload-1"
@using System 
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment

<h3>Upload Files</h3>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Uploading...</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;

    private async Task LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                loadedFiles.Add(file);

                var trustedFileNameForFileStorage = Path.GetRandomFileName();
                var path = Path.Combine(Environment.ContentRootPath,
                        Environment.EnvironmentName, "unsafe_uploads",
                        trustedFileNameForFileStorage);

                await using FileStream fs = new(path, FileMode.Create);
                await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {Filename} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}
@page "/file-upload-1"
@using System 
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment

<h3>Upload Files</h3>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Uploading...</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;

    private async Task LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                loadedFiles.Add(file);

                var trustedFileNameForFileStorage = Path.GetRandomFileName();
                var path = Path.Combine(Environment.ContentRootPath,
                        Environment.EnvironmentName, "unsafe_uploads",
                        trustedFileNameForFileStorage);

                await using FileStream fs = new(path, FileMode.Create);
                await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {Filename} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}
@page "/file-upload-1"
@using System 
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger
@inject IWebHostEnvironment Environment

<h3>Upload Files</h3>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Uploading...</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;

    private async Task LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                loadedFiles.Add(file);

                var trustedFileNameForFileStorage = Path.GetRandomFileName();
                var path = Path.Combine(Environment.ContentRootPath,
                        Environment.EnvironmentName, "unsafe_uploads",
                        trustedFileNameForFileStorage);

                await using FileStream fs = new(path, FileMode.Create);
                await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {Filename} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}

Contoh unggahan file sisi klien

Contoh berikut memproses byte file dan tidak mengirim file ke tujuan di luar aplikasi. Untuk contoh Razor komponen yang mengirim file ke server atau layanan, lihat bagian berikut ini:

Komponen mengasumsikan bahwa mode render Interactive WebAssembly (InteractiveWebAssembly) diwarisi dari komponen induk atau diterapkan secara global ke aplikasi.

@page "/file-upload-1"
@inject ILogger<FileUpload1> Logger

<PageTitle>File Upload 1</PageTitle>

<h1>File Upload Example 1</h1>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Uploading...</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;

    private void LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                loadedFiles.Add(file);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {FileName} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger

<h3>Upload Files</h3>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Uploading...</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;

    private void LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                loadedFiles.Add(file);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {FileName} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger

<h3>Upload Files</h3>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Uploading...</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;

    private void LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                loadedFiles.Add(file);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {Filename} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}
@page "/file-upload-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload1> Logger

<h3>Upload Files</h3>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Uploading...</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;

    private void LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                loadedFiles.Add(file);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {Filename} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}

IBrowserFile mengembalikan metadata yang diekspos oleh browser sebagai properti. Gunakan metadata ini untuk validasi awal.

Jangan pernah mempercayai nilai properti sebelumnya, terutama Name properti untuk ditampilkan di UI. Perlakukan semua data yang disediakan pengguna sebagai risiko keamanan yang signifikan terhadap aplikasi, server, dan jaringan. Untuk informasi selengkapnya, lihat Mengunggah file di ASP.NET Core.

Mengunggah file ke server dengan penyajian sisi server

Bagian ini berlaku untuk komponen Server Interaktif di Blazor Web Apps.

Bagian ini berlaku untuk Blazor Server aplikasi.

Contoh berikut menunjukkan pengunggahan file dari aplikasi sisi server ke pengontrol API web backend di aplikasi terpisah, mungkin di server terpisah.

Dalam file aplikasi Program sisi server, tambahkan IHttpClientFactory dan layanan terkait yang memungkinkan aplikasi membuat HttpClient instans:

builder.Services.AddHttpClient();

Untuk informasi lebih lanjut, lihat Membuat permintaan HTTP menggunakan IHttpClientFactory di ASP.NET Core.

Untuk contoh di bagian ini:

  • API web berjalan di URL: https://localhost:5001
  • Aplikasi sisi server berjalan di URL: https://localhost:5003

Untuk pengujian, URL sebelumnya dikonfigurasi dalam file proyek Properties/launchSettings.json .

Kelas berikut UploadResult mempertahankan hasil file yang diunggah. Ketika file gagal diunggah di server, kode kesalahan dikembalikan ErrorCode untuk ditampilkan kepada pengguna. Nama file aman dihasilkan di server untuk setiap file dan dikembalikan ke klien untuk StoredFileName ditampilkan. File di-key antara klien dan server menggunakan nama file yang tidak aman/tidak tepercaya di FileName.

UploadResult.cs:

public class UploadResult
{
    public bool Uploaded { get; set; }
    public string? FileName { get; set; }
    public string? StoredFileName { get; set; }
    public int ErrorCode { get; set; }
}

Catatan

Praktik terbaik keamanan untuk aplikasi produksi adalah menghindari pengiriman pesan kesalahan ke klien yang mungkin mengungkapkan informasi sensitif tentang aplikasi, server, atau jaringan. Menyediakan pesan kesalahan terperinci dapat membantu pengguna berbahaya dalam merancang serangan pada aplikasi, server, atau jaringan. Contoh kode di bagian ini hanya mengirim kembali nomor kode kesalahan (int) untuk ditampilkan oleh sisi klien komponen jika terjadi kesalahan sisi server. Jika pengguna memerlukan bantuan dengan unggahan file, mereka memberikan kode kesalahan untuk mendukung personel untuk resolusi tiket dukungan tanpa mengetahui penyebab pasti kesalahan.

Komponen berikut FileUpload2 :

  • Mengizinkan pengguna mengunggah file dari klien.
  • Menampilkan nama file yang tidak tepercaya/tidak aman yang disediakan oleh klien di UI. Nama file yang tidak tepercaya/tidak aman secara otomatis dikodekan HTML oleh Razor untuk tampilan aman di UI.

Peringatan

Jangan percaya nama file yang disediakan oleh klien untuk:

  • Menyimpan file ke sistem atau layanan file.
  • Tampilkan di UI yang tidak mengodekan nama file secara otomatis atau melalui kode pengembang.

Untuk informasi selengkapnya tentang pertimbangan keamanan saat mengunggah file ke server, lihat Mengunggah file di ASP.NET Core.

FileUpload2.razor:

@page "/file-upload-2"
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<PageTitle>File Upload 2</PageTitle>

<h1>File Upload Example 2</h1>

<p>
    This example requires a backend server API to function. For more information, 
    see the <em>Upload files to a server</em> section 
    of the <em>ASP.NET Core Blazor file uploads</em> article.
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        long maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var fileContent = 
                        new StreamContent(file.OpenReadStream(maxFileSize));

                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var client = ClientFactory.CreateClient();

            var response = 
                await client.PostAsync("https://localhost:5001/Filesave", 
                content);

            if (response.IsSuccessStatusCode)
            {
                var options = new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true,
                };

                using var responseStream =
                    await response.Content.ReadAsStreamAsync();

                var newUploadResults = await JsonSerializer
                    .DeserializeAsync<IList<UploadResult>>(responseStream, options);

                if (newUploadResults is not null)
                {
                    uploadResults = uploadResults.Concat(newUploadResults).ToList();
                }
            }
        }

        shouldRender = true;
    }

    private static bool FileUpload(IList<UploadResult> uploadResults,
        string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
    {
        result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();

        if (!result.Uploaded)
        {
            logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
            result.ErrorCode = 5;
        }

        return result.Uploaded;
    }

    private class File
    {
        public string? Name { get; set; }
    }
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        long maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var fileContent = 
                        new StreamContent(file.OpenReadStream(maxFileSize));

                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var client = ClientFactory.CreateClient();

            var response = 
                await client.PostAsync("https://localhost:5001/Filesave", 
                content);

            if (response.IsSuccessStatusCode)
            {
                var options = new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true,
                };

                using var responseStream =
                    await response.Content.ReadAsStreamAsync();

                var newUploadResults = await JsonSerializer
                    .DeserializeAsync<IList<UploadResult>>(responseStream, options);

                if (newUploadResults is not null)
                {
                    uploadResults = uploadResults.Concat(newUploadResults).ToList();
                }
            }
        }

        shouldRender = true;
    }

    private static bool FileUpload(IList<UploadResult> uploadResults,
        string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
    {
        result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();

        if (!result.Uploaded)
        {
            logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
            result.ErrorCode = 5;
        }

        return result.Uploaded;
    }

    private class File
    {
        public string? Name { get; set; }
    }
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        long maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var fileContent = 
                        new StreamContent(file.OpenReadStream(maxFileSize));

                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var client = ClientFactory.CreateClient();

            var response = 
                await client.PostAsync("https://localhost:5001/Filesave", 
                content);

            if (response.IsSuccessStatusCode)
            {
                var options = new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true,
                };

                using var responseStream =
                    await response.Content.ReadAsStreamAsync();

                var newUploadResults = await JsonSerializer
                    .DeserializeAsync<IList<UploadResult>>(responseStream, options);

                if (newUploadResults is not null)
                {
                    uploadResults = uploadResults.Concat(newUploadResults).ToList();
                }
            }
        }

        shouldRender = true;
    }

    private static bool FileUpload(IList<UploadResult> uploadResults,
        string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
    {
        result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();

        if (!result.Uploaded)
        {
            logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
            result.ErrorCode = 5;
        }

        return result.Uploaded;
    }

    private class File
    {
        public string? Name { get; set; }
    }
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        long maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var fileContent = 
                        new StreamContent(file.OpenReadStream(maxFileSize));

                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var client = ClientFactory.CreateClient();

            var response = 
                await client.PostAsync("https://localhost:5001/Filesave", 
                content);

            if (response.IsSuccessStatusCode)
            {
                var options = new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true,
                };

                using var responseStream =
                    await response.Content.ReadAsStreamAsync();

                var newUploadResults = await JsonSerializer
                    .DeserializeAsync<IList<UploadResult>>(responseStream, options);

                uploadResults = uploadResults.Concat(newUploadResults).ToList();
            }
        }

        shouldRender = true;
    }

    private static bool FileUpload(IList<UploadResult> uploadResults,
        string fileName, ILogger<FileUpload2> logger, out UploadResult result)
    {
        result = uploadResults.SingleOrDefault(f => f.FileName == fileName);

        if (result is null)
        {
            logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
            result = new();
            result.ErrorCode = 5;
        }

        return result.Uploaded;
    }

    private class File
    {
        public string Name { get; set; }
    }
}

Pengontrol berikut dalam proyek API web menyimpan file yang diunggah dari klien.

Penting

Pengontrol di bagian ini dimaksudkan untuk digunakan dalam proyek API web terpisah dari Blazor aplikasi. API web harus mengurangi serangan Pemalsuan Permintaan Lintas Situs (XSRF/CSRF) jika pengguna pengunggah file diautentikasi.

Catatan

Mengikat nilai formulir dengan [FromForm] atribut tidak didukung secara asli untuk API Minimal di ASP.NET Core di .NET 6. Oleh karena itu, contoh pengontrol berikut Filesave tidak dapat dikonversi untuk menggunakan API Minimal. Dukungan untuk mengikat dari nilai formulir dengan API Minimal tersedia di ASP.NET Core di .NET 7 atau yang lebih baru.

Untuk menggunakan kode berikut, buat Development/unsafe_uploads folder di akar proyek API web untuk aplikasi yang berjalan di Development lingkungan.

Karena contoh menggunakan lingkungan aplikasi sebagai bagian dari jalur tempat file disimpan, folder tambahan diperlukan jika lingkungan lain digunakan dalam pengujian dan produksi. Misalnya, buat Staging/unsafe_uploads folder untuk Staging lingkungan. Buat Production/unsafe_uploads folder untuk Production lingkungan.

Peringatan

Contoh menyimpan file tanpa memindai kontennya, dan panduan dalam artikel ini tidak memperhitungkan praktik terbaik keamanan tambahan untuk file yang diunggah. Pada sistem penahapan dan produksi, nonaktifkan izin eksekusi pada folder unggah dan pindai file dengan API pemindai anti-virus/anti-malware segera setelah diunggah. Untuk informasi selengkapnya, lihat Mengunggah file di ASP.NET Core.

Controllers/FilesaveController.cs:

using System.Net;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("[controller]")]
public class FilesaveController : ControllerBase
{
    private readonly IHostEnvironment env;
    private readonly ILogger<FilesaveController> logger;

    public FilesaveController(IHostEnvironment env, 
        ILogger<FilesaveController> logger)
    {
        this.env = env;
        this.logger = logger;
    }

    [HttpPost]
    public async Task<ActionResult<IList<UploadResult>>> PostFile(
        [FromForm] IEnumerable<IFormFile> files)
    {
        var maxAllowedFiles = 3;
        long maxFileSize = 1024 * 15;
        var filesProcessed = 0;
        var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
        List<UploadResult> uploadResults = new();

        foreach (var file in files)
        {
            var uploadResult = new UploadResult();
            string trustedFileNameForFileStorage;
            var untrustedFileName = file.FileName;
            uploadResult.FileName = untrustedFileName;
            var trustedFileNameForDisplay =
                WebUtility.HtmlEncode(untrustedFileName);

            if (filesProcessed < maxAllowedFiles)
            {
                if (file.Length == 0)
                {
                    logger.LogInformation("{FileName} length is 0 (Err: 1)",
                        trustedFileNameForDisplay);
                    uploadResult.ErrorCode = 1;
                }
                else if (file.Length > maxFileSize)
                {
                    logger.LogInformation("{FileName} of {Length} bytes is " +
                        "larger than the limit of {Limit} bytes (Err: 2)",
                        trustedFileNameForDisplay, file.Length, maxFileSize);
                    uploadResult.ErrorCode = 2;
                }
                else
                {
                    try
                    {
                        trustedFileNameForFileStorage = Path.GetRandomFileName();
                        var path = Path.Combine(env.ContentRootPath,
                            env.EnvironmentName, "unsafe_uploads",
                            trustedFileNameForFileStorage);

                        await using FileStream fs = new(path, FileMode.Create);
                        await file.CopyToAsync(fs);

                        logger.LogInformation("{FileName} saved at {Path}",
                            trustedFileNameForDisplay, path);
                        uploadResult.Uploaded = true;
                        uploadResult.StoredFileName = trustedFileNameForFileStorage;
                    }
                    catch (IOException ex)
                    {
                        logger.LogError("{FileName} error on upload (Err: 3): {Message}",
                            trustedFileNameForDisplay, ex.Message);
                        uploadResult.ErrorCode = 3;
                    }
                }

                filesProcessed++;
            }
            else
            {
                logger.LogInformation("{FileName} not uploaded because the " +
                    "request exceeded the allowed {Count} of files (Err: 4)",
                    trustedFileNameForDisplay, maxAllowedFiles);
                uploadResult.ErrorCode = 4;
            }

            uploadResults.Add(uploadResult);
        }

        return new CreatedResult(resourcePath, uploadResults);
    }
}

Dalam kode sebelumnya, GetRandomFileName dipanggil untuk menghasilkan nama file yang aman. Jangan pernah mempercayai nama file yang disediakan oleh browser, karena penyerang dapat memilih nama file yang ada yang menimpa file yang ada atau mengirim jalur yang mencoba menulis di luar aplikasi.

Aplikasi server harus mendaftarkan layanan pengontrol dan titik akhir pengontrol peta. Untuk informasi selengkapnya, lihat Perutean ke tindakan pengontrol di ASP.NET Core.

Mengunggah file ke server

Contoh berikut menunjukkan pengunggahan file ke pengontrol API web.

Kelas berikut UploadResult mempertahankan hasil file yang diunggah. Ketika file gagal diunggah di server, kode kesalahan dikembalikan ErrorCode untuk ditampilkan kepada pengguna. Nama file aman dihasilkan di server untuk setiap file dan dikembalikan ke klien untuk StoredFileName ditampilkan. File di-key antara klien dan server menggunakan nama file yang tidak aman/tidak tepercaya di FileName.

UploadResult.cs:

public class UploadResult
{
    public bool Uploaded { get; set; }
    public string? FileName { get; set; }
    public string? StoredFileName { get; set; }
    public int ErrorCode { get; set; }
}

Catatan

Kelas sebelumnya dapat dibagikan UploadResult antara proyek berbasis klien dan server. Saat klien dan proyek server berbagi kelas, tambahkan impor ke setiap file proyek _Imports.razor untuk proyek bersama. Contohnya:

@using BlazorSample.Shared

Komponen berikut FileUpload2 :

  • Mengizinkan pengguna mengunggah file dari klien.
  • Menampilkan nama file yang tidak tepercaya/tidak aman yang disediakan oleh klien di UI. Nama file yang tidak tepercaya/tidak aman secara otomatis dikodekan HTML oleh Razor untuk tampilan aman di UI.

Praktik terbaik keamanan untuk aplikasi produksi adalah menghindari pengiriman pesan kesalahan ke klien yang mungkin mengungkapkan informasi sensitif tentang aplikasi, server, atau jaringan. Menyediakan pesan kesalahan terperinci dapat membantu pengguna berbahaya dalam merancang serangan pada aplikasi, server, atau jaringan. Contoh kode di bagian ini hanya mengirim kembali nomor kode kesalahan (int) untuk ditampilkan oleh sisi klien komponen jika terjadi kesalahan sisi server. Jika pengguna memerlukan bantuan dengan unggahan file, mereka memberikan kode kesalahan untuk mendukung personel untuk resolusi tiket dukungan tanpa mengetahui penyebab pasti kesalahan.

Peringatan

Jangan percaya nama file yang disediakan oleh klien untuk:

  • Menyimpan file ke sistem atau layanan file.
  • Tampilkan di UI yang tidak mengodekan nama file secara otomatis atau melalui kode pengembang.

Untuk informasi selengkapnya tentang pertimbangan keamanan saat mengunggah file ke server, lihat Mengunggah file di ASP.NET Core.

Blazor Di proyek utama Aplikasi Web, tambahkan IHttpClientFactory dan layanan terkait dalam file proyekProgram:

builder.Services.AddHttpClient();

Layanan HttpClient harus ditambahkan ke proyek utama karena komponen sisi klien telah dirender di server. Jika Anda menonaktifkan pra-penyajian untuk komponen berikut, Anda tidak diharuskan menyediakan HttpClient layanan di aplikasi utama dan tidak perlu menambahkan baris sebelumnya ke proyek utama.

Untuk informasi selengkapnya tentang menambahkan HttpClient layanan ke aplikasi ASP.NET Core, lihat Membuat permintaan HTTP menggunakan IHttpClientFactory di ASP.NET Core.

Proyek klien (.Client) Aplikasi Blazor Web juga harus mendaftarkan HttpClient permintaan HTTP POST ke pengontrol API web backend. Konfirmasi atau tambahkan yang berikut ini ke file proyek Program klien:

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

Contoh sebelumnya mengatur alamat dasar dengan builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress), yang mendapatkan alamat dasar untuk aplikasi dan biasanya berasal dari <base> nilai tag href di halaman host. Jika Anda memanggil API web eksternal, atur URI ke alamat dasar API web.

Tentukan atribut mode render Interactive WebAssembly di bagian atas komponen berikut dalam Blazor Aplikasi Web:

@rendermode InteractiveWebAssembly

FileUpload2.razor:

@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger

<PageTitle>File Upload 2</PageTitle>

<h1>File Upload Example 2</h1>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        long maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var fileContent = 
                        new StreamContent(file.OpenReadStream(maxFileSize));

                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var response = await Http.PostAsync("/Filesave", content);

            var newUploadResults = await response.Content
                .ReadFromJsonAsync<IList<UploadResult>>();

            if (newUploadResults is not null)
            {
                uploadResults = uploadResults.Concat(newUploadResults).ToList();
            }
        }

        shouldRender = true;
    }

    private static bool FileUpload(IList<UploadResult> uploadResults,
        string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
    {
        result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();

        if (!result.Uploaded)
        {
            logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
            result.ErrorCode = 5;
        }

        return result.Uploaded;
    }

    private class File
    {
        public string? Name { get; set; }
    }
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        long maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var fileContent = 
                        new StreamContent(file.OpenReadStream(maxFileSize));

                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var response = await Http.PostAsync("/Filesave", content);

            var newUploadResults = await response.Content
                .ReadFromJsonAsync<IList<UploadResult>>();

            if (newUploadResults is not null)
            {
                uploadResults = uploadResults.Concat(newUploadResults).ToList();
            }
        }

        shouldRender = true;
    }

    private static bool FileUpload(IList<UploadResult> uploadResults,
        string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
    {
        result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();

        if (!result.Uploaded)
        {
            logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
            result.ErrorCode = 5;
        }

        return result.Uploaded;
    }

    private class File
    {
        public string? Name { get; set; }
    }
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        long maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var fileContent = 
                        new StreamContent(file.OpenReadStream(maxFileSize));

                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var response = await Http.PostAsync("/Filesave", content);

            var newUploadResults = await response.Content
                .ReadFromJsonAsync<IList<UploadResult>>();

            if (newUploadResults is not null)
            {
                uploadResults = uploadResults.Concat(newUploadResults).ToList();
            }
        }

        shouldRender = true;
    }

    private static bool FileUpload(IList<UploadResult> uploadResults,
        string? fileName, ILogger<FileUpload2> logger, out UploadResult result)
    {
        result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new();

        if (!result.Uploaded)
        {
            logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
            result.ErrorCode = 5;
        }

        return result.Uploaded;
    }

    private class File
    {
        public string? Name { get; set; }
    }
}
@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using Microsoft.Extensions.Logging
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<p>
    <label>
        Upload up to @maxAllowedFiles files:
        <InputFile OnChange="OnInputFileChange" multiple />
    </label>
</p>

@if (files.Count > 0)
{
    <div class="card">
        <div class="card-body">
            <ul>
                @foreach (var file in files)
                {
                    <li>
                        File: @file.Name
                        <br>
                        @if (FileUpload(uploadResults, file.Name, Logger,
                           out var result))
                        {
                            <span>
                                Stored File Name: @result.StoredFileName
                            </span>
                        }
                        else
                        {
                            <span>
                                There was an error uploading the file
                                (Error: @result.ErrorCode).
                            </span>
                        }
                    </li>
                }
            </ul>
        </div>
    </div>
}

@code {
    private List<File> files = new();
    private List<UploadResult> uploadResults = new();
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        long maxFileSize = 1024 * 15;
        var upload = false;

        using var content = new MultipartFormDataContent();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            if (uploadResults.SingleOrDefault(
                f => f.FileName == file.Name) is null)
            {
                try
                {
                    files.Add(new() { Name = file.Name });

                    var fileContent = 
                        new StreamContent(file.OpenReadStream(maxFileSize));

                    fileContent.Headers.ContentType = 
                        new MediaTypeHeaderValue(file.ContentType);

                    content.Add(
                        content: fileContent,
                        name: "\"files\"",
                        fileName: file.Name);

                    upload = true;
                }
                catch (Exception ex)
                {
                    Logger.LogInformation(
                        "{FileName} not uploaded (Err: 6): {Message}", 
                        file.Name, ex.Message);

                    uploadResults.Add(
                        new()
                        {
                            FileName = file.Name, 
                            ErrorCode = 6, 
                            Uploaded = false
                        });
                }
            }
        }

        if (upload)
        {
            var response = await Http.PostAsync("/Filesave", content);

            var newUploadResults = await response.Content
                .ReadFromJsonAsync<IList<UploadResult>>();

            uploadResults = uploadResults.Concat(newUploadResults).ToList();
        }

        shouldRender = true;
    }

    private static bool FileUpload(IList<UploadResult> uploadResults,
        string fileName, ILogger<FileUpload2> logger, out UploadResult result)
    {
        result = uploadResults.SingleOrDefault(f => f.FileName == fileName);

        if (result is null)
        {
            logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName);
            result = new();
            result.ErrorCode = 5;
        }

        return result.Uploaded;
    }

    private class File
    {
        public string Name { get; set; }
    }
}

Pengontrol berikut dalam proyek sisi server menyimpan file yang diunggah dari klien.

Catatan

Mengikat nilai formulir dengan [FromForm] atribut tidak didukung secara asli untuk API Minimal di ASP.NET Core di .NET 6. Oleh karena itu, contoh pengontrol berikut Filesave tidak dapat dikonversi untuk menggunakan API Minimal. Dukungan untuk mengikat dari nilai formulir dengan API Minimal tersedia di ASP.NET Core di .NET 7 atau yang lebih baru.

Untuk menggunakan kode berikut, buat Development/unsafe_uploads folder di akar proyek sisi server untuk aplikasi yang berjalan di Development lingkungan.

Karena contoh menggunakan lingkungan aplikasi sebagai bagian dari jalur tempat file disimpan, folder tambahan diperlukan jika lingkungan lain digunakan dalam pengujian dan produksi. Misalnya, buat Staging/unsafe_uploads folder untuk Staging lingkungan. Buat Production/unsafe_uploads folder untuk Production lingkungan.

Peringatan

Contoh menyimpan file tanpa memindai kontennya, dan panduan dalam artikel ini tidak memperhitungkan praktik terbaik keamanan tambahan untuk file yang diunggah. Pada sistem penahapan dan produksi, nonaktifkan izin eksekusi pada folder unggah dan pindai file dengan API pemindai anti-virus/anti-malware segera setelah diunggah. Untuk informasi selengkapnya, lihat Mengunggah file di ASP.NET Core.

Dalam contoh berikut, perbarui namespace layanan proyek bersama agar sesuai dengan proyek bersama jika proyek bersama menyediakan UploadResult kelas .

Controllers/FilesaveController.cs:

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using BlazorSample.Shared;

[ApiController]
[Route("[controller]")]
public class FilesaveController : ControllerBase
{
    private readonly IHostEnvironment env;
    private readonly ILogger<FilesaveController> logger;

    public FilesaveController(IHostEnvironment env,
        ILogger<FilesaveController> logger)
    {
        this.env = env;
        this.logger = logger;
    }

    [HttpPost]
    public async Task<ActionResult<IList<UploadResult>>> PostFile(
        [FromForm] IEnumerable<IFormFile> files)
    {
        var maxAllowedFiles = 3;
        long maxFileSize = 1024 * 15;
        var filesProcessed = 0;
        var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
        List<UploadResult> uploadResults = new();

        foreach (var file in files)
        {
            var uploadResult = new UploadResult();
            string trustedFileNameForFileStorage;
            var untrustedFileName = file.FileName;
            uploadResult.FileName = untrustedFileName;
            var trustedFileNameForDisplay =
                WebUtility.HtmlEncode(untrustedFileName);

            if (filesProcessed < maxAllowedFiles)
            {
                if (file.Length == 0)
                {
                    logger.LogInformation("{FileName} length is 0 (Err: 1)",
                        trustedFileNameForDisplay);
                    uploadResult.ErrorCode = 1;
                }
                else if (file.Length > maxFileSize)
                {
                    logger.LogInformation("{FileName} of {Length} bytes is " +
                        "larger than the limit of {Limit} bytes (Err: 2)",
                        trustedFileNameForDisplay, file.Length, maxFileSize);
                    uploadResult.ErrorCode = 2;
                }
                else
                {
                    try
                    {
                        trustedFileNameForFileStorage = Path.GetRandomFileName();
                        var path = Path.Combine(env.ContentRootPath,
                            env.EnvironmentName, "unsafe_uploads",
                            trustedFileNameForFileStorage);

                        await using FileStream fs = new(path, FileMode.Create);
                        await file.CopyToAsync(fs);

                        logger.LogInformation("{FileName} saved at {Path}",
                            trustedFileNameForDisplay, path);
                        uploadResult.Uploaded = true;
                        uploadResult.StoredFileName = trustedFileNameForFileStorage;
                    }
                    catch (IOException ex)
                    {
                        logger.LogError("{FileName} error on upload (Err: 3): {Message}",
                            trustedFileNameForDisplay, ex.Message);
                        uploadResult.ErrorCode = 3;
                    }
                }

                filesProcessed++;
            }
            else
            {
                logger.LogInformation("{FileName} not uploaded because the " +
                    "request exceeded the allowed {Count} of files (Err: 4)",
                    trustedFileNameForDisplay, maxAllowedFiles);
                uploadResult.ErrorCode = 4;
            }

            uploadResults.Add(uploadResult);
        }

        return new CreatedResult(resourcePath, uploadResults);
    }
}

Dalam kode sebelumnya, GetRandomFileName dipanggil untuk menghasilkan nama file yang aman. Jangan pernah mempercayai nama file yang disediakan oleh browser, karena penyerang dapat memilih nama file yang ada yang menimpa file yang ada atau mengirim jalur yang mencoba menulis di luar aplikasi.

Aplikasi server harus mendaftarkan layanan pengontrol dan titik akhir pengontrol peta. Untuk informasi selengkapnya, lihat Perutean ke tindakan pengontrol di ASP.NET Core.

Membatalkan unggahan file

Komponen pengunggahan file dapat mendeteksi kapan pengguna telah membatalkan unggahan dengan menggunakan CancellationToken saat memanggil ke IBrowserFile.OpenReadStream atau StreamReader.ReadAsync.

Buat CancellationTokenSource untuk InputFile komponen. Di awal OnInputFileChange metode, periksa apakah unggahan sebelumnya sedang berlangsung.

Jika unggahan file sedang berlangsung:

Unggah file sisi server dengan kemajuan

Contoh berikut menunjukkan cara mengunggah file di aplikasi sisi server dengan kemajuan pengunggahan yang ditampilkan kepada pengguna.

Untuk menggunakan contoh berikut dalam aplikasi pengujian:

  • Buat folder untuk menyimpan file yang Development diunggah untuk lingkungan: Development/unsafe_uploads.
  • Konfigurasikan ukuran file maksimum (maxFileSize, 15 KB dalam contoh berikut) dan jumlah maksimum file yang diizinkan (maxAllowedFiles, 3 dalam contoh berikut).
  • Atur buffer ke nilai yang berbeda (10 KB dalam contoh berikut), jika diinginkan, untuk peningkatan granularitas dalam pelaporan yang sedang berlangsung. Kami tidak merekomendasikan penggunaan buffer yang lebih besar dari 30 KB karena masalah performa dan keamanan.

Peringatan

Contoh menyimpan file tanpa memindai kontennya, dan panduan dalam artikel ini tidak memperhitungkan praktik terbaik keamanan tambahan untuk file yang diunggah. Pada sistem penahapan dan produksi, nonaktifkan izin eksekusi pada folder unggah dan pindai file dengan API pemindai anti-virus/anti-malware segera setelah diunggah. Untuk informasi selengkapnya, lihat Mengunggah file di ASP.NET Core.

FileUpload3.razor:

@page "/file-upload-3"
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment

<PageTitle>File Upload 3</PageTitle>

<h1>File Upload Example 3</h1>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;
    private decimal progressPercent;

    private async Task LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();
        progressPercent = 0;

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                var trustedFileName = Path.GetRandomFileName();
                var path = Path.Combine(Environment.ContentRootPath,
                    Environment.EnvironmentName, "unsafe_uploads", trustedFileName);

                await using FileStream writeStream = new(path, FileMode.Create);
                using var readStream = file.OpenReadStream(maxFileSize);
                var bytesRead = 0;
                var totalRead = 0;
                var buffer = new byte[1024 * 10];

                while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
                {
                    totalRead += bytesRead;
                    await writeStream.WriteAsync(buffer, 0, bytesRead);
                    progressPercent = Decimal.Divide(totalRead, file.Size);
                    StateHasChanged();
                }

                loadedFiles.Add(file);

                Logger.LogInformation(
                    "Unsafe Filename: {UnsafeFilename} File saved: {Filename}",
                    file.Name, trustedFileName);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {FileName} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}
@page "/file-upload-3"
@using System 
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment

<h3>Upload Files</h3>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;
    private decimal progressPercent;

    private async Task LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();
        progressPercent = 0;

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                var trustedFileName = Path.GetRandomFileName();
                var path = Path.Combine(Environment.ContentRootPath,
                    Environment.EnvironmentName, "unsafe_uploads", trustedFileName);

                await using FileStream writeStream = new(path, FileMode.Create);
                using var readStream = file.OpenReadStream(maxFileSize);
                var bytesRead = 0;
                var totalRead = 0;
                var buffer = new byte[1024 * 10];

                while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
                {
                    totalRead += bytesRead;

                    await writeStream.WriteAsync(buffer, 0, bytesRead);

                    progressPercent = Decimal.Divide(totalRead, file.Size);

                    StateHasChanged();
                }

                loadedFiles.Add(file);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {FileName} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}
@page "/file-upload-3"
@using System 
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment

<h3>Upload Files</h3>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;
    private decimal progressPercent;

    private async Task LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();
        progressPercent = 0;

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                var trustedFileName = Path.GetRandomFileName();
                var path = Path.Combine(Environment.ContentRootPath,
                    Environment.EnvironmentName, "unsafe_uploads", trustedFileName);

                await using FileStream writeStream = new(path, FileMode.Create);
                using var readStream = file.OpenReadStream(maxFileSize);
                var bytesRead = 0;
                var totalRead = 0;
                var buffer = new byte[1024 * 10];

                while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
                {
                    totalRead += bytesRead;

                    await writeStream.WriteAsync(buffer, 0, bytesRead);

                    progressPercent = Decimal.Divide(totalRead, file.Size);

                    StateHasChanged();
                }

                loadedFiles.Add(file);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {Filename} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}
@page "/file-upload-3"
@using System 
@using System.IO
@using Microsoft.AspNetCore.Hosting
@using Microsoft.Extensions.Logging
@inject ILogger<FileUpload3> Logger
@inject IWebHostEnvironment Environment

<h3>Upload Files</h3>

<p>
    <label>
        Max file size:
        <input type="number" @bind="maxFileSize" />
    </label>
</p>

<p>
    <label>
        Max allowed files:
        <input type="number" @bind="maxAllowedFiles" />
    </label>
</p>

<p>
    <label>
        Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
        <InputFile OnChange="LoadFiles" multiple />
    </label>
</p>

@if (isLoading)
{
    <p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>Name: @file.Name</li>
                    <li>Last modified: @file.LastModified.ToString()</li>
                    <li>Size (bytes): @file.Size</li>
                    <li>Content type: @file.ContentType</li>
                </ul>
            </li>
        }
    </ul>
}

@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 15;
    private int maxAllowedFiles = 3;
    private bool isLoading;
    private decimal progressPercent;

    private async Task LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();
        progressPercent = 0;

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                var trustedFileName = Path.GetRandomFileName();
                var path = Path.Combine(Environment.ContentRootPath,
                    Environment.EnvironmentName, "unsafe_uploads", trustedFileName);

                await using FileStream writeStream = new(path, FileMode.Create);
                using var readStream = file.OpenReadStream(maxFileSize);
                var bytesRead = 0;
                var totalRead = 0;
                var buffer = new byte[1024 * 10];

                while ((bytesRead = await readStream.ReadAsync(buffer)) != 0)
                {
                    totalRead += bytesRead;

                    await writeStream.WriteAsync(buffer, 0, bytesRead);

                    progressPercent = Decimal.Divide(totalRead, file.Size);

                    StateHasChanged();
                }

                loadedFiles.Add(file);
            }
            catch (Exception ex)
            {
                Logger.LogError("File: {Filename} Error: {Error}", 
                    file.Name, ex.Message);
            }
        }

        isLoading = false;
    }
}

Untuk informasi selengkapnya, lihat sumber daya API berikut ini:

  • FileStream: Menyediakan Stream untuk file, mendukung operasi baca dan tulis yang sinkron dan asinkron.
  • FileStream.ReadAsync: Komponen sebelumnya FileUpload3 membaca aliran secara asinkron dengan ReadAsync. Membaca aliran secara sinkron dengan Read tidak didukung dalam Razor komponen.

Aliran file

Dengan interaktivitas server, data file dialirkan melalui SignalR koneksi ke dalam kode .NET di server saat file dibaca.

RemoteBrowserFileStreamOptions memungkinkan konfigurasi karakteristik pengunggahan file.

Untuk komponen yang dirender WebAssembly, data file dialirkan langsung ke kode .NET dalam browser.

Unggah pratinjau gambar

Untuk pratinjau gambar mengunggah gambar, mulailah dengan menambahkan InputFile komponen dengan referensi komponen dan handler OnChange :

<InputFile @ref="inputFile" OnChange="ShowPreview" />

Tambahkan elemen gambar dengan referensi elemen, yang berfungsi sebagai tempat penampung untuk pratinjau gambar:

<img @ref="previewImageElem" />

Tambahkan referensi terkait:

@code {
    private InputFile? inputFile;
    private ElementReference previewImageElem;
}

Di JavaScript, tambahkan fungsi yang disebut dengan HTML input dan img elemen yang melakukan hal berikut:

  • Mengekstrak file terpilih.
  • Membuat URL objek dengan createObjectURL.
  • Mengatur pendengar peristiwa untuk mencabut URL objek dengan revokeObjectURL setelah gambar dimuat, sehingga memori tidak bocor.
  • img Mengatur sumber elemen untuk menampilkan gambar.
window.previewImage = (inputElem, imgElem) => {
  const url = URL.createObjectURL(inputElem.files[0]);
  imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
  imgElem.src = url;
}

Terakhir, gunakan yang disuntikkan IJSRuntime untuk menambahkan OnChange handler yang memanggil fungsi JavaScript:

@inject IJSRuntime JS

...

@code {
    ...

    private async Task ShowPreview() => await JS.InvokeVoidAsync(
        "previewImage", inputFile!.Element, previewImageElem);
}

Contoh sebelumnya adalah untuk mengunggah satu gambar. Pendekatan dapat diperluas untuk mendukung multiple gambar.

Komponen berikut FileUpload4 menunjukkan contoh lengkapnya.

FileUpload4.razor:

@page "/file-upload-4"
@inject IJSRuntime JS

<h1>File Upload Example</h1>

<InputFile @ref="inputFile" OnChange="ShowPreview" />

<img style="max-width:200px;max-height:200px" @ref="previewImageElem" />

@code {
    private InputFile? inputFile;
    private ElementReference previewImageElem;

    private async Task ShowPreview() => await JS.InvokeVoidAsync(
        "previewImage", inputFile!.Element, previewImageElem);
}
@page "/file-upload-4"
@inject IJSRuntime JS

<h1>File Upload Example</h1>

<InputFile @ref="inputFile" OnChange="ShowPreview" />

<img style="max-width:200px;max-height:200px" @ref="previewImageElem" />

@code {
    private InputFile? inputFile;
    private ElementReference previewImageElem;

    private async Task ShowPreview() => await JS.InvokeVoidAsync(
        "previewImage", inputFile!.Element, previewImageElem);
}

Mengunggah file ke layanan eksternal

Alih-alih aplikasi yang menangani byte pengunggahan file dan server aplikasi yang menerima file yang diunggah, klien dapat langsung mengunggah file ke layanan eksternal. Aplikasi ini dapat memproses file dengan aman dari layanan eksternal sesuai permintaan. Pendekatan ini memperkuat aplikasi dan servernya terhadap serangan berbahaya dan potensi masalah performa.

Pertimbangkan pendekatan yang menggunakan Azure Files, Azure Blob Storage, atau layanan pihak ketiga dengan manfaat potensial berikut:

Untuk informasi selengkapnya tentang Azure Blob Storage dan Azure Files, lihat dokumentasi Azure Storage.

Batas ukuran pesan sisi SignalR server

Unggahan file mungkin gagal bahkan sebelum dimulai, saat Blazor mengambil data tentang file yang melebihi ukuran pesan maksimum SignalR .

SignalR menentukan batas ukuran pesan yang berlaku untuk setiap pesan Blazor yang diterima, dan InputFile komponen mengalirkan file ke server dalam pesan yang mematuhi batas yang dikonfigurasi. Namun, pesan pertama, yang menunjukkan kumpulan file yang akan diunggah, dikirim sebagai pesan tunggal yang unik. Ukuran pesan pertama mungkin melebihi SignalR batas ukuran pesan. Masalah ini tidak terkait dengan ukuran file, ini terkait dengan jumlah file.

Kesalahan yang dicatat mirip dengan yang berikut ini:

Kesalahan: Koneksi terputus dengan kesalahan 'Kesalahan: Server mengembalikan kesalahan saat ditutup: Koneksi ditutup dengan kesalahan.'. e.log @ blazor.server.js:1

Saat mengunggah file, mencapai batas ukuran pesan pada pesan pertama jarang terjadi. Jika batas tercapai, aplikasi dapat mengonfigurasi HubOptions.MaximumReceiveMessageSize dengan nilai yang lebih besar.

Untuk informasi selengkapnya tentang SignalR konfigurasi dan cara mengatur MaximumReceiveMessageSize, lihat panduan ASP.NET CoreBlazorSignalR.

Pemanggilan paralel maksimum per pengaturan hub klien

Blazor bergantung pada MaximumParallelInvocationsPerClient atur ke 1, yang merupakan nilai default.

Meningkatkan nilai mengarah pada probabilitas tinggi yang CopyTo dilemparkan System.InvalidOperationException: 'Reading is not allowed after reader was completed.'operasi . Untuk informasi selengkapnya, lihat MaximumParallelInvocationsPerClient > 1 memutus unggahan file dalam Blazor Server mode (dotnet/aspnetcore #53951).

Sumber Daya Tambahan: