Dela via


ASP.NET Core Blazor filuppladdningar

Notera

Det här är inte den senaste versionen av den här artikeln. För den aktuella versionen, se .NET 9-versionen av artikeln.

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. Den aktuella utgåvan finns i den .NET 9-versionen av den här artikeln.

Viktig

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.

Den här artikeln beskriver hur du laddar upp filer i Blazor med komponenten InputFile.

Filuppladdningar

Varning

Följ alltid rekommenderade säkerhetsmetoder när du tillåter användare att ladda upp filer. Mer information finns i Ladda upp filer i ASP.NET Core.

Använd komponenten InputFile för att läsa webbläsarfildata till .NET-kod. Komponenten InputFile renderar ett HTML-<input> element av typen file för enstaka filuppladdningar. Lägg till attributet multiple så att användaren kan ladda upp flera filer samtidigt.

Filval är inte kumulativt när du använder en InputFile-komponent eller dess underliggande HTML-<input type="file">, så du kan inte lägga till filer till en befintlig filval. Komponenten ersätter alltid användarens ursprungliga filval, så filreferenser från tidigare val är inte tillgängliga.

Följande InputFile-komponent kör LoadFiles-metoden när händelsen OnChange (change) inträffar. En InputFileChangeEventArgs ger åtkomst till den valda fillistan och information om varje fil:

<InputFile OnChange="LoadFiles" multiple />

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

Renderad HTML:

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

Notera

I föregående exempel används <input>-elementets _bl_2-attribut för Blazorinterna bearbetning.

Om du vill läsa data från en användarvald fil med en Stream som representerar filens byte anropar du IBrowserFile.OpenReadStream på filen och läser från den returnerade strömmen. Mer information finns i avsnittet Filströmmar.

OpenReadStream tvingar fram en maximal storlek i bytes av sin Stream. Att läsa en fil eller flera filer som är större än 500 KB resulterar i ett undantag. Den här gränsen hindrar utvecklare från att oavsiktligt läsa stora filer i minnet. Parametern maxAllowedSize för OpenReadStream kan användas för att ange en större storlek om det behövs.

Undvik att läsa den inkommande filströmmen direkt i minnet samtidigt utanför bearbetningen av en liten fil. Kopiera till exempel inte alla filens byte till en MemoryStream eller läs hela strömmen till en bytematris samtidigt. Dessa metoder kan resultera i försämrad appprestanda och potentiell DoS (Denial of Service) risk, särskilt för komponenter på serversidan. Överväg i stället att använda någon av följande metoder:

  • Kopiera strömmen direkt till en fil på disken utan att läsa den i minnet. Observera att Blazor appar som kör kod på servern inte kan komma åt klientens filsystem direkt.
  • Ladda upp filer från klienten direkt till en extern tjänst. Mer information finns i avsnittet Ladda upp filer till en extern tjänst.

I följande exempel implementerar browserFileIBrowserFile för att representera en uppladdad fil. Arbetsimplementeringar för IBrowserFile visas i filuppladdningskomponenterna senare i den här artikeln.

När du anropar OpenReadStreamrekommenderar vi att du skickar en maximal tillåten filstorlek i parametern maxAllowedSize vid gränsen för de filstorlekar som du förväntar dig att få. Standardvärdet är 500 KB. I den här artikelns exempel används en maximal tillåten filstorleksvariabel eller konstant med namnet maxFileSize men visar vanligtvis inte inställningen av ett specifikt värde.

✔️ stöds: Följande metod rekommenderas eftersom filens Stream tillhandahålls direkt till konsumenten, en FileStream som skapar filen på den angivna sökvägen:

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

stöds: Följande metod rekommenderas för Microsoft Azure Blob Storage eftersom filens Stream tillhandahålls direkt till UploadBlobAsync:

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

✔️ Rekommenderas endast för små filer: Följande metod rekommenderas endast för små filer eftersom filens Stream innehåll läss in i en MemoryStream i minnet (memoryStream), vilket medför en prestandaavgift och DoS risk. Ett exempel som visar den här tekniken för att spara en miniatyrbild med en IBrowserFile till en databas med Entity Framework Core (EF Core)finns i avsnittet Spara små filer direkt till en databas med EF Core senare i den här artikeln.

using var memoryStream = new MemoryStream();
await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);
var smallFileByteArray = memoryStream.ToArray();

Rekommenderas inte: Följande metod rekommenderas inte eftersom filens Stream innehåll läss in i en String i minnet (reader):

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

Rekommenderas inte: Följande metod rekommenderas INTE för Microsoft Azure Blob Storage eftersom filens Stream innehåll kopieras till MemoryStream i minne (memoryStream) innan UploadBlobAsyncanropas:

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

En komponent som tar emot en bildfil kan anropa bekvämlighetsmetoden BrowserFileExtensions.RequestImageFileAsync på filen för att ändra storleken på bilddatan i webbläsarens JavaScript-miljö innan bilden strömmas till appen. Användningsfall för att anropa RequestImageFileAsync är lämpligast för Blazor WebAssembly appar.

Autofac Inversion av kontroll (IoC) containrar användare

Om du använder IoC-container (Autofac Inversion of Control) i stället för den inbyggda containern för ASP.NET Core-beroendeinmatning anger du DisableImplicitFromServicesParameters till true i serversidans hubbalternativ för kretshanterare. Mer information finns i FileUpload: Tog inte emot några data under den tilldelade tiden (dotnet/aspnetcore #38842).

Läs- och uppladdningsgränser för filstorlek

För Chromium-baserade webbläsare (till exempel Google Chrome och Microsoft Edge) som använder HTTP/2-protokollet, HTTPS och CORS, stöds klient-sidan Blazor av Streams API för att möjliggöra uppladdning av stora filer med begäran strömning.

Utan en Chromium-webbläsare, HTTP/2-protokoll eller HTTPS, läser klienten Blazor filens byte till en enda JavaScript-arraybuffer när data överförs från JavaScript till C#, vilket är begränsat till 2 GB eller enhetens tillgängliga minne. Stora filuppladdningar kan misslyckas för uppladdningar på klientsidan med hjälp av komponenten InputFile.

Blazor på klientsidan läser in filens byte i en enda JavaScript-matrisbuffert när du konverterar data från JavaScript till C#, vilket är begränsat till 2 GB eller till enhetens tillgängliga minne. Stora filuppladdningar kan misslyckas för uppladdningar på klientsidan med hjälp av komponenten InputFile. Vi rekommenderar att du antar med ASP.NET Core 9.0 eller senare.

Säkerhetshänsyn

Undvik IBrowserFile.Size för filstorleksgränser

Undvik att använda IBrowserFile.Size för att införa en gräns för filstorleken. I stället för att använda den osäkra filstorleken som tillhandahålls av klienten anger du uttryckligen den maximala filstorleken. I följande exempel används den maximala filstorleken som tilldelats maxFileSize:

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

Säkerhet för filnamn

Använd aldrig ett filnamn som tillhandahålls av klienten för att spara en fil till fysisk lagring. Skapa ett säkert filnamn för filen med hjälp av Path.GetRandomFileName() eller Path.GetTempFileName() för att skapa en fullständig sökväg (inklusive filnamnet) för tillfällig lagring.

Razor HTML-kodar automatiskt egenskapsvärden för visning. Följande kod är säker att använda:

@foreach (var file in Model.DatabaseFiles) {
    <tr>
        <td>
            @file.UntrustedName
        </td>
    </tr>
}

Utanför Razoranvänder du alltid HtmlEncode för att på ett säkert sätt koda filnamn från en användares begäran.

Många implementeringar måste innehålla en kontroll av att filen finns. Annars skrivs filen över av en fil med samma namn. Ange ytterligare logik för att uppfylla appens specifikationer.

Exempel

I följande exempel visas flera filuppladdningar i en komponent. InputFileChangeEventArgs.GetMultipleFiles tillåter läsning av flera filer. Ange det maximala antalet filer för att förhindra att en obehörig användare laddar upp ett större antal filer än vad appen förväntar sig. InputFileChangeEventArgs.File tillåter läsning av den första och enda filen om filuppladdningen inte stöder flera filer.

InputFileChangeEventArgs finns i Microsoft.AspNetCore.Components.Forms namnrymd, vilket vanligtvis är en av namnrymderna i appens _Imports.razor-fil. När namnområdet finns i filen _Imports.razor ger det API-medlemsåtkomst till appens komponenter.

Namnområden i _Imports.razor-filen tillämpas inte på C#-filer (.cs). C#-filer kräver ett explicit using direktiv högst upp i klassfilen:

using Microsoft.AspNetCore.Components.Forms;

För att testa filuppladdningskomponenter kan du skapa testfiler av valfri storlek med PowerShell-:

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

I föregående kommando:

  • Platshållaren {SIZE} är storleken på filen i byte (till exempel 2097152 för en fil på 2 MB).
  • Platshållaren {PATH} är sökvägen och filen med filnamnstillägget (till exempel D:/test_files/testfile2MB.txt).

Exempel på filuppladdning på serversidan

Om du vill använda följande kod skapar du en Development/unsafe_uploads mapp i roten för appen som körs i den Development miljön.

Eftersom exemplet använder appens miljö som en del av sökvägen där filer sparas, krävs ytterligare mappar om andra miljöer används i testning och produktion. Skapa till exempel en Staging/unsafe_uploads mapp för den Staging miljön. Skapa en Production/unsafe_uploads mapp för den Production miljön.

Varning

I exemplet sparas filer utan att deras innehåll genomsöks, och riktlinjerna i den här artikeln tar inte hänsyn till ytterligare säkerhetsmetoder för uppladdade filer. På mellanlagrings- och produktionssystem inaktiverar du körningsbehörigheten för uppladdningsmappen och genomsöker filer med ett API för skanner mot virus/skadlig kod omedelbart efter uppladdningen. Mer information finns i Ladda upp filer i 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 = [];
    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;
    }
}

Exempel på filuppladdning på klientsidan

I följande exempel bearbetas filbyte och filer skickas inte till ett mål utanför appen. Ett exempel på en Razor komponent som skickar en fil till en server eller tjänst finns i följande avsnitt:

Komponenten förutsätter att det interaktiva webassembly-återgivningsläget (InteractiveWebAssembly) ärvs från en överordnad komponent eller tillämpas globalt på appen.

@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 = [];
    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 returnerar metadata som exponeras av webbläsaren som egenskaper. Använd dessa metadata för preliminär validering.

Lita aldrig på värdena för föregående egenskaper, särskilt egenskapen Name för visning i användargränssnittet. Behandla alla data från användaren som en betydande säkerhetsrisk för appen, servern och nätverket. Mer information finns i Ladda upp filer i ASP.NET Core.

Ladda upp filer till en server med återgivning på serversidan

Det här avsnittet gäller interaktiva serverkomponenter i Blazor Web Appeller Blazor Server appar.

I följande exempel visas hur du laddar upp filer från en app på serversidan till en webb-API-styrenhet för serverdelen i en separat app, eventuellt på en separat server.

I serverappens Program-fil, lägg till IHttpClientFactory och relaterade tjänster som gör att appen kan skapa HttpClient-exemplar:

builder.Services.AddHttpClient();

Mer information finns i Gör HTTP-begäranden med IHttpClientFactory i ASP.NET Core.

För exemplen i det här avsnittet:

  • Webb-API:et körs på URL:en: https://localhost:5001
  • Appen på serversidan körs på URL:en: https://localhost:5003

För testning konfigureras de föregående URL:erna i projektens Properties/launchSettings.json filer.

Följande UploadResult-klass underhåller resultatet av en uppladdad fil. När en fil inte kan laddas upp på servern returneras en felkod i ErrorCode för visning till användaren. Ett säkert filnamn genereras på servern för varje fil och returneras till klienten i StoredFileName för visning. Filer nyckelas mellan klienten och servern med hjälp av det osäkra/ej betrodda filnamnet i 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; }
}

En säkerhetsmetod för produktionsappar är att undvika att skicka felmeddelanden till klienter som kan avslöja känslig information om en app, server eller ett nätverk. Att tillhandahålla detaljerade felmeddelanden kan hjälpa en illvillig användare att utforma attacker på en app, server eller ett nätverk. Exempelkoden i det här avsnittet skickar bara tillbaka ett felkodnummer (int) för visning av komponentklientsidan om ett fel på serversidan inträffar. Om en användare behöver hjälp med en filuppladdning tillhandahåller de felkoden för att stödja personal för lösning av supportbegäran utan att någonsin känna till den exakta orsaken till felet.

Följande LazyBrowserFileStream-klass definierar en anpassad strömtyp som latt anropar OpenReadStream precis innan de första byteen i strömmen begärs. Strömmen överförs inte från webbläsaren till servern förrän läsningen av strömmen börjar i .NET.

LazyBrowserFileStream.cs:

using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;

namespace BlazorSample;

internal sealed class LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize) 
    : Stream
{
    private readonly IBrowserFile file = file;
    private readonly int maxAllowedSize = maxAllowedSize;
    private Stream? underlyingStream;
    private bool isDisposed;

    public override bool CanRead => true;

    public override bool CanSeek => false;

    public override bool CanWrite => false;

    public override long Length => file.Size;

    public override long Position
    {
        get => underlyingStream?.Position ?? 0;
        set => throw new NotSupportedException();
    }

    public override void Flush() => underlyingStream?.Flush();

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, 
        CancellationToken cancellationToken)
    {
        EnsureStreamIsOpen();

        return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
    }

    public override ValueTask<int> ReadAsync(Memory<byte> buffer, 
        CancellationToken cancellationToken = default)
    {
        EnsureStreamIsOpen();
        return underlyingStream.ReadAsync(buffer, cancellationToken);
    }

    [MemberNotNull(nameof(underlyingStream))]
    private void EnsureStreamIsOpen() => 
        underlyingStream ??= file.OpenReadStream(maxAllowedSize);

    protected override void Dispose(bool disposing)
    {
        if (isDisposed)
        {
            return;
        }

        underlyingStream?.Dispose();
        isDisposed = true;

        base.Dispose(disposing);
    }

    public override int Read(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();

    public override long Seek(long offset, SeekOrigin origin)
        => throw new NotSupportedException();

    public override void SetLength(long value)
        => throw new NotSupportedException();

    public override void Write(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();
}
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;

namespace BlazorSample;

internal sealed class LazyBrowserFileStream : Stream
{
    private readonly IBrowserFile file;
    private readonly int maxAllowedSize;
    private Stream? underlyingStream;
    private bool isDisposed;

    public override bool CanRead => true;

    public override bool CanSeek => false;

    public override bool CanWrite => false;

    public override long Length => file.Size;

    public override long Position
    {
        get => underlyingStream?.Position ?? 0;
        set => throw new NotSupportedException();
    }

    public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
    {
        this.file = file;
        this.maxAllowedSize = maxAllowedSize;
    }

    public override void Flush()
    {
        underlyingStream?.Flush();
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, 
        CancellationToken cancellationToken)
    {
        EnsureStreamIsOpen();

        return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
    }

    public override ValueTask<int> ReadAsync(Memory<byte> buffer, 
        CancellationToken cancellationToken = default)
    {
        EnsureStreamIsOpen();
        return underlyingStream.ReadAsync(buffer, cancellationToken);
    }

    [MemberNotNull(nameof(underlyingStream))]
    private void EnsureStreamIsOpen()
    {
        underlyingStream ??= file.OpenReadStream(maxAllowedSize);
    }

    protected override void Dispose(bool disposing)
    {
        if (isDisposed)
        {
            return;
        }

        underlyingStream?.Dispose();
        isDisposed = true;

        base.Dispose(disposing);
    }

    public override int Read(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();

    public override long Seek(long offset, SeekOrigin origin)
        => throw new NotSupportedException();

    public override void SetLength(long value)
        => throw new NotSupportedException();

    public override void Write(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();
}
using Microsoft.AspNetCore.Components.Forms;
using System.Diagnostics.CodeAnalysis;

namespace BlazorSample;

internal sealed class LazyBrowserFileStream : Stream
{
    private readonly IBrowserFile file;
    private readonly int maxAllowedSize;
    private Stream? underlyingStream;
    private bool isDisposed;

    public override bool CanRead => true;

    public override bool CanSeek => false;

    public override bool CanWrite => false;

    public override long Length => file.Size;

    public override long Position
    {
        get => underlyingStream?.Position ?? 0;
        set => throw new NotSupportedException();
    }

    public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
    {
        this.file = file;
        this.maxAllowedSize = maxAllowedSize;
    }

    public override void Flush()
    {
        underlyingStream?.Flush();
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, 
        CancellationToken cancellationToken)
    {
        EnsureStreamIsOpen();

        return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
    }

    public override ValueTask<int> ReadAsync(Memory<byte> buffer, 
        CancellationToken cancellationToken = default)
    {
        EnsureStreamIsOpen();
        return underlyingStream.ReadAsync(buffer, cancellationToken);
    }

    [MemberNotNull(nameof(underlyingStream))]
    private void EnsureStreamIsOpen()
    {
        underlyingStream ??= file.OpenReadStream(maxAllowedSize);
    }

    protected override void Dispose(bool disposing)
    {
        if (isDisposed)
        {
            return;
        }

        underlyingStream?.Dispose();
        isDisposed = true;

        base.Dispose(disposing);
    }

    public override int Read(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();

    public override long Seek(long offset, SeekOrigin origin)
        => throw new NotSupportedException();

    public override void SetLength(long value)
        => throw new NotSupportedException();

    public override void Write(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();
}
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorSample;

internal sealed class LazyBrowserFileStream : Stream
{
    private readonly IBrowserFile file;
    private readonly int maxAllowedSize;
    private Stream underlyingStream;
    private bool isDisposed;

    public override bool CanRead => true;

    public override bool CanSeek => false;

    public override bool CanWrite => false;

    public override long Length => file.Size;

    public override long Position
    {
        get => underlyingStream?.Position ?? 0;
        set => throw new NotSupportedException();
    }

    public LazyBrowserFileStream(IBrowserFile file, int maxAllowedSize)
    {
        this.file = file;
        this.maxAllowedSize = maxAllowedSize;
    }

    public override void Flush()
    {
        underlyingStream?.Flush();
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, 
        CancellationToken cancellationToken)
    {
        EnsureStreamIsOpen();

        return underlyingStream.ReadAsync(buffer, offset, count, cancellationToken);
    }

    public override ValueTask<int> ReadAsync(Memory<byte> buffer, 
        CancellationToken cancellationToken = default)
    {
        EnsureStreamIsOpen();
        return underlyingStream.ReadAsync(buffer, cancellationToken);
    }

    [MemberNotNull(nameof(underlyingStream))]
    private void EnsureStreamIsOpen()
    {
        underlyingStream ??= file.OpenReadStream(maxAllowedSize);
    }

    protected override void Dispose(bool disposing)
    {
        if (isDisposed)
        {
            return;
        }

        underlyingStream?.Dispose();
        isDisposed = true;

        base.Dispose(disposing);
    }

    public override int Read(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();

    public override long Seek(long offset, SeekOrigin origin)
        => throw new NotSupportedException();

    public override void SetLength(long value)
        => throw new NotSupportedException();

    public override void Write(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();
}

Följande FileUpload2 komponent:

  • Tillåter användare att ladda upp filer från klienten.
  • Visar det ej betrodda/osäkra filnamnet som tillhandahålls av klienten i användargränssnittet. Det ej betrodda/osäkra filnamnet HTML-kodas automatiskt av Razor för säker visning i användargränssnittet.

Varning

Lita inte på filnamn som tillhandahålls av klienter för:

  • Spara filen i ett filsystem eller en tjänst.
  • Visa i UIs som inte kodar filnamn automatiskt eller via utvecklarkod.

Mer information om säkerhetsöverväganden vid uppladdning av filer till en server finns i Ladda upp filer i 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.Any())
{
    <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 = [];
    private List<UploadResult> uploadResults = [];
    private int maxAllowedFiles = 3;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        shouldRender = false;
        int 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 stream = new LazyBrowserFileStream(file, maxFileSize);
                    var fileContent = new StreamContent(stream);

                    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.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<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;
        int 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 stream = new LazyBrowserFileStream(file, maxFileSize);
                    var fileContent = new StreamContent(stream);
                    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.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<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;
        int 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 stream = new LazyBrowserFileStream(file, maxFileSize);
                    var fileContent = new StreamContent(stream);
                    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.Net.Http.Headers
@using System.Text.Json
@using Microsoft.Extensions.Logging
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<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;
        int 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 stream = new LazyBrowserFileStream(file, maxFileSize);
                    var fileContent = new StreamContent(stream);
                    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; }
    }
}

Om komponenten begränsar filuppladdningar till en enskild fil i taget eller om komponenten endast använder återgivning på klientsidan (CSR, InteractiveWebAssembly) kan komponenten undvika användning av LazyBrowserFileStream och använda en Stream. Följande visar ändringarna för komponenten FileUpload2:

- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));

Ta bort klassen LazyBrowserFileStream (LazyBrowserFileStream.cs), eftersom den inte används.

Om komponenten begränsar filuppladdningar till en enskild fil i taget kan komponenten undvika användning av LazyBrowserFileStream och använda en Stream. Följande visar ändringarna för komponenten FileUpload2:

- var stream = new LazyBrowserFileStream(file, maxFileSize);
- var fileContent = new StreamContent(stream);
+ var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));

Ta bort klassen LazyBrowserFileStream (LazyBrowserFileStream.cs), eftersom den inte används.

Följande kontrollant i webb-API-projektet sparar uppladdade filer från klienten.

Viktig

Kontrollanten i det här avsnittet är avsedd att användas i ett separat webb-API-projekt från Blazor-appen. Webb-API:et bör minimera XSRF-/CSRF-attacker (Cross-Site Request Forgery) om filuppladdningsanvändare autentiseras.

Not

Att binda formulärvärden med attributet [FromForm] stöds inte internt för minimala API:er i ASP.NET Core i .NET 6. Därför kan inte följande Filesave kontrollantexempel konverteras till att använda minimala API:er. Stöd för bindning från formulärvärden med minimala API:er finns i ASP.NET Core i .NET 7 eller senare.

Om du vill använda följande kod skapar du en Development/unsafe_uploads mapp i roten för webb-API-projektet för appen som körs i den Development miljön.

Eftersom exemplet använder appens miljö som en del av sökvägen där filer sparas, krävs ytterligare mappar om andra miljöer används i testning och produktion. Skapa till exempel en Staging/unsafe_uploads mapp för den Staging miljön. Skapa en Production/unsafe_uploads mapp för den Production miljön.

Varning

I exemplet sparas filer utan att deras innehåll genomsöks, och riktlinjerna i den här artikeln tar inte hänsyn till ytterligare säkerhetsmetoder för uppladdade filer. På mellanlagrings- och produktionssystem inaktiverar du körningsbehörigheten för uppladdningsmappen och genomsöker filer med ett API för skanner mot virus/skadlig kod omedelbart efter uppladdningen. Mer information finns i Ladda upp filer i ASP.NET Core.

Controllers/FilesaveController.cs:

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

[ApiController]
[Route("[controller]")]
public class FilesaveController(
    IHostEnvironment env, ILogger<FilesaveController> logger) 
    : ControllerBase
{
    [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 = [];

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

I föregående kod anropas GetRandomFileName för att generera ett säkert filnamn. Lita aldrig på filnamnet som tillhandahålls av webbläsaren, eftersom en cyberattack kan välja ett befintligt filnamn som skriver över en befintlig fil eller skicka en sökväg som försöker skriva utanför appen.

Serverappen måste registrera kontrolltjänster och kartlägga slutpunkter för kontroller. Mer information finns i Routning till kontrollantåtgärder i ASP.NET Core.

Ladda upp filer till en server med återgivning på klientsidan (CSR)

Det här avsnittet gäller för renderade komponenter på klientsidan (CSR) i Blazor Web Appeller Blazor WebAssembly appar.

I följande exempel visas hur du laddar upp filer till en webb-API-styrenhet för serverdelen i en separat app, eventuellt på en separat server, från en komponent i en Blazor Web App som använder CSR eller en komponent i en Blazor WebAssembly app.

I exemplet antas begäran om strömning för en Chromium-baserad webbläsare (till exempel Google Chrome eller Microsoft Edge) med HTTP/2-protokoll och HTTPS. Om det inte går att använda begärandeströmning försämras Blazor på ett smidigt sätt till Fetch API utan begärandeströmning. Mer information finns i avsnittet läs- och uppladdningsgränser för filstorlek.

Följande UploadResult-klass underhåller resultatet av en uppladdad fil. När en fil inte kan laddas upp på servern returneras en felkod i ErrorCode för visning till användaren. Ett säkert filnamn genereras på servern för varje fil och returneras till klienten i StoredFileName för visning. Filer nyckelas mellan klienten och servern med hjälp av det osäkra/ej betrodda filnamnet i 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; }
}

Not

Föregående UploadResult-klass kan delas mellan klient- och serverbaserade projekt. När klient- och serverprojekt delar klassen lägger du till en import till varje projekts _Imports.razor fil för det delade projektet. Till exempel:

@using BlazorSample.Shared

Följande FileUpload2 komponent:

  • Tillåter användare att ladda upp filer från klienten.
  • Visar det ej betrodda/osäkra filnamnet som tillhandahålls av klienten i användargränssnittet. Det ej betrodda/osäkra filnamnet HTML-kodas automatiskt av Razor för säker visning i användargränssnittet.

En säkerhetsmetod för produktionsappar är att undvika att skicka felmeddelanden till klienter som kan avslöja känslig information om en app, server eller ett nätverk. Att tillhandahålla detaljerade felmeddelanden kan hjälpa en illvillig användare att utforma attacker på en app, server eller ett nätverk. Exempelkoden i det här avsnittet skickar bara tillbaka ett felkodnummer (int) för visning av komponentklientsidan om ett fel på serversidan inträffar. Om en användare behöver hjälp med en filuppladdning tillhandahåller de felkoden för att stödja personal för lösning av supportbegäran utan att någonsin känna till den exakta orsaken till felet.

Varning

Lita inte på filnamn som tillhandahålls av klienter för:

  • Spara filen i ett filsystem eller en tjänst.
  • Visa i UIs som inte kodar filnamn automatiskt eller via utvecklarkod.

Mer information om säkerhetsöverväganden vid uppladdning av filer till en server finns i Ladda upp filer i ASP.NET Core.

I Blazor Web App-serverprojektet lägger du till IHttpClientFactory och relaterade tjänster i projektets Program-fil:

builder.Services.AddHttpClient();

De HttpClient tjänsterna måste läggas till i serverprojektet eftersom komponenten på klientsidan är förinstallerad på servern. Om du inaktiverar prerendering för följande komponentbehöver du inte tillhandahålla HttpClient tjänster i serverprojektet och behöver inte lägga till ovannämnda rad i serverprojektet.

Mer information om hur du lägger till HttpClient-tjänster i en ASP.NET Core-app finns i Göra HTTP-begäranden med IHttpClientFactory i ASP.NET Core.

Klientprojektet (.Client) för en Blazor Web App måste också registrera en HttpClient för HTTP POST-begäranden till en backend webb-API-kontroller. Bekräfta eller lägg till följande i klientprojektets Program-fil:

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

I föregående exempel anges basadressen med builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress), som hämtar basadressen för appen och vanligtvis härleds från <base> taggens href värde på värdsidan. Om du anropar ett externt webb-API anger du URI:n till webb-API:ets basadress.

En fristående Blazor WebAssembly-app som laddar upp filer till ett separat serverwebb-API använder antingen en med namnet HttpClient eller anger standardregistreringen för HttpClient-tjänsten så att den pekar på webb-API:ets slutpunkt. I följande exempel där webb-API:et finns lokalt på port 5001 är basadressen https://localhost:5001:

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri("https://localhost:5001") });

I en Blazor Web Applägger du till Microsoft.AspNetCore.Components.WebAssembly.Http-namnområdet i komponentens direktiv:

@using Microsoft.AspNetCore.Components.WebAssembly.Http

FileUpload2.razor:

@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@using System.Net
@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 request = new HttpRequestMessage(HttpMethod.Post, "/Filesave");
            request.SetBrowserRequestStreamingEnabled(true);
            request.Content = content;

            var response = await Http.SendAsync(request);

            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
@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 = [];
    private List<UploadResult> uploadResults = [];
    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; }
    }
}

Följande styrenhet i projektet på serversidan sparar uppladdade filer från klienten.

Notera

Att binda formulärvärden med attributet [FromForm] stöds inte internt för minimala API:er i ASP.NET Core i .NET 6. Därför kan inte följande Filesave kontrollantexempel konverteras till att använda minimala API:er. Stöd för bindning från formulärvärden med minimala API:er finns i ASP.NET Core i .NET 7 eller senare.

Om du vill använda följande kod skapar du en Development/unsafe_uploads mapp i roten för serverprojektet för appen som körs i Development miljön.

Eftersom exemplet använder appens miljö som en del av sökvägen där filer sparas, krävs ytterligare mappar om andra miljöer används i testning och produktion. Skapa till exempel en Staging/unsafe_uploads mapp för den Staging miljön. Skapa en Production/unsafe_uploads mapp för den Production miljön.

Varning

I exemplet sparas filer utan att deras innehåll genomsöks, och riktlinjerna i den här artikeln tar inte hänsyn till ytterligare säkerhetsmetoder för uppladdade filer. På mellanlagrings- och produktionssystem inaktiverar du körningsbehörigheten för uppladdningsmappen och genomsöker filer med ett API för skanner mot virus/skadlig kod omedelbart efter uppladdningen. Mer information finns i Ladda upp filer i ASP.NET Core.

I följande exempel för en värdbaserad Blazor WebAssembly app eller där ett delat projekt används för att ange UploadResult-klassen lägger du till det delade projektets namnområde:

using BlazorSample.Shared;

Vi rekommenderar att du använder ett namnområde för följande kontrollant (till exempel: namespace BlazorSample.Controllers).

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;

[ApiController]
[Route("[controller]")]
public class FilesaveController(
    IHostEnvironment env, ILogger<FilesaveController> logger) 
    : ControllerBase
{
    [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 = [];

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

I föregående kod anropas GetRandomFileName för att generera ett säkert filnamn. Lita aldrig på filnamnet som tillhandahålls av webbläsaren, eftersom en cyberattack kan välja ett befintligt filnamn som skriver över en befintlig fil eller skicka en sökväg som försöker skriva utanför appen.

Serverappen måste registrera kontrollertjänster och kartlägga styrenhetens slutpunkter. Mer information finns i Routning till kontrollantåtgärder i ASP.NET Core. Vi rekommenderar att du lägger till kontrollanttjänster med AddControllersWithViews för att automatiskt minimera XSRF-/CSRF-attacker (Cross-Site Request Forgery) för autentiserade användare. Om du bara använder AddControllersaktiveras inte förfalskningsskydd automatiskt. Mer information finns i Routning till kontrollantåtgärder i ASP.NET Core.

CORS-konfiguration (Cross-Origin Requests) på servern krävs för begärandeströmning när servern finns på en annan domän, och en förberedande begäran görs alltid av klienten. I tjänstkonfigurationen för serverns Program -fil (serverprojektet för en Blazor Web App eller serverdelsserverns webb-API för en Blazor WebAssembly app) är följande standardprincip för CORS lämplig för testning med exemplen i den här artikeln. Klienten gör den lokala begäran från port 5003. Ändra portnumret så att det matchar klientappporten som du använder:

Konfigurera CORS (Cross-Origin Requests) på servern. I tjänstkonfigurationen för serverns Program -fil (serverprojektet för en Blazor Web App eller serverdelsserverns webb-API för en Blazor WebAssembly app) är följande standardprincip för CORS lämplig för testning med exemplen i den här artikeln. Klienten gör den lokala begäran från port 5003. Ändra portnumret så att det matchar klientappporten som du använder:

Konfigurera CORS (Cross-Origin Requests) på servern. I tjänstkonfigurationen av serverserverwebb-API:ets Program-fil är följande standardprincip för CORS lämplig för testning med exemplen i den här artikeln. Klienten gör den lokala begäran från port 5003. Ändra portnumret så att det matchar klientappporten som du använder:

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("https://localhost:5003")
                  .AllowAnyMethod()
                  .AllowAnyHeader();
        });
});

När du har anropat UseHttpsRedirection i filen Program anropar du UseCors för att lägga till CORS-mellanprogram:

app.UseCors();

Mer information finns i Aktivera CORS (Cross-Origin Requests) i ASP.NET Core.

Konfigurera serverns maximala storlek för begärandetext och längd på flera delar om gränserna begränsar uppladdningsstorleken.

För Kestrel-servern anger du MaxRequestBodySize (standard: 30 000 000 byte) och FormOptions.MultipartBodyLengthLimit (standard: 134 217 728 byte). Ange variabeln maxFileSize i komponenten och styrenheten till samma värde.

I följande Program fil Kestrel konfiguration (serverprojektet för en Blazor Web App eller serverdelsserverns webb-API för en Blazor WebAssembly app) är platshållaren för {LIMIT} gränsen i byte:

using Microsoft.AspNetCore.Http.Features;

...

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.MaxRequestBodySize = {LIMIT};
});

builder.Services.Configure<FormOptions>(options =>
{
    options.MultipartBodyLengthLimit = {LIMIT};
});

Avbryt en filuppladdning

En filuppladdningskomponent kan identifiera när en användare har avbrutit en uppladdning med hjälp av en CancellationToken vid anrop till IBrowserFile.OpenReadStream eller StreamReader.ReadAsync.

Skapa en CancellationTokenSource för komponenten InputFile. I början av metoden OnInputFileChange kontrollerar du om en tidigare uppladdning pågår.

Om en filuppladdning pågår:

Ladda upp filer på serversidan med framsteg

I följande exempel visas hur du laddar upp filer i en app på serversidan med uppladdningsförloppet som visas för användaren.

Så här använder du följande exempel i en testapp:

  • Skapa en mapp för att spara uppladdade filer för den Development miljön: Development/unsafe_uploads.
  • Konfigurera den maximala filstorleken (maxFileSize, 15 KB i följande exempel) och maximalt antal tillåtna filer (maxAllowedFiles, 3 i följande exempel).
  • Ange bufferten till ett annat värde (10 KB i följande exempel), om så önskas, för ökad kornighet i förloppsrapportering. Vi rekommenderar inte att du använder en buffert som är större än 30 kB på grund av prestanda och säkerhetsproblem.

Varning

I exemplet sparas filer utan att deras innehåll genomsöks, och riktlinjerna i den här artikeln tar inte hänsyn till ytterligare säkerhetsmetoder för uppladdade filer. På mellanlagrings- och produktionssystem inaktiverar du körningsbehörigheten för uppladdningsmappen och genomsöker filer med ett API för skanner mot virus/skadlig kod omedelbart efter uppladdningen. Mer information finns i Ladda upp filer i 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 = [];
    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;
    }
}

Mer information finns i följande API-resurser:

  • FileStream: Tillhandahåller en Stream för en fil som stöder både synkrona och asynkrona läs- och skrivåtgärder.
  • FileStream.ReadAsync: Den föregående FileUpload3-komponenten läser strömmen asynkront med ReadAsync. Läsning av en ström synkront med Read stöds inte i Razor-komponenter.

Filströmmar

Med serverinteraktivitet strömmas fildata över den SignalR anslutningen till .NET-kod på servern när filen läss.

RemoteBrowserFileStreamOptions gör det möjligt att konfigurera filuppladdningsegenskaper.

För en WebAssembly-renderad komponent strömmas fildata direkt till .NET-koden i webbläsaren.

Ladda upp bildförhandsgranskning

Börja med att lägga till en InputFile komponent med en komponentreferens och en OnChange-hanterare för en förhandsgranskning av uppladdning av bilder:

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

Lägg till ett bildelement med en -elementreferens, som fungerar som platshållare för förhandsgranskningen av bilden:

<img @ref="previewImageElem" />

Lägg till de associerade referenserna:

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

I JavaScript lägger du till en funktion som anropas med ett HTML-input- och img-element och som utför följande:

  • Extraherar den markerade filen.
  • Skapar en objekt-URL med createObjectURL.
  • Anger att en händelselyssnare ska återkalla objekt-URL:en med revokeObjectURL när avbildningen har lästs in, så att minnet inte läcker ut.
  • Anger img-elementets källa för att visa bilden.
window.previewImage = (inputElem, imgElem) => {
  const url = URL.createObjectURL(inputElem.files[0]);
  imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
  imgElem.src = url;
}

Slutligen använder du en inmatad IJSRuntime för att lägga till OnChange-hanteraren som anropar JavaScript-funktionen:

@inject IJSRuntime JS

...

@code {
    ...

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

Föregående exempel är för att ladda upp en enda bild. Metoden kan utökas för att stödja multiple bilder.

Följande FileUpload4 komponent visar det fullständiga exemplet.

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

Spara små filer direkt i en databas med EF Core

Många ASP.NET Core-appar använder Entity Framework Core (EF Core) för att hantera databasåtgärder. Att spara miniatyrer och avatarer direkt till databasen är ett vanligt krav. Det här avsnittet visar en allmän metod som kan förbättras ytterligare för produktionsappar.

Följande mönster:

  • Självstudieappen baseras på filmdatabasenBlazor .
  • Kan utökas med ytterligare kod för filstorlek och innehållstyp valideringsfeedback.
  • Ådrar sig en prestandastraff och DoS risk. Väg risken noggrant när du läser en fil i minnet och överväg alternativa metoder, särskilt för större filer. Alternativa metoder är att spara filer direkt till disken eller en tjänst från tredje part för kontroller av antivirus-/program mot skadlig kod, ytterligare bearbetning och servering till klienter.

För att följande exempel ska fungera i en Blazor Web App (ASP.NET Core 8.0 eller senare) måste komponenten använda ett interaktivt återgivningsläge (till exempel @rendermode InteractiveServer) för att anropa HandleSelectedThumbnail på en InputFile komponentfiländring (OnChange parameter/händelse). Blazor Server appkomponenter är alltid interaktiva och kräver inget återgivningsläge.

I följande exempel sparas en liten miniatyrbild (<= 100 KB) i en IBrowserFile till en databas med EF Core. Om en fil inte har valts av användaren för den InputFile komponenten sparas en standardminiatyr i databasen.

Standardminiatyren (default-thumbnail.jpg) finns i projektroten med inställningen Kopiera till utdatakatalog inställningen Kopiera om nyare:

standardbild för allmän miniatyrbild

Modell Movie (Movie.cs) har en egenskap (Thumbnail) att innehålla miniatyrbilddata:

[Column(TypeName = "varbinary(MAX)")]
public byte[]? Thumbnail { get; set; }

Bilddata lagras som byte i databasen som varbinary(MAX). Appen base-64 kodar byte för visning eftersom base-64-kodade data är ungefär en tredjedel större än bildens råa byte, vilket innebär att base-64-bilddata kräver ytterligare databaslagring och minskar prestandan för databasläsnings-/skrivåtgärder.

Komponenter som visar miniatyrbilden skickar bilddata till img-taggs src-attribut som JPEG-data, kodade i base-64:

<img src="data:image/jpeg;base64,@Convert.ToBase64String(movie.Thumbnail)" 
    alt="User thumbnail" />

I följande Create komponent bearbetas en bilduppladdning. Du kan förbättra exemplet ytterligare med anpassad validering för filtyp och storlek med hjälp av metoderna i ASP.NET Core Blazor formulärverifiering. Om du vill se den fullständiga Create-komponenten utan att inkludera koden för uppladdning av miniatyrbilder i följande exempel, kan du titta på BlazorWebAppMovies-exempelappen i Blazor-exempel på GitHub-lagringsplatsen.

Components/Pages/MoviePages/Create.razor:

@page "/movies/create"
@rendermode InteractiveServer
@using Microsoft.EntityFrameworkCore
@using BlazorWebAppMovies.Models
@inject IDbContextFactory<BlazorWebAppMovies.Data.BlazorWebAppMoviesContext> DbFactory
@inject NavigationManager NavigationManager

...

<div class="row">
    <div class="col-md-4">
        <EditForm method="post" Model="Movie" OnValidSubmit="AddMovie" 
            FormName="create" Enhance>
            <DataAnnotationsValidator />
            <ValidationSummary class="text-danger" role="alert"/>

            ...

            <div class="mb-3">
                <label for="thumbnail" class="form-label">Thumbnail:</label>
                <InputFile id="thumbnail" OnChange="HandleSelectedThumbnail" 
                    class="form-control" />
            </div>
            <button type="submit" class="btn btn-primary">Create</button>
        </EditForm>
    </div>
</div>

...

@code {
    private const long maxFileSize = 102400;
    private IBrowserFile? browserFile;

    [SupplyParameterFromForm]
    private Movie Movie { get; set; } = new();

    private void HandleSelectedThumbnail(InputFileChangeEventArgs e)
    {
        browserFile = e.File;
    }

    private async Task AddMovie()
    {
        using var context = DbFactory.CreateDbContext();

        if (browserFile?.Size > 0 && browserFile?.Size <= maxFileSize)
        {
            using var memoryStream = new MemoryStream();
            await browserFile.OpenReadStream(maxFileSize).CopyToAsync(memoryStream);

            Movie.Thumbnail = memoryStream.ToArray();
        }
        else
        {
            Movie.Thumbnail = File.ReadAllBytes(
                $"{AppDomain.CurrentDomain.BaseDirectory}default_thumbnail.jpg");
        }

        context.Movie.Add(Movie);
        await context.SaveChangesAsync();
        NavigationManager.NavigateTo("/movies");
    }
}

Samma metod skulle användas i komponenten Edit med ett interaktivt renderingsläge om användarna tilläts redigera en films miniatyrbild.

Ladda upp filer till en extern tjänst

I stället för att en app hanterar byte för filuppladdning och appens server tar emot uppladdade filer kan klienter direkt ladda upp filer till en extern tjänst. Appen kan på ett säkert sätt bearbeta filerna från den externa tjänsten på begäran. Den här metoden skyddar appen och dess server mot skadliga attacker och potentiella prestandaproblem.

Överväg en metod som använder Azure Files, Azure Blob Storageeller en tjänst från tredje part med följande potentiella fördelar:

Mer information om Azure Blob Storage och Azure Files finns i dokumentationen Azure Storage.

Storleksgräns för SignalR meddelande på serversidan

Filuppladdningar kan misslyckas redan innan de startas, när Blazor hämtar data om de filer som överskrider den maximala SignalR meddelandestorleken.

SignalR definierar en storleksgräns för meddelanden som gäller för varje meddelande Blazor tar emot, och den InputFile komponenten strömmar filer till servern i meddelanden som respekterar den konfigurerade gränsen. Det första meddelandet, som anger vilken uppsättning filer som ska laddas upp, skickas dock som ett unikt enda meddelande. Storleken på det första meddelandet kan överskrida storleksgränsen för SignalR meddelande. Problemet är inte relaterat till storleken på filerna, det är relaterat till antalet filer.

Det loggade felet liknar följande:

Fel: Anslutningen kopplades bort på grund av ett fel: "Fel: Servern returnerade ett fel vid avslutning: Anslutningen avslutades med ett fel.". e.log @ blazor.server.js:1

När du laddar upp filer är det ovanligt att du når meddelandestorleksgränsen för det första meddelandet. Om gränsen nås kan appen konfigurera HubOptions.MaximumReceiveMessageSize med ett större värde.

Mer information om SignalR konfiguration och hur du ställer in MaximumReceiveMessageSizefinns i ASP.NET Core BlazorSignalR vägledning.

Maximalt antal parallella anropningar per klient-hub-inställning

Blazor förlitar sig på att MaximumParallelInvocationsPerClient är inställt på 1, vilket är standardvärdet.

Att öka värdet leder till en hög sannolikhet att CopyTo operationer utlöser System.InvalidOperationException: 'Reading is not allowed after reader was completed.'. Mer information finns i MaximumParallelInvocationsPerClient > 1 breaks file upload in Blazor Server mode (dotnet/aspnetcore #53951).

Felsöka

Raden som anropar IBrowserFile.OpenReadStream genererar en System.TimeoutException:

System.TimeoutException: Did not receive any data in the allotted time.

Möjliga orsaker:

Ytterligare resurser