Aracılığıyla paylaş


ASP.NET Core dosya yüklemeleri

Not or Uyarı

Bu, bu makalenin en son sürümü değildir. Geçerli sürüm için bu makalenin .NET 9 sürümüne bakın.

Uyarı

ASP.NET Core'un bu sürümü artık desteklenmiyor. Daha fazla bilgi için bkz . .NET ve .NET Core Destek İlkesi. Geçerli sürüm için bu makalenin .NET 9 sürümüne bakın.

Önemli

Bu bilgiler, ticari olarak piyasaya sürülmeden önce önemli ölçüde değiştirilebilen bir yayın öncesi ürünle ilgilidir. Burada verilen bilgilerle ilgili olarak Microsoft açık veya zımni hiçbir garanti vermez.

Geçerli sürüm için bu makalenin .NET 9 sürümüne bakın.

Bu makale, Blazor ile InputFile bileşeni kullanarak dosya yükleme işlemini açıklar.

Dosya yüklemeleri

Uyarı

Kullanıcıların dosyaları karşıya yüklemesine izin verilirken her zaman en iyi güvenlik yöntemlerini izleyin. Daha fazla bilgi için bkz ASP.NET Core'da dosya yükleme.

InputFile Tarayıcı dosya verilerini .NET koduna okumak için bileşenini kullanın. InputFile bileşeni, tek dosya yüklemeleri için bir HTML <input> öğesini file türünde işler. multiple Kullanıcının aynı anda birden çok dosya yüklemesine izin vermek için özniteliğini ekleyin.

Bir InputFile bileşeni veya temel HTML <input type="file"> kullanıldığında, dosya seçimi toplayıcı olmadığından, mevcut bir dosya seçimine dosya ekleyemezsiniz. Bileşen her zaman kullanıcının ilk dosya seçiminin yerini alır, bu nedenle önceki seçimlerden dosya başvuruları kullanılamaz.

Aşağıdaki InputFile bileşeni, OnChange (change) olayı gerçekleştiğinde LoadFiles yöntemini yürütür. , InputFileChangeEventArgs seçili dosya listesine ve her dosyayla ilgili ayrıntılara erişim sağlar:

<InputFile OnChange="LoadFiles" multiple />

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

İşlenen HTML:

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

Not

Yukarıdaki örnekte, <input> öğesinin _bl_2 özniteliği, Blazor'nin iç işlemesi için kullanılır.

Bir dosyadaki verileri okumak için, dosyanın baytlarını temsil eden Stream içeren, kullanıcının seçtiği dosyada IBrowserFile.OpenReadStream fonksiyonunu çağırın ve döndürülen akıştan okuyun. Daha fazla bilgi için Dosya akışları bölümüne bakın.

OpenReadStream, Stream için bayt cinsinden en büyük boyutu zorlar. Bir dosya veya 500 KB'tan büyük birden çok dosyanın okunması özel durumla sonuçlandı. Bu sınır, geliştiricilerin yanlışlıkla büyük dosyaları belleğe okumasını engeller. maxAllowedSize parametresiOpenReadStream, gerekirse daha büyük bir boyut belirtmek için kullanılabilir.

Küçük bir dosyayı işlemenin dışında, gelen dosya akışını bir kerede doğrudan belleğe okumaktan kaçının. Örneğin, dosyanın tüm baytlarını bir MemoryStream içine kopyalamayın veya akışın tamamını aynı anda bayt dizisine okumayın. Bu yaklaşımlar, özellikle sunucu tarafı bileşenler için uygulama performansının düşmesine ve olası Hizmet Reddi (DoS) riskine neden olabilir. Bunun yerine, aşağıdaki yaklaşımlardan birini benimsemeyi göz önünde bulundurun:

  • Akışı belleğe okumadan doğrudan disk üzerindeki bir dosyaya kopyalayın. Blazor Sunucuda kod yürüten uygulamaların istemcinin dosya sistemine doğrudan erişemeyeceğini unutmayın.
  • dosyaları istemciden doğrudan bir dış hizmete yükleyin. Daha fazla bilgi için Dosyaları dış hizmete yükleme bölümüne bakın.

Aşağıdaki örneklerde, browserFile karşıya yüklenen bir dosyayı temsil etmek için IBrowserFile'i kullanır. için IBrowserFile çalışan uygulamalar, bu makalenin devamında dosya yükleme bileşenlerinde gösterilir.

OpenReadStreamçağırırken, maxAllowedSize parametresinde, almayı beklediğiniz dosya boyutlarının sınırında izin verilen en büyük dosya boyutunu geçirmenizi öneririz. Varsayılan değer 500 KB'tır. Bu makaledeki örneklerde izin verilen en yüksek dosya boyutu değişkeni veya maxFileSize adlı sabit kullanılır, ancak genellikle belirli bir değerin ayarlanması gösterilmez.

Destekleniyor: Aşağıdaki yaklaşım önerilir çünkü dosyaların Stream doğrudan tüketiciye sunulması ve sağlanan yolda dosyayı oluşturan bir FileStream kullanılması önerilmektedir.

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

Desteklenen: Aşağıdaki yaklaşım önerilirMicrosoft Azure Blob Depolama için çünkü dosyanın Stream doğrudan UploadBlobAsync sağlanır:

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

✔️ Yalnızca küçük dosyalar için önerilir: Aşağıdaki yaklaşım, yalnızca küçük dosyalar için önerilir çünkü dosyanın Stream içeriği bellekte MemoryStream (memoryStream) okunarak bir performans cezasına ve DoS riskine neden olur. Entity Framework Core (EF Core) kullanarak bir küçük resim görüntüsünü veritabanına IBrowserFile kaydetme tekniğini gösteren bir örnek için, bu makalenin devamında Küçük dosyaları EF Core içeren bir veritabanına doğrudan kaydetme bölümüne bakın.

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

Önerilmez: Dosyanın içeriği bellekte Stream (String) okunduğu için aşağıdaki yaklaşım reader:

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

Önerilmez: Aşağıdaki yaklaşım Microsoft Azure Blob Depolama için önerilmez çünkü dosyanın Stream içeriği çağrılmadan MemoryStreamönce bir memoryStream bellekte (UploadBlobAsync) kopyalanır:

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

Görüntü dosyası alan bir bileşen, dosyada BrowserFileExtensions.RequestImageFileAsync kolaylık sağlayan yöntemi çağırarak görüntü uygulamaya akışla aktarılmadan önce, tarayıcının JavaScript çalışma zamanı içinde görüntü verilerini yeniden boyutlandırabilir. RequestImageFileAsync için kullanım durumları, Blazor WebAssembly uygulamaları için en uygun olanıdır.

Autofac Inversion of Control (IoC) kapsayıcı kullanıcıları

Yerleşik ASP.NET Core bağımlılık ekleme kapsayıcısı yerine Autofac Inversion of Control (IoC) kapsayıcısını kullanıyorsanız, sunucu tarafı devre işleyici hub seçeneklerindeDisableImplicitFromServicesParameters seçeneğini true olarak ayarlayın. Daha fazla bilgi için bkz . FileUpload: Ayrılan sürede veri alınmadı (dotnet/aspnetcore #38842).

Dosya boyutu okuma ve karşıya yükleme sınırları

HTTP/2 protokolü, HTTPS ve CORSkullanan Chromium tabanlı tarayıcılar (örneğin, Google Chrome ve Microsoft Edge) için istemci tarafı Blazor, istek akışıile büyük dosyaların karşıya yüklenmesine izin vermek Streams API'sinin kullanılmasını destekler.

Chromium tarayıcısı, HTTP/2 protokolü veya HTTPS olmadan, istemci tarafı Blazor JavaScript'ten C# 'ye verileri sıralarken dosyanın baytlarını tek bir JavaScript dizi arabelleğine okur ve bu da 2 GB veya cihazın kullanılabilir belleğiyle sınırlıdır. Büyük dosya yüklemeleri, InputFile bileşeni kullanılarak istemci tarafında yapılan karşıya yüklemeler için başarısız olabilir.

İstemci tarafı Blazor, JavaScript'ten C#'ye verileri hazırlarken dosyanın baytlarını tek bir JavaScript dizi arabelleğine okur, bu da 2 GB veya cihazın kullanılabilir belleğiyle sınırlıdır. Büyük dosya yüklemeleri, istemci tarafı yüklemeleri için InputFile bileşeni kullanılarak başarısız olabilir. ASP.NET Core 9.0 veya sonraki sürümleriyle istek akışını benimsemenizi öneririz.

Güvenlikle ilgili dikkat edilmesi gerekenler

Dosya boyutu sınırlarında IBrowserFile.Size'dan kaçının

Dosya boyutuna bir sınır getirmek için IBrowserFile.Size kullanmaktan kaçının. Güvenli olmayan istemci tarafından sağlanan dosya boyutunu kullanmak yerine, en büyük dosya boyutunu açıkça belirtin. Aşağıdaki örnek, maxFileSizeatanan en büyük dosya boyutunu kullanır:

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

Dosya adı güvenliği

Bir dosyayı fiziksel depolama alanına kaydetmek için hiçbir zaman istemci tarafından sağlanan bir dosya adı kullanmayın. Geçici depolama için tam yol (dosya adı dahil) oluşturmak üzere Path.GetRandomFileName() veya Path.GetTempFileName() kullanarak dosya için güvenli bir dosya adı oluşturun.

Razor HTML, görüntü için özellik değerlerini otomatik olarak kodlar. Aşağıdaki kodun kullanımı güvenlidir:

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

Razordışında, kullanıcının isteğinden dosya adlarını güvenli bir şekilde kodlamak için her zaman HtmlEncode kullanın.

Birçok uygulama dosyanın mevcut olup olmadığını denetlemelidir; aksi takdirde, dosyanın üzerine aynı ada sahip bir dosya yazılır. Uygulamanızın belirtimlerini karşılamak için ek mantık sağlayın.

Örnekler

Aşağıdaki örnekler, bir bileşende birden çok dosya yüklenmesini göstermektedir. InputFileChangeEventArgs.GetMultipleFiles birden çok dosya okumaya izin verir. Kötü amaçlı bir kullanıcının uygulamanın beklediğinden daha fazla sayıda dosya yüklemesini önlemek için en fazla dosya sayısını belirtin. InputFileChangeEventArgs.File , dosya yükleme işlemi birden çok dosyayı desteklemiyorsa ilk ve tek dosyanın okunmasına izin verir.

InputFileChangeEventArgs genellikle, uygulamanın Microsoft.AspNetCore.Components.Forms dosyasında yer alan _Imports.razor ad alanlarından birinde bulunur. _Imports.razor dosyasında ad alanı mevcut olduğunda, uygulamanın bileşenlerine API üyelerine erişim mümkün kılar.

Dosyadaki _Imports.razor ad alanları C# dosyalarına (.cs ) uygulanmaz. C# dosyaları, sınıf dosyasının en üstünde açık using bir yönerge gerektirir:

using Microsoft.AspNetCore.Components.Forms;

Dosya karşıya yükleme bileşenlerini test etme için PowerShell ile her boyutta test dosyası oluşturabilirsiniz:

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

Yukarıdaki komutta:

  • Yer {SIZE} tutucu, dosyanın bayt cinsinden boyutudur (örneğin, 2 MB'lik dosyalar için 2097152).
  • Yer tutucu {PATH}, dosya uzantısına sahip yol ve dosyadır (örneğin, D:/test_files/testfile2MB.txt).

Sunucu tarafı dosya yükleme örneği

Aşağıdaki kodu kullanmak için, ortamda çalışan uygulamanın kökünde Development/unsafe_uploads bir Development klasör oluşturun.

Örnek, dosyaların kaydedildiği yolun bir parçası olarak uygulamanın ortamını kullandığından, test ve üretimde diğer ortamlar kullanılıyorsa ek klasörler gerekir. Örneğin, Staging ortamına bir Staging/unsafe_uploads klasörü oluşturun. Production/unsafe_uploads için Production ortam klasörü oluşturun.

Uyarı

Örnek, dosyaları içeriklerini taramadan kaydeder ve bu makaledeki yönergeler karşıya yüklenen dosyalar için ek güvenlik en iyi yöntemlerini hesaba katmıyor. Hazırlama ve üretim sistemlerinde, yükleme klasöründe yürütme iznini devre dışı bırakın ve yükleme işlemi tamamlandığında dosyaları virüsten koruma/kötü amaçlı yazılımdan koruma tarayıcı API'siyle tarayın. Daha fazla bilgi için bkz ASP.NET Core'da dosyaları karşıya yükleme.

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

İstemci tarafı dosya yükleme örneği

Aşağıdaki örnek dosya baytlarını işler ve uygulama dışındaki bir hedefe dosya göndermez. Sunucuya veya hizmete dosya gönderen bir bileşen örneği Razor için aşağıdaki bölümlere bakın:

Bileşen, Interactive WebAssembly işleme modunun (InteractiveWebAssembly) bir üst bileşenden devralındığını veya uygulamaya genel olarak uygulandığını varsayar.

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

IBrowserFiletarayıcı tarafından sunulan meta verileri özellik olarak döndürür. Ön doğrulama için bu meta verileri kullanın.

Önceki özelliklerin değerlerine, özellikle de Name kullanıcı arabiriminde görüntülenme özelliğine asla güvenmeyin. Kullanıcı tarafından sağlanan tüm verileri uygulama, sunucu ve ağ için önemli bir güvenlik riski olarak değerlendirin. Daha fazla bilgi için bkz ASP.NET Core'da dosya karşıya yükleme.

Sunucu tarafı işleme ile dosyaları sunucuya yükleme

Bu bölüm, s veya Blazor Web App uygulamalardaki Blazor ServerEtkileşimli Sunucu bileşenleri için geçerlidir.

Aşağıdaki örnekte, bir sunucu tarafı uygulamasından arka uç web API'sinin denetleyicisine ayrı bir uygulamada(muhtemelen ayrı bir sunucuda) dosya yükleme işlemi gösterilmektedir.

Sunucu tarafı uygulamasının Program dosyasında, uygulamanın HttpClient örnek oluşturmasına izin veren IHttpClientFactory ve ilgili hizmetleri ekleyin.

builder.Services.AddHttpClient();

Daha fazla bilgi için, bkz. ASP.NET Core'da IHttpClientFactory kullanarak HTTP isteği yapma.

Bu bölümdeki örnekler için:

  • Web API'si URL'de çalışır: https://localhost:5001
  • Sunucu tarafı uygulaması URL'de çalışır: https://localhost:5003

Test için, önceki URL'ler projelerin Properties/launchSettings.json dosyalarında yapılandırılır.

Aşağıdaki UploadResult sınıf, karşıya yüklenen bir dosyanın sonucunu korur. Bir dosya sunucuya yüklenemediğinde, kullanıcıya görüntülenmesi için içinde ErrorCode bir hata kodu döndürülür. Her dosya için sunucuda güvenli bir dosya adı oluşturulur ve görüntülenmek üzere istemciye StoredFileName döndürülür. Dosyalar, içinde güvenli olmayan/güvenilmeyen dosya adı FileNamekullanılarak istemci ile sunucu arasında anahtarlanır.

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

Üretim uygulamaları için en iyi güvenlik uygulaması, istemcilere uygulama, sunucu veya ağ hakkındaki hassas bilgileri ortaya çıkarabilecek hata iletileri göndermekten kaçınmaktır. Ayrıntılı hata iletileri sağlamak, kötü amaçlı bir kullanıcının uygulama, sunucu veya ağ üzerindeki saldırıları geliştirmelerine yardımcı olabilir. Bu bölümdeki örnek kod, yalnızca sunucu tarafı hatası oluşursa bileşen istemci tarafı tarafından görüntülenmesi için bir hata kodu numarası (int) gönderir. Bir kullanıcı dosya yükleme konusunda yardıma ihtiyaç duyarsa, hatanın tam nedenini bilmeden destek bileti çözümü için destek personeline hata kodunu sağlar.

Aşağıdaki LazyBrowserFileStream sınıf, akışın ilk baytları istenmeden hemen önce OpenReadStream'i tembel bir şekilde çağıran özel bir akış türü tanımlar. Akış, .NET'te okunmaya başlayana kadar tarayıcıdan sunucuya iletilmez.

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

Aşağıdaki FileUpload2 bileşen:

  • Kullanıcıların istemciden dosya yüklemesine izin verir.
  • kullanıcı arabiriminde istemci tarafından sağlanan güvenilmeyen/güvenli olmayan dosya adını görüntüler. Güvenilmeyen/güvenli olmayan dosya adı, kullanıcı arabiriminde güvenli görüntü için tarafından Razor otomatik olarak HTML ile kodlanır.

Uyarı

İstemciler tarafından sağlanan dosya adlara güvenmeyin:

  • Dosyayı bir dosya sistemine veya hizmete kaydetme.
  • Dosya adlarını otomatik olarak veya geliştirici kodu aracılığıyla kodlamamış OLAN UI'lerde görüntüleyin.

Bir sunucuya dosya yüklerken dikkat edilmesi gereken güvenlik konuları hakkında daha fazla bilgi için bkz . ASP.NET Core'da dosyaları karşıya yükleme.

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

Bileşen, dosya yüklemelerini tek seferde tek bir dosya ile sınırlandırıyorsa veya yalnızca istemci tarafında render etmeyi (CSR, InteractiveWebAssembly) benimsiyorsa, bileşen LazyBrowserFileStream kullanımını önleyebilir ve bunun yerine Streamkullanabilir. Aşağıda, FileUpload2 bileşeni için yapılan değişiklikler gösterilmektedir:

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

Kullanılmadığı için LazyBrowserFileStream sınıfını (LazyBrowserFileStream.cs) kaldırın.

Bileşen, dosya yüklemelerini tek seferde bir dosyayla sınırlıyorsa, LazyBrowserFileStream kullanmaktan kaçınıp Stream kullanabilir. Aşağıda, FileUpload2 bileşenine yönelik değişiklikler gösterilmektedir:

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

LazyBrowserFileStream sınıfını (LazyBrowserFileStream.cs) kullanılmadığından kaldırın.

Web API projesindeki aşağıdaki denetleyici, istemciden karşıya yüklenen dosyaları kaydeder.

Önemli

Bu bölümdeki denetleyici, uygulamadan ayrı bir web API'sinde Blazor kullanılmak üzere tasarlanmıştır. Dosya yükleme kullanıcılarının kimliği doğrulanırsa web API'sinin Siteler Arası İstek Sahteciliği (XSRF/CSRF) saldırılarını azaltması gerekir.

Not

.NET 6'da ASP.NET Core'daki Minimum API'ler[FromForm]özniteliğine sahip form değerlerini bağlama yerel olarak desteklenmez. Bu nedenle, aşağıdaki Filesave denetleyici örneği Minimum API'leri kullanacak şekilde dönüştürülemez. Minimum API'lere sahip form değerlerinden bağlama desteği ,NET 7 veya sonraki sürümlerde ASP.NET Core'da kullanılabilir.

Aşağıdaki kodu kullanmak için, ortamda çalışan uygulamanın web API'sinin projesinin kökünde Development/unsafe_uploads bir Development klasör oluşturun.

Örnek, dosyaların kaydedildiği yolun bir parçası olarak uygulamanın ortamını kullandığından, test ve üretimde diğer ortamlar kullanılıyorsa ek klasörler gerekir. Örneğin, Staging ortamı için bir Staging/unsafe_uploads klasör oluşturun. Production/unsafe_uploads klasörü oluşturun Production ortamı için.

Uyarı

Örnek, dosyaları içeriklerini taramadan kaydeder ve bu makaledeki yönergeler karşıya yüklenen dosyalar için ek güvenlik en iyi yöntemlerini hesaba katmıyor. Hazırlık ve üretim sistemlerinde, yükleme klasöründe yürütme iznini devre dışı bırakın ve dosyaları yükledikten hemen sonra virüsten koruma/kötü amaçlı yazılımdan koruma tarayıcı API'siyle tarayın. Daha fazla bilgi için bkz ASP.NET Core'da dosya yükleme.

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

Yukarıdaki kodda, GetRandomFileName güvenli bir dosya adı oluşturmak için çağrılır. Tarayıcı tarafından sağlanan dosya adına asla güvenmeyin; bir siber saldırı uzmanı, var olan bir dosyanın üzerine yazan veya uygulamanın dışına yazmaya çalışan bir yol gönderen mevcut bir dosya adını seçebilir.

Sunucu uygulaması, denetleyici hizmetlerini kaydetmeli ve denetleyici uç noktalarını eşlemelidir. Daha fazla bilgi için bkz . ASP.NET Core'da denetleyici eylemlerine yönlendirme.

İstemci tarafı işleme (CSR) ile bir sunucuya dosya yükleme

Bu bölüm, Blazor Web App'ler veya Blazor WebAssembly uygulamalardaki istemci tarafından işlenmiş (CSR) bileşenler için geçerlidir.

Aşağıdaki örnek, CSR benimseyen bir Blazor Web App bileşeni veya Blazor WebAssembly uygulamasındaki bir bileşenden ayrı olarak, büyük olasılıkla ayrı bir sunucuda olan bağımsız bir uygulamadaki arka uç web API denetleyicisine dosya yükleme işlemini göstermektedir.

Örnek, HTTP/2 protokolü ve HTTPS ile Chromium tabanlı bir tarayıcı (örneğin Google Chrome veya Microsoft Edge) için istek akışı benimser. İstek akışı kullanılamıyorsa, Blazor istek akışı olmadan zarif bir şekilde Fetch API düzeyine indirgenir. Daha fazla bilgi için Dosya boyutu okuma ve karşıya yükleme sınırları bölümüne bakın.

Aşağıdaki UploadResult sınıf, karşıya yüklenen bir dosyanın sonucunu saklar. Bir dosya sunucuya yüklenemediğinde, kullanıcıya görüntülenmesi için içinde ErrorCode bir hata kodu döndürülür. Her dosya için sunucuda güvenli bir dosya adı oluşturulur ve görüntülenmek üzere istemciye StoredFileName döndürülür. Dosyalar, içinde güvenli olmayan/güvenilmeyen dosya adı FileNamekullanılarak istemci ile sunucu arasında anahtarlanır.

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

Önceki UploadResult sınıf istemci ve sunucu tabanlı projeler arasında paylaşılabilir. İstemci ve sunucu projeleri sınıfı paylaştığında, paylaşılan proje için her projenin _Imports.razor dosyasına bir içeri aktarma ekleyin. Örneğin:

@using BlazorSample.Shared

Aşağıdaki FileUpload2 bileşen:

  • Kullanıcıların istemciden dosya yüklemesine izin verir.
  • kullanıcı arabiriminde istemci tarafından sağlanan güvenilmeyen/güvenli olmayan dosya adını görüntüler. Güvenilmeyen/güvenli olmayan dosya adı, kullanıcı arabiriminde güvenli görüntü için tarafından Razor otomatik olarak HTML ile kodlanır.

Üretim uygulamaları için en iyi güvenlik uygulaması, istemcilere uygulama, sunucu veya ağ hakkındaki hassas bilgileri ortaya çıkarabilecek hata iletileri göndermekten kaçınmaktır. Ayrıntılı hata iletileri sağlamak, kötü amaçlı bir kullanıcının uygulama, sunucu veya ağ üzerindeki saldırıları geliştirmelerine yardımcı olabilir. Bu bölümdeki örnek kod, yalnızca sunucu tarafı hatası oluşursa bileşen istemci tarafı tarafından görüntülenmesi için bir hata kodu numarası (int) gönderir. Bir kullanıcı dosya yükleme konusunda yardıma ihtiyaç duyarsa, hatanın tam nedenini bilmeden destek bileti çözümü için destek personeline hata kodunu sağlar.

Uyarı

İstemciler tarafından sağlanan dosya adlara güvenmeyin:

  • Dosyayı bir dosya sistemine veya hizmete kaydetme.
  • Dosya adlarını otomatik olarak veya geliştirici kodu aracılığıyla kodlamamış OLAN UI'lerde görüntüleyin.

Bir sunucuya dosya yüklerken dikkat edilmesi gereken güvenlik konuları hakkında daha fazla bilgi için bkz . ASP.NET Core'da dosyaları karşıya yükleme.

Blazor Web App sunucu projesinde, projenin Program dosyasına IHttpClientFactory ve ilgili hizmetleri ekleyin:

builder.Services.AddHttpClient();

İstemci tarafı bileşeni sunucuda önceden depolandığından, HttpClient hizmetleri sunucu projesine eklenmelidir. aşağıdaki bileşen için öndevre dışı bırakırsanız, sunucu projesindeki HttpClient hizmetlerini sağlamanız gerekmez ve önceki satırı sunucu projesine eklemeniz gerekmez.

ASP.NET Core uygulamasına hizmet ekleme HttpClient hakkında daha fazla bilgi için bkz . ASP.NET Core'da IHttpClientFactory kullanarak HTTP istekleri oluşturma.

Bir istemci projesi, HTTP POST isteklerini bir arka uç web API denetleyicisine yönlendirebilmek için HttpClient kaydetmeli .Client (Blazor Web App). İstemci projesinin Program dosyasına aşağıdakileri onaylayın veya ekleyin:

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

Önceki örnek, uygulamanın temel adresini builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress) ile ayarlar, bu adres genellikle ana bilgisayar sayfasındaki <base> etiketinin href değerinden türetilir. Dış web API'sini çağırıyorsanız URI'yi web API'sinin temel adresine ayarlayın.

Dosyaları ayrı bir sunucu web API'sine yükleyen tek başına Blazor WebAssembly uygulaması, HttpClientadlı bir kullanır veya varsayılan HttpClient hizmet kaydını web API'sinin uç noktasına işaret etmek üzere ayarlar. Web API'sinin 5001 numaralı bağlantı noktasında yerel olarak barındırıldığı aşağıdaki örnekte temel adres https://localhost:5001:

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

Blazor Web Appiçinde, Microsoft.AspNetCore.Components.WebAssembly.Http ad alanını bileşenin yönergelerine ekleyin.

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

Sunucu tarafı projesindeki aşağıdaki denetleyici, karşıya yüklenen dosyaları istemciden kaydeder.

Notlar

.NET 6'da ASP.NET Core'da Minimal API'ler için [FromForm] özniteliği ile form değerlerini bağlama yerel olarak desteklenmez. Bu nedenle, aşağıdaki Filesave denetleyici örneği Minimum API'leri kullanacak şekilde dönüştürülemez. Minimum API'lere sahip form değerlerinden bağlama desteği ,NET 7 veya sonraki sürümlerde ASP.NET Core'da kullanılabilir.

Aşağıdaki kodu kullanmak için, ortamda çalışan uygulamanın sunucu tarafı projesinin kökünde Development/unsafe_uploads bir Development klasör oluşturun.

Örnek, dosyaların kaydedildiği yolun bir parçası olarak uygulamanın ortamını kullandığından, test ve üretimde diğer ortamlar kullanılıyorsa ek klasörler gerekir. Örneğin, Staging/unsafe_uploads ortamı için bir Staging klasörü oluşturun. Production/unsafe_uploads klasörünü Production ortamı için oluşturun.

Uyarı

Örnek, dosyaları içeriklerini taramadan kaydeder ve bu makaledeki yönergeler karşıya yüklenen dosyalar için ek güvenlik en iyi yöntemlerini hesaba katmıyor. Hazırlık ve üretim sistemlerinde, yükleme klasöründe yürütme iznini devre dışı bırakın ve yüklemeden hemen sonra dosyaları bir virüsten koruma/kötü amaçlı yazılımdan koruma tarama API'si ile tarayın. Daha fazla bilgi için ASP.NET Core'da dosyaları karşıya yükleme bölümüne bakın.

Barındırılan bir Blazor WebAssembly uygulaması için veya paylaşılan projenin UploadResult sınıfını sağlamak için kullanıldığı aşağıdaki örnekte, paylaşılan projenin ad alanını ekleyin:

using BlazorSample.Shared;

Aşağıdaki denetleyici için bir ad alanı kullanmanızı öneririz (örneğin: 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);
    }
}

Yukarıdaki kodda, GetRandomFileName güvenli bir dosya adı oluşturmak için çağrılır. Tarayıcı tarafından sağlanan dosya adına asla güvenmeyin; bir siber saldırı uzmanı, var olan bir dosyanın üzerine yazan veya uygulamanın dışına yazmaya çalışan bir yol gönderen mevcut bir dosya adını seçebilir.

Sunucu uygulamasının denetleyici hizmetlerini kaydetmesi ve denetleyici uç noktalarını eşlemesi gerekir. Daha fazla bilgi için bkz . ASP.NET Core'da denetleyici eylemlerine yönlendirme. Kimliği doğrulanmış kullanıcılar için siteler Arası İstek Sahteciliği (XSRF/CSRF) saldırılarını otomatik olarak azaltmak için denetleyici hizmetlerini AddControllersWithViews ile eklemenizi öneririz. Yalnızca AddControllerskullanıyorsanız, kötü amaçlı yazılımdan koruma otomatik olarak etkinleştirilmez. Daha fazla bilgi için bkz . ASP.NET Core'da denetleyici eylemlerine yönlendirme.

Sunucu farklı bir kaynakta barındırıldığında istek akışı için sunucudaki Çıkış Noktaları Arası İstekler (CORS) yapılandırması gerekir ve her zaman istemci tarafından bir denetim öncesi isteği yapılır. Sunucunun Program dosyasının (Blazor Web App sunucu projesi veya Blazor WebAssembly bir uygulamanın arka uç sunucusu web API'sinin) hizmet yapılandırmasında, aşağıdaki varsayılan CORS ilkesi bu makaledeki örneklerle test için uygundur. İstemci, 5003 numaralı bağlantı noktasından yerel istekte bulunur. Bağlantı noktası numarasını, kullandığınız istemci uygulama bağlantı noktasıyla eşleşecek şekilde değiştirin:

Sunucuda Çapraz Kaynak İstekleri (CORS) yapılandırın. Sunucunun Program dosyasının (Blazor Web App sunucu projesi veya Blazor WebAssembly bir uygulamanın arka uç sunucusu web API'sinin) hizmet yapılandırmasında, aşağıdaki varsayılan CORS ilkesi bu makaledeki örneklerle test için uygundur. İstemci, 5003 numaralı bağlantı noktasından yerel istekte bulunur. Bağlantı noktası numarasını, kullandığınız istemci uygulama bağlantı noktasıyla eşleşecek şekilde değiştirin:

Sunucuda Kaynaklar Arası Paylaşım İstekleri (CORS) yapılandırın. Arka uç sunucusu web API'sinin Program dosyasının hizmet yapılandırmasında, aşağıdaki varsayılan CORS ilkesi bu makaledeki örneklerle test için uygundur. İstemci, 5003 numaralı bağlantı noktasından yerel istekte bulunur. Bağlantı noktası numarasını, kullandığınız istemci uygulama bağlantı noktasıyla eşleşecek şekilde değiştirin:

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

Program dosyasındaki UseHttpsRedirection'ı çağırdıktan sonra CORS ara yazılımını eklemek için UseCors'yi çağırın.

app.UseCors();

Daha fazla bilgi için bkz. ASP.NET Core'da Kaynaklar Arası İstekleri (CORS) Etkinleştirme.

Sınırlar yükleme boyutunu kısıtlıyorsa sunucunun maksimum istek gövdesi boyutunu ve çoklu parçalı gövde uzunluk sınırlarını yapılandırın.

Kestrel sunucusu için MaxRequestBodySize (varsayılan: 30.000.000 bayt) ve FormOptions.MultipartBodyLengthLimit (varsayılan: 134.217.728 bayt) ayarlayın. Bileşende ve denetleyicide maxFileSize değişkenini aynı değere ayarlayın.

Aşağıdaki Program dosya Kestrel yapılandırmasında (Blazor Web App sunucu projesi veya bir Blazor WebAssembly uygulamanın arka uç sunucusu web API'sinde), {LIMIT} yer tutucusu bayt cinsinden sınırdır.

using Microsoft.AspNetCore.Http.Features;

...

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

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

Dosya yükleme işlemini iptal etme

Bir dosya karşıya yükleme bileşeni, bir kullanıcı yüklemeyi iptal ettiğinde CancellationToken, IBrowserFile.OpenReadStream veya StreamReader.ReadAsync içine çağrı yaparak bunu algılayabilir.

InputFile bileşeni için bir CancellationTokenSource oluşturun. Yöntemin OnInputFileChange başlangıcında, daha önce başlatılmış bir yüklemenin devam edip etmediğini kontrol edin.

Karşıya dosya yükleme işlemi sürüyorsa:

Dosyaları sunucu tarafında ilerleme göstergesiyle yükleme

Aşağıdaki örnekte, kullanıcıya yükleme ilerlemesinin görüntülendiği sunucu tarafı bir uygulamada dosyaların nasıl yükleneceği açıklanmaktadır.

Bir test uygulamasında aşağıdaki örneği kullanmak için:

  • Karşıya yüklenen dosyaları ortama kaydetmek için Development bir klasör oluşturun: Development/unsafe_uploads.
  • En büyük dosya boyutunu (maxFileSizeaşağıdaki örnekte 15 KB) ve izin verilen en fazla dosya sayısını (maxAllowedFilesaşağıdaki örnekte 3) yapılandırın.
  • Devam eden raporlamada daha fazla ayrıntı düzeyi için arabelleği farklı bir değere (aşağıdaki örnekte 10 KB) ayarlayın. Performans ve güvenlik endişeleri nedeniyle 30 KB'den büyük bir arabellek kullanmanızı önermiyoruz.

Uyarı

Örnek, dosyaları içeriklerini taramadan kaydeder ve bu makaledeki yönergeler karşıya yüklenen dosyalar için ek güvenlik en iyi yöntemlerini hesaba katmıyor. Hazırlama ve üretim sistemlerinde karşıya yükleme klasöründe yürütme iznini devre dışı bırakın ve karşıya yüklemeden hemen sonra dosyaları virüsten koruma/kötü amaçlı yazılımdan koruma tarayıcı API'siyle tarayın. Daha fazla bilgi için ASP.NET Core'da dosya yükleme bölümüne bakın.

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

Daha fazla bilgi için aşağıdaki API kaynaklarına bakın:

  • FileStream: Hem zaman uyumlu hem de zaman uyumsuz okuma ve yazma işlemlerini destekleyen bir dosya sağlar Stream .
  • FileStream.ReadAsync: Önceki FileUpload3 bileşen, akışı ReadAsync ile eş zamansız olarak okur. Read ile zaman uyumlu akış okuma, Razor bileşenlerde desteklenmez.

Dosya akışları

Sunucu etkileşimi ile dosya verileri, dosya okundukça SignalR bağlantısı üzerinden sunucudaki .NET koduna akışı sağlanır.

RemoteBrowserFileStreamOptions dosya karşıya yükleme özelliklerinin yapılandırılmasına izin verir.

WebAssembly tarafından işlenen bir bileşen için dosya verileri doğrudan tarayıcıdaki .NET koduna akışla aktarılır.

Resim önizlemesini yükle

Görüntü yüklemelerinin önizlemesini sağlamak için, bir bileşen referansı ve işleyici içeren bir InputFile bileşeni ekleyerek OnChange işe başlayın.

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

Görüntü önizlemesi için yer tutucu görevi gören bir öğe referansı ile bir görüntü öğesi ekleyin:

<img @ref="previewImageElem" />

İlişkili referansları ekleyin.

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

JavaScript'te, aşağıdakileri gerçekleştiren HTML input ve img öğesiyle adlı bir işlev ekleyin:

  • Seçili dosyayı ayıklar.
  • ile createObjectURLbir nesne URL'si oluşturur.
  • Görüntü yüklendikten sonra nesne URL'sini revokeObjectURL iptal etmek için bir olay dinleyicisi ayarlar, böylece bellek sızdırılamaz.
  • img Görüntüyü görüntülemek için öğenin kaynağını ayarlar.
window.previewImage = (inputElem, imgElem) => {
  const url = URL.createObjectURL(inputElem.files[0]);
  imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });
  imgElem.src = url;
}

Son olarak, JavaScript işlevini çağıran OnChange işleyicisini eklemek için IJSRuntime enjekte edilmiş öğesini kullanın.

@inject IJSRuntime JS

...

@code {
    ...

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

Yukarıdaki örnek, tek bir görüntüyü karşıya yüklemeye yöneliktir. Yaklaşım, görüntüleri destekleyecek multiple şekilde genişletilebilir.

Aşağıdaki FileUpload4 bileşende tam örnek gösterilmektedir.

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

EF Core ile küçük dosyaları doğrudan veritabanına kaydetme

Birçok ASP.NET Core uygulaması, veritabanı işlemlerini yönetmek için Entity Framework Core (EF Core) kullanır. Küçük resimleri ve avatarları doğrudan veritabanına kaydetmek yaygın bir gereksinimdir. Bu bölümde, üretim uygulamaları için daha da geliştirilebilen genel bir yaklaşım gösterilmektedir.

Aşağıdaki desen:

  • Blazor film veritabanı öğretici uygulamasıüzerine kuruludur.
  • doğrulama geri bildirimidosya boyutu ve içerik türü için ek kod ile geliştirilebilir.
  • Performans kaybı ve DoS riski oluşur. Herhangi bir dosyayı belleğe okurken riski dikkatle değerlendirin ve özellikle daha büyük dosyalar için alternatif yaklaşımları göz önünde bulundurun. Alternatif yaklaşımlar arasında dosyaları doğrudan diske veya virüsten koruma/kötü amaçlı yazılımdan koruma denetimleri için üçüncü taraf bir hizmete kaydetme, daha fazla işleme ve istemcilere sunma sayılabilir.

Aşağıdaki örneğin Blazor Web App (ASP.NET Core 8.0 veya üzeri) üzerinde çalışması için, bileşenin bir InputFile bileşen dosya değişikliğinde (OnChange parametre/olay) HandleSelectedThumbnail çağırmak için etkileşimli işleme modu (örneğin, @rendermode InteractiveServer) benimsemesi gerekir. Blazor Server uygulama bileşenleri her zaman etkileşimlidir ve işleme modu gerektirmez.

Aşağıdaki örnekte, IBrowserFile içindeki küçük bir küçük resim (<= 100 KB), EF Corekullanılarak bir veritabanına kaydedilir. InputFile bileşeni için kullanıcı tarafından bir dosya seçilmediyse, veritabanına varsayılan bir küçük resim kaydedilir.

Varsayılan küçük resim (default-thumbnail.jpg), daha yeni Çıkış Dizinine KopyalaKopyala ayarıyla proje kökündedir:

Varsayılan genel küçük resim görüntüsü

Movie modelinin (Movie.cs) küçük resim görüntüsü verilerini tutmak için bir özelliği (Thumbnail) vardır:

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

Görüntü verileri veritabanında varbinary(MAX)olarak bayt olarak depolanır. Base-64 kodlanmış veriler görüntünün ham baytlarından kabaca üçüncü derecede büyük olduğundan, uygulama base-64 görüntüleme baytlarını kodlar, bu nedenle base-64 görüntü verileri ek veritabanı depolaması gerektirir ve veritabanı okuma/yazma işlemlerinin performansını azaltır.

Küçük resmi görüntüleyen bileşenler görüntü verilerini img etiketinin src özniteliğine JPEG, base-64 ile kodlanmış veriler olarak geçirir:

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

Aşağıdaki Create bileşeninde bir görüntü yükleme işlemi gerçekleştirilir. ASP.NET Core Blazor form doğrulamayaklaşımlarını kullanarak dosya türü ve boyutu için özel doğrulama ile örneği daha da geliştirebilirsiniz. Aşağıdaki örnekte küçük resim karşıya yükleme kodu olmadan Create bileşeninin tamamını görmek için Blazor örnekleri GitHub deposuBlazorWebAppMovies örnek uygulamasına bakın.

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

Kullanıcıların bir filmin küçük resim görüntüsünü düzenlemesine izin verildiğinde, Edit bileşeninde etkileşimli işleme moduyla aynı yaklaşım benimsenecek.

Dosyaları dış hizmete yükleme

İstemciler, dosya yükleme işlemlerini ve yüklenen dosyaları işleyen uygulama sunucusu yerine, dosyaları doğrudan dış bir hizmete yükleyebilir. Uygulama, dış hizmetten gelen dosyaları isteğe bağlı olarak güvenli bir şekilde işleyebilir. Bu yaklaşım, uygulamayı ve sunucusunu kötü amaçlı saldırılara ve olası performans sorunlarına karşı güçlendirmektedir.

Aşağıdaki olası avantajlara sahip Azure Dosyalar, Azure Blob Depolama veya üçüncü taraf hizmet kullanan bir yaklaşımı göz önünde bulundurun:

Azure Blob Depolama ve Azure Dosyalar hakkında daha fazla bilgi için Azure Depolama belgelerine bakın.

Sunucu tarafı SignalR ileti boyutu sınırı

Dosya yüklemeleri, en büyük Blazor ileti boyutunu aşan dosyalar hakkındaki verileri aldığında, SignalR bunlar başlamadan önce bile başarısız olabilir.

SignalR , alınan her ileti için geçerli olan bir ileti Blazor boyutu sınırı tanımlar ve InputFile bileşen, yapılandırılan sınıra uygun iletilerde dosyaları sunucuya akışla aktarır. Ancak, karşıya yüklenecek dosya kümesini gösteren ilk ileti benzersiz bir tek ileti olarak gönderilir. İlk iletinin boyutu ileti boyutu sınırını aşabilir SignalR . Sorun dosyaların boyutuyla değil, dosya sayısıyla ilgilidir.

Günlüğe kaydedilen hata aşağıdakine benzer:

Hata: Bağlantı 'Hata: Sunucu kapatma sırasında bir hata döndürdü: Bağlantı bir hatayla kapatıldı' hatasıyla kesildi. e.log @ blazor.server.js:1

Dosyaları karşıya yüklerken, ilk iletide ileti boyutu sınırına ulaşmak nadirdir. Sınıra ulaşılırsa uygulama daha büyük bir değerle yapılandırılabilir HubOptions.MaximumReceiveMessageSize .

yapılandırması ve MaximumReceiveMessageSize nasıl ayarlanacağı hakkında daha fazla bilgi için ASP.NET Core BlazorSignalR kılavuzuna bakın.

İstemci bağlantı merkezi başına maksimum paralel çağrı ayarı

Blazor, varsayılan değer olan 1'e ayarlanan MaximumParallelInvocationsPerClient'ye dayanır.

Değerin artırılması, CopyTo işlemlerin hata System.InvalidOperationException: 'Reading is not allowed after reader was completed.' atma olasılığının yüksek olmasına neden olur. Daha fazla bilgi için bkz MaximumParallelInvocationsPerClient > 1, dosya yüklemesini Blazor Server modunda bozuyor (dotnet/aspnetcore #53951).

Sorun giderme

Çağıran IBrowserFile.OpenReadStream satır bir System.TimeoutException oluşturur:

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

Olası nedenler:

Ek kaynaklar