Udostępnij za pośrednictwem


przekazywanie plików ASP.NET Core Blazor

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

W tym artykule wyjaśniono, jak przekazywać pliki za Blazor pomocą InputFile składnika.

Operacje przekazywania plików

Ostrzeżenie

Zawsze przestrzegaj najlepszych rozwiązań w zakresie zabezpieczeń, gdy zezwalają użytkownikom na przekazywanie plików. Aby uzyskać więcej informacji, zobacz Przekazywanie plików w programie ASP.NET Core.

InputFile Użyj składnika, aby odczytać dane pliku przeglądarki do kodu platformy .NET. Składnik InputFile renderuje element HTML <input> typu file. Domyślnie użytkownik wybiera pojedyncze pliki. multiple Dodaj atrybut, aby zezwolić użytkownikowi na przekazywanie wielu plików jednocześnie.

Wybór pliku nie jest skumulowany w przypadku używania składnika lub jego bazowego InputFilekodu HTML <input type="file">, więc nie można dodawać plików do istniejącego zaznaczenia pliku. Składnik zawsze zastępuje początkowy wybór pliku użytkownika, więc odwołania do plików z poprzednich wyborów nie są dostępne.

Poniższy InputFile składnik wykonuje metodę LoadFiles , gdy OnChange wystąpi zdarzenie (change). Element InputFileChangeEventArgs zapewnia dostęp do wybranej listy plików i szczegółów dotyczących każdego pliku:

<InputFile OnChange="LoadFiles" multiple />

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

Renderowany kod HTML:

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

Uwaga

W poprzednim przykładzie <input> atrybut elementu _bl_2 jest używany do Blazorwewnętrznego przetwarzania.

Aby odczytać dane z wybranego przez użytkownika pliku, wywołaj IBrowserFile.OpenReadStream plik i odczytaj z zwróconego strumienia. Aby uzyskać więcej informacji, zobacz sekcję Strumienie plików.

OpenReadStream wymusza maksymalny rozmiar w bajtach jego Streamwartości . Odczytywanie jednego pliku lub wielu plików większych niż 500 KB powoduje wyjątek. Ten limit uniemożliwia deweloperom przypadkowe odczytywanie dużych plików do pamięci. Parametr maxAllowedSize parametru można użyć do określenia większego rozmiaru OpenReadStream , jeśli jest to wymagane.

Jeśli potrzebujesz dostępu do elementu Stream reprezentującego bajty pliku, użyj polecenia IBrowserFile.OpenReadStream. Unikaj odczytywania przychodzącego strumienia plików bezpośrednio do pamięci jednocześnie. Na przykład nie kopiuj wszystkich bajtów pliku do MemoryStream ani nie odczytaj całego strumienia do tablicy bajtów jednocześnie. Te podejścia mogą powodować problemy z wydajnością i zabezpieczeniami, szczególnie w przypadku składników po stronie serwera. Zamiast tego należy rozważyć przyjęcie jednego z następujących podejść:

  • Skopiuj strumień bezpośrednio do pliku na dysku bez odczytywania go do pamięci. Należy pamiętać, że Blazor aplikacje wykonujące kod na serwerze nie mogą bezpośrednio uzyskać dostępu do systemu plików klienta.
  • Przekazywanie plików z klienta bezpośrednio do usługi zewnętrznej. Aby uzyskać więcej informacji, zobacz sekcję Przekazywanie plików do usługi zewnętrznej.

W poniższych przykładach browserFile reprezentuje przekazany plik i implementuje IBrowserFileelement . Działające implementacje programu IBrowserFile są wyświetlane w składnikach przekazywania plików w dalszej części tego artykułu.

Nieobsługiwane: Następujące podejście nie jest zalecane , ponieważ zawartość pliku Stream jest odczytywana w String pamięci (reader):

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

Nieobsługiwane: następujące podejście nie jest zalecane w przypadku usługi Microsoft Azure Blob Storage , ponieważ zawartość pliku Stream jest kopiowana do MemoryStream pamięci (memoryStream) przed wywołaniem metody UploadBlobAsync:

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

Obsługiwane: zalecane jest następujące podejście, ponieważ plik Stream jest udostępniany bezpośrednio użytkownikowi, czyli plik, który FileStream tworzy plik w podanej ścieżce:

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

Obsługiwane: w przypadku usługi Microsoft Azure Blob Storage zalecane jest następujące podejście, ponieważ plik Stream jest dostarczany bezpośrednio do UploadBlobAsyncusługi :

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

Składnik odbierający plik obrazu może wywołać BrowserFileExtensions.RequestImageFileAsync metodę wygody w pliku, aby zmienić rozmiar danych obrazu w środowisku uruchomieniowym JavaScript przeglądarki przed przesyłaniem strumieniowym obrazu do aplikacji. Przypadki użycia wywołania RequestImageFileAsync są najbardziej odpowiednie dla Blazor WebAssembly aplikacji.

Limity odczytu i przekazywania rozmiaru pliku

Po stronie serwera lub po stronie klienta nie ma limitu rozmiaru InputFile odczytu ani przekazywania pliku specjalnie dla składnika. Jednak po stronie Blazor klienta odczytuje bajty pliku do pojedynczego buforu tablicy JavaScript podczas marshalingu danych z języka JavaScript do języka C#, który jest ograniczony do 2 GB lub do dostępnej pamięci urządzenia. Przekazywanie dużych plików (> 250 MB) może zakończyć się niepowodzeniem w przypadku przekazywania po stronie klienta przy użyciu InputFile składnika. Aby uzyskać więcej informacji, zobacz następujące dyskusje:

Maksymalny obsługiwany rozmiar pliku dla InputFile składnika to 2 GB. Ponadto po stronie Blazor klienta odczytuje bajty pliku do pojedynczego buforu tablicy JavaScript podczas marshalingu danych z języka JavaScript do języka C#, który jest ograniczony do 2 GB lub do dostępnej pamięci urządzenia. Przekazywanie dużych plików (> 250 MB) może zakończyć się niepowodzeniem w przypadku przekazywania po stronie klienta przy użyciu InputFile składnika. Aby uzyskać więcej informacji, zobacz następujące dyskusje:

W przypadku przekazywania dużych plików po stronie klienta, które kończą się niepowodzeniem podczas próby użycia InputFile składnika, zalecamy fragmentowanie dużych plików za pomocą składnika niestandardowego przy użyciu wielu żądań zakresu HTTP zamiast używania InputFile składnika.

Obecnie zaplanowano pracę dla platformy .NET 9 (pod koniec 2024 r.), aby rozwiązać problem z ograniczeniem przekazywania plików po stronie klienta.

Przykłady

W poniższych przykładach pokazano wiele przekazywania plików w składniku. InputFileChangeEventArgs.GetMultipleFiles umożliwia odczytywanie wielu plików. Określ maksymalną liczbę plików, aby uniemożliwić złośliwemu użytkownikowi przekazywanie większej liczby plików niż oczekiwana przez aplikację. InputFileChangeEventArgs.File umożliwia odczytywanie pierwszego pliku i tylko wtedy, gdy przekazywanie pliku nie obsługuje wielu plików.

InputFileChangeEventArgs znajduje się w Microsoft.AspNetCore.Components.Forms przestrzeni nazw, która jest zazwyczaj jedną z przestrzeni nazw w pliku aplikacji _Imports.razor . Gdy przestrzeń nazw znajduje się w _Imports.razor pliku, zapewnia dostęp do składowych interfejsu API do składników aplikacji.

Przestrzenie nazw w _Imports.razor pliku nie są stosowane do plików C# (.cs). Pliki języka C# wymagają jawnej using dyrektywy w górnej części pliku klasy:

using Microsoft.AspNetCore.Components.Forms;

W przypadku testowania składników przekazywania plików można utworzyć pliki testowe o dowolnym rozmiarze za pomocą programu PowerShell:

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

W powyższym poleceniu:

  • Symbol {SIZE} zastępczy to rozmiar pliku w bajtach (na przykład 2097152 dla pliku o rozmiarze 2 MB).
  • Symbol {PATH} zastępczy to ścieżka i plik z rozszerzeniem pliku (na przykład D:/test_files/testfile2MB.txt).

Przykład przekazywania pliku po stronie serwera

Aby użyć następującego kodu, utwórz Development/unsafe_uploads folder w katalogu głównym aplikacji działającej Development w środowisku.

Ponieważ w przykładzie używane jest środowisko aplikacji jako część ścieżki, w której są zapisywane pliki, wymagane są dodatkowe foldery, jeśli inne środowiska są używane w testowaniu i środowisku produkcyjnym. Na przykład utwórz Staging/unsafe_uploads folder dla Staging środowiska. Production/unsafe_uploads Utwórz folder dla Production środowiska.

Ostrzeżenie

Przykład zapisuje pliki bez skanowania ich zawartości, a wskazówki zawarte w tym artykule nie uwzględniają dodatkowych najlepszych rozwiązań w zakresie zabezpieczeń przekazanych plików. W systemach przejściowych i produkcyjnych wyłącz uprawnienie do wykonywania w folderze przekazywania i skanuj pliki za pomocą interfejsu API skanera antywirusowego/chroniącego przed złośliwym oprogramowaniem natychmiast po przekazaniu. Aby uzyskać więcej informacji, zobacz Przekazywanie plików w programie 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;
    }
}

Przykład przekazywania pliku po stronie klienta

Poniższy przykład przetwarza bajty plików i nie wysyła plików do miejsca docelowego poza aplikacją. Razor Przykładowy składnik, który wysyła plik do serwera lub usługi, zobacz następujące sekcje:

Składnik zakłada, że tryb renderowania interactive WebAssembly (InteractiveWebAssembly) jest dziedziczony z składnika nadrzędnego lub stosowany globalnie do aplikacji.

@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 zwraca metadane uwidocznione przez przeglądarkę jako właściwości. Użyj tych metadanych do wstępnej weryfikacji.

Nigdy nie ufaj wartościom poprzednich właściwości, zwłaszcza Name właściwości do wyświetlania w interfejsie użytkownika. Traktuj wszystkie dane dostarczone przez użytkownika jako istotne zagrożenie bezpieczeństwa aplikacji, serwera i sieci. Aby uzyskać więcej informacji, zobacz Przekazywanie plików w programie ASP.NET Core.

Przekazywanie plików na serwer za pomocą renderowania po stronie serwera

Ta sekcja dotyczy składników interactive server w usłudze Blazor Web Apps.

Ta sekcja dotyczy Blazor Server aplikacji.

W poniższym przykładzie pokazano przekazywanie plików z aplikacji po stronie serwera do kontrolera internetowego interfejsu API zaplecza w oddzielnej aplikacji, prawdopodobnie na osobnym serwerze.

W pliku aplikacji Program po stronie serwera dodaj IHttpClientFactory i powiązane usługi, które umożliwiają aplikacji tworzenie HttpClient wystąpień:

builder.Services.AddHttpClient();

Aby uzyskać więcej informacji, zobacz Tworzenie żądań HTTP za pomocą interfejsu IHttpClientFactory na platformie ASP.NET Core.

Przykłady w tej sekcji:

  • Internetowy interfejs API jest uruchamiany pod adresem URL: https://localhost:5001
  • Aplikacja po stronie serwera działa pod adresem URL: https://localhost:5003

Na potrzeby testowania poprzednie adresy URL są konfigurowane w plikach projektów Properties/launchSettings.json .

Poniższa UploadResult klasa zachowuje wynik przekazanego pliku. Gdy przekazywanie pliku na serwerze nie powiedzie się, zostanie zwrócony ErrorCode kod błędu w celu wyświetlenia użytkownikowi. Bezpieczna nazwa pliku jest generowana na serwerze dla każdego pliku i zwracana do klienta w StoredFileName programie w celu wyświetlenia. Pliki są kluczami między klientem a serwerem przy użyciu niebezpiecznej/niezaufanej nazwy pliku w programie 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; }
}

Uwaga

Najlepszym rozwiązaniem w zakresie zabezpieczeń dla aplikacji produkcyjnych jest unikanie wysyłania komunikatów o błędach do klientów, którzy mogą ujawniać poufne informacje o aplikacji, serwerze lub sieci. Udostępnianie szczegółowych komunikatów o błędach może pomóc złośliwego użytkownika w opracowywaniu ataków na aplikację, serwer lub sieć. Przykładowy kod w tej sekcji wysyła tylko numer kodu błędu (int) do wyświetlenia po stronie klienta składnika, jeśli wystąpi błąd po stronie serwera. Jeśli użytkownik wymaga pomocy przy przekazaniu pliku, podaj kod błędu pomocy technicznej dla personelu pomocy technicznej w celu rozwiązania biletu pomocy technicznej bez znajomości dokładnej przyczyny błędu.

FileUpload2 Następujący składnik:

  • Zezwala użytkownikom na przekazywanie plików z klienta.
  • Wyświetla niezaufaną/niebezpieczną nazwę pliku podaną przez klienta w interfejsie użytkownika. Niezaufana/niebezpieczna nazwa pliku jest automatycznie zakodowana w Razor formacie HTML w celu bezpiecznego wyświetlania w interfejsie użytkownika.

Ostrzeżenie

Nie ufaj nazwam plików dostarczonym przez klientów dla:

  • Zapisywanie pliku w systemie plików lub usłudze.
  • Wyświetlaj w interfejsach użytkownika, które nie kodują automatycznie nazw plików ani za pośrednictwem kodu dewelopera.

Aby uzyskać więcej informacji na temat zagadnień dotyczących zabezpieczeń podczas przekazywania plików na serwer, zobacz Przekazywanie plików w programie 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; }
    }
}

Następujący kontroler w projekcie internetowego interfejsu API zapisuje przekazane pliki z klienta.

Ważne

Kontroler w tej sekcji jest przeznaczony do użycia w osobnym projekcie internetowego interfejsu Blazor API z aplikacji. Internetowy interfejs API powinien ograniczyć ryzyko fałszowania żądań między witrynami (XSRF/CSRF) w przypadku uwierzytelnienia użytkowników przekazywania plików.

Uwaga

Powiązanie wartości formularza z atrybutem[FromForm]nie jest natywnie obsługiwane dla minimalnych interfejsów API w programie ASP.NET Core na platformie .NET 6. W związku z tym nie można przekonwertować następującego Filesave przykładu kontrolera w celu użycia interfejsów API minimalnych. Obsługa powiązań z wartości formularza z minimalnymi interfejsami API jest dostępna w programie ASP.NET Core na platformie .NET 7 lub nowszym.

Aby użyć następującego kodu, utwórz Development/unsafe_uploads folder w katalogu głównym projektu internetowego interfejsu API dla aplikacji działającej Development w środowisku.

Ponieważ w przykładzie używane jest środowisko aplikacji jako część ścieżki, w której są zapisywane pliki, wymagane są dodatkowe foldery, jeśli inne środowiska są używane w testowaniu i środowisku produkcyjnym. Na przykład utwórz Staging/unsafe_uploads folder dla Staging środowiska. Production/unsafe_uploads Utwórz folder dla Production środowiska.

Ostrzeżenie

Przykład zapisuje pliki bez skanowania ich zawartości, a wskazówki zawarte w tym artykule nie uwzględniają dodatkowych najlepszych rozwiązań w zakresie zabezpieczeń przekazanych plików. W systemach przejściowych i produkcyjnych wyłącz uprawnienie do wykonywania w folderze przekazywania i skanuj pliki za pomocą interfejsu API skanera antywirusowego/chroniącego przed złośliwym oprogramowaniem natychmiast po przekazaniu. Aby uzyskać więcej informacji, zobacz Przekazywanie plików w programie 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);
    }
}

W poprzednim kodzie GetRandomFileName jest wywoływana w celu wygenerowania bezpiecznej nazwy pliku. Nigdy nie ufaj nazwie pliku podanej przez przeglądarkę, ponieważ osoba atakująca może wybrać istniejącą nazwę pliku, która zastępuje istniejący plik lub wysyła ścieżkę, która próbuje zapisać poza aplikacją.

Aplikacja serwera musi rejestrować usługi kontrolera i punkty końcowe kontrolera mapy. Aby uzyskać więcej informacji, zobacz Routing do akcji kontrolera w ASP.NET Core.

Przekazywanie plików na serwer

W poniższym przykładzie pokazano przekazywanie plików do internetowego kontrolera interfejsu API.

Poniższa UploadResult klasa zachowuje wynik przekazanego pliku. Gdy przekazywanie pliku na serwerze nie powiedzie się, zostanie zwrócony ErrorCode kod błędu w celu wyświetlenia użytkownikowi. Bezpieczna nazwa pliku jest generowana na serwerze dla każdego pliku i zwracana do klienta w StoredFileName programie w celu wyświetlenia. Pliki są kluczami między klientem a serwerem przy użyciu niebezpiecznej/niezaufanej nazwy pliku w programie 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; }
}

Uwaga

Poprzednia UploadResult klasa może być współdzielona między projektami klienckimi i serwerowymi. Gdy projekty klienta i serwera współużytkuje klasę, dodaj import do pliku każdego projektu _Imports.razor dla udostępnionego projektu. Na przykład:

@using BlazorSample.Shared

FileUpload2 Następujący składnik:

  • Zezwala użytkownikom na przekazywanie plików z klienta.
  • Wyświetla niezaufaną/niebezpieczną nazwę pliku podaną przez klienta w interfejsie użytkownika. Niezaufana/niebezpieczna nazwa pliku jest automatycznie zakodowana w Razor formacie HTML w celu bezpiecznego wyświetlania w interfejsie użytkownika.

Najlepszym rozwiązaniem w zakresie zabezpieczeń dla aplikacji produkcyjnych jest unikanie wysyłania komunikatów o błędach do klientów, którzy mogą ujawniać poufne informacje o aplikacji, serwerze lub sieci. Udostępnianie szczegółowych komunikatów o błędach może pomóc złośliwego użytkownika w opracowywaniu ataków na aplikację, serwer lub sieć. Przykładowy kod w tej sekcji wysyła tylko numer kodu błędu (int) do wyświetlenia po stronie klienta składnika, jeśli wystąpi błąd po stronie serwera. Jeśli użytkownik wymaga pomocy przy przekazaniu pliku, podaj kod błędu pomocy technicznej dla personelu pomocy technicznej w celu rozwiązania biletu pomocy technicznej bez znajomości dokładnej przyczyny błędu.

Ostrzeżenie

Nie ufaj nazwam plików dostarczonym przez klientów dla:

  • Zapisywanie pliku w systemie plików lub usłudze.
  • Wyświetlaj w interfejsach użytkownika, które nie kodują automatycznie nazw plików ani za pośrednictwem kodu dewelopera.

Aby uzyskać więcej informacji na temat zagadnień dotyczących zabezpieczeń podczas przekazywania plików na serwer, zobacz Przekazywanie plików w programie ASP.NET Core.

W głównym projekcie Blazor aplikacji internetowej dodaj IHttpClientFactory i powiązane usługi w pliku projektu Program :

builder.Services.AddHttpClient();

Usługi HttpClient należy dodać do głównego projektu, ponieważ składnik po stronie klienta jest wstępnie zainstalowany na serwerze. Jeśli wyłączysz prerendering dla następującego składnika, nie musisz udostępniać HttpClient usług w głównej aplikacji i nie musisz dodawać poprzedniego wiersza do głównego projektu.

Aby uzyskać więcej informacji na temat dodawania HttpClient usług do aplikacji ASP.NET Core, zobacz Make HTTP requests using IHttpClientFactory in ASP.NET Core (Tworzenie żądań HTTP przy użyciu elementu IHttpClientFactory w programie ASP.NET Core).

Projekt klienta (.Client) Blazor aplikacji internetowej musi również zarejestrować HttpClient żądania HTTP POST do kontrolera internetowego interfejsu API zaplecza. Potwierdź lub dodaj następujący kod do pliku projektu Program klienta:

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

W poprzednim przykładzie ustawiono adres podstawowy (IWebAssemblyHostEnvironment.BaseAddress), który pobiera adres builder.HostEnvironment.BaseAddress podstawowy dla aplikacji i jest zwykle pobierany z <base> wartości tagu href na stronie hosta. Jeśli wywołujesz zewnętrzny internetowy interfejs API, ustaw identyfikator URI na adres podstawowy internetowego interfejsu API.

Określ atrybut Tryb renderowania interactive WebAssembly w górnej części następującego składnika w Blazor aplikacji internetowej:

@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; }
    }
}

Następujący kontroler w projekcie po stronie serwera zapisuje przekazane pliki z klienta.

Uwaga

Powiązanie wartości formularza z atrybutem[FromForm]nie jest natywnie obsługiwane dla minimalnych interfejsów API w programie ASP.NET Core na platformie .NET 6. W związku z tym nie można przekonwertować następującego Filesave przykładu kontrolera w celu użycia interfejsów API minimalnych. Obsługa powiązań z wartości formularza z minimalnymi interfejsami API jest dostępna w programie ASP.NET Core na platformie .NET 7 lub nowszym.

Aby użyć następującego kodu, utwórz Development/unsafe_uploads folder w katalogu głównym projektu po stronie serwera dla aplikacji działającej Development w środowisku.

Ponieważ w przykładzie używane jest środowisko aplikacji jako część ścieżki, w której są zapisywane pliki, wymagane są dodatkowe foldery, jeśli inne środowiska są używane w testowaniu i środowisku produkcyjnym. Na przykład utwórz Staging/unsafe_uploads folder dla Staging środowiska. Production/unsafe_uploads Utwórz folder dla Production środowiska.

Ostrzeżenie

Przykład zapisuje pliki bez skanowania ich zawartości, a wskazówki zawarte w tym artykule nie uwzględniają dodatkowych najlepszych rozwiązań w zakresie zabezpieczeń przekazanych plików. W systemach przejściowych i produkcyjnych wyłącz uprawnienie do wykonywania w folderze przekazywania i skanuj pliki za pomocą interfejsu API skanera antywirusowego/chroniącego przed złośliwym oprogramowaniem natychmiast po przekazaniu. Aby uzyskać więcej informacji, zobacz Przekazywanie plików w programie ASP.NET Core.

W poniższym przykładzie zaktualizuj przestrzeń nazw udostępnionego projektu tak, aby odpowiadała projektowi udostępnionemu, jeśli udostępniony projekt dostarcza klasę UploadResult .

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);
    }
}

W poprzednim kodzie GetRandomFileName jest wywoływana w celu wygenerowania bezpiecznej nazwy pliku. Nigdy nie ufaj nazwie pliku podanej przez przeglądarkę, ponieważ osoba atakująca może wybrać istniejącą nazwę pliku, która zastępuje istniejący plik lub wysyła ścieżkę, która próbuje zapisać poza aplikacją.

Aplikacja serwera musi rejestrować usługi kontrolera i punkty końcowe kontrolera mapy. Aby uzyskać więcej informacji, zobacz Routing do akcji kontrolera w ASP.NET Core.

Anulowanie przekazywania pliku

Składnik przekazywania plików może wykryć, kiedy użytkownik anulował przekazywanie przy użyciu CancellationToken elementu podczas wywoływania elementu IBrowserFile.OpenReadStream lub StreamReader.ReadAsync.

Utwórz element CancellationTokenSource dla InputFile składnika. Na początku OnInputFileChange metody sprawdź, czy poprzednie przekazywanie jest w toku.

Jeśli przekazywanie pliku jest w toku:

Przekazywanie plików po stronie serwera z postępem

W poniższym przykładzie pokazano, jak przekazywać pliki w aplikacji po stronie serwera z postępem przekazywania wyświetlanym użytkownikowi.

Aby użyć następującego przykładu w aplikacji testowej:

  • Utwórz folder do zapisania przekazanych plików dla Development środowiska: Development/unsafe_uploads.
  • Skonfiguruj maksymalny rozmiar pliku (maxFileSize15 KB w poniższym przykładzie) i maksymalną liczbę dozwolonych plików (maxAllowedFiles3 w poniższym przykładzie).
  • Ustaw bufor na inną wartość (10 KB w poniższym przykładzie), jeśli jest to konieczne, aby zwiększyć stopień szczegółowości w toku raportowania. Nie zalecamy używania buforu większego niż 30 KB z powodu problemów z wydajnością i zabezpieczeniami.

Ostrzeżenie

Przykład zapisuje pliki bez skanowania ich zawartości, a wskazówki zawarte w tym artykule nie uwzględniają dodatkowych najlepszych rozwiązań w zakresie zabezpieczeń przekazanych plików. W systemach przejściowych i produkcyjnych wyłącz uprawnienie do wykonywania w folderze przekazywania i skanuj pliki za pomocą interfejsu API skanera antywirusowego/chroniącego przed złośliwym oprogramowaniem natychmiast po przekazaniu. Aby uzyskać więcej informacji, zobacz Przekazywanie plików w programie 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;
    }
}

Aby uzyskać więcej informacji, zobacz następujące zasoby interfejsu API:

  • FileStream: udostępnia Stream element dla pliku, który obsługuje zarówno operacje synchroniczne, jak i asynchroniczne operacji odczytu i zapisu.
  • FileStream.ReadAsync: Poprzedni FileUpload3 składnik odczytuje strumień asynchronicznie za pomocą polecenia ReadAsync. Odczytywanie strumienia synchronicznie z elementem Read nie jest obsługiwane w Razor składnikach.

Strumienie plików

W przypadku interakcyjności serwera dane plików są przesyłane strumieniowo za pośrednictwem SignalR połączenia z kodem platformy .NET na serwerze, ponieważ plik jest odczytywany.

RemoteBrowserFileStreamOptions umożliwia konfigurowanie właściwości przekazywania plików.

W przypadku składnika renderowanego za pomocą zestawu WebAssembly dane plików są przesyłane strumieniowo bezpośrednio do kodu platformy .NET w przeglądarce.

Przekazywanie podglądu obrazu

Aby uzyskać podgląd obrazu przekazywania obrazów, zacznij od dodania InputFile składnika z odwołaniem do składnika i procedurą OnChange obsługi:

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

Dodaj element obrazu z odwołaniem do elementu, który służy jako symbol zastępczy podglądu obrazu:

<img @ref="previewImageElem" />

Dodaj skojarzone odwołania:

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

W języku JavaScript dodaj funkcję o nazwie z kodem HTML input i img elementem, który wykonuje następujące czynności:

  • Wyodrębnia wybrany plik.
  • Tworzy adres URL obiektu za pomocą polecenia createObjectURL.
  • Ustawia odbiornik zdarzeń, aby odwołać adres URL obiektu za pomocą revokeObjectURL polecenia po załadowaniu obrazu, więc pamięć nie zostanie ujawniona.
  • img Ustawia źródło elementu, aby wyświetlić obraz.
window.previewImage = (inputElem, imgElem) => {
  const url = URL.createObjectURL(inputElem.files[0]);
  imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
  imgElem.src = url;
}

Na koniec użyj wstrzykniętego IJSRuntime elementu , aby dodać procedurę OnChange obsługi, która wywołuje funkcję JavaScript:

@inject IJSRuntime JS

...

@code {
    ...

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

Powyższy przykład dotyczy przekazywania pojedynczego obrazu. Podejście można rozszerzyć w celu obsługi multiple obrazów.

FileUpload4 Poniższy składnik przedstawia kompletny przykład.

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);
}

Przekazywanie plików do usługi zewnętrznej

Zamiast aplikacji obsługującej bajty przekazywania plików i serwera aplikacji odbierającego przekazane pliki, klienci mogą bezpośrednio przekazywać pliki do usługi zewnętrznej. Aplikacja może bezpiecznie przetwarzać pliki z usługi zewnętrznej na żądanie. Takie podejście wzmacnia zabezpieczenia aplikacji i jej serwera przed złośliwymi atakami i potencjalnymi problemami z wydajnością.

Rozważmy podejście korzystające z usługi Azure Files, Azure Blob Storage lub usługi innej firmy z następującymi potencjalnymi korzyściami:

Aby uzyskać więcej informacji na temat usług Azure Blob Storage i Azure Files, zobacz dokumentację usługi Azure Storage.

Limit rozmiaru komunikatów po stronie SignalR serwera

Przekazywanie plików może zakończyć się niepowodzeniem nawet przed rozpoczęciem, gdy Blazor pobiera dane dotyczące plików, które przekraczają maksymalny SignalR rozmiar komunikatu.

SignalR Definiuje limit rozmiaru komunikatów, który ma zastosowanie do każdego odbieranego komunikatu Blazor , a InputFile składnik przesyła strumieniowo pliki do serwera w komunikatach, które przestrzegają skonfigurowanego limitu. Jednak pierwszy komunikat, który wskazuje zestaw plików do przekazania, jest wysyłany jako unikatowy pojedynczy komunikat. Rozmiar pierwszego komunikatu może przekroczyć limit rozmiaru komunikatu SignalR . Problem nie jest związany z rozmiarem plików, jest związany z liczbą plików.

Zarejestrowany błąd jest podobny do następującego:

Błąd: Połączenie ion rozłączony z błędem "Błąd: Serwer zwrócił błąd podczas zamykania: Połączenie ion został zamknięty z powodu błędu. e.log @ blazor.server.js:1

Podczas przekazywania plików osiągnięcie limitu rozmiaru komunikatu dla pierwszego komunikatu jest rzadkie. Jeśli limit zostanie osiągnięty, aplikacja może skonfigurować HubOptions.MaximumReceiveMessageSize z większą wartością.

Aby uzyskać więcej informacji na SignalR temat konfiguracji i sposobu ustawiania MaximumReceiveMessageSizeprogramu , zobacz wskazówki dotyczące platformy ASP.NET CoreBlazorSignalR.

Maksymalna liczba wywołań równoległych na ustawienie centrum klienta

Blazor wartość jest ustawiona na MaximumParallelInvocationsPerClient wartość 1, która jest wartością domyślną.

Zwiększenie wartości prowadzi do wysokiego prawdopodobieństwa, że CopyTo operacje zgłaszają wartość System.InvalidOperationException: 'Reading is not allowed after reader was completed.'. Aby uzyskać więcej informacji, zobacz MaximumParallelInvocationsPerClient > 1 przerywa przekazywanie plików w Blazor Server trybie (dotnet/aspnetcore #53951).

Dodatkowe zasoby