ASP.NET Core dosya yüklemeleri
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.
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, maxFileSize
atanan 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çin2097152
). - 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ı FileName
kullanı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ı FileName
kullanı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ı, HttpClient
adlı 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:
- Önceki yüklemeyi Cancel olarak arayın.
- Sonraki yükleme için yeni bir CancellationTokenSource oluşturun ve CancellationTokenSource.Token'i OpenReadStream veya ReadAsync'e iletin.
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 (
maxFileSize
aşağıdaki örnekte 15 KB) ve izin verilen en fazla dosya sayısını (maxAllowedFiles
aş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
createObjectURL
bir 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:
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:
- Dosyaları istemciden doğrudan JavaScript istemci kitaplığı veya REST API'sine sahip bir dış hizmete yükleyin. Örneğin, Azure aşağıdaki istemci kitaplıklarını ve API'leri sunar:
- Her istemci dosyası yüklemesi için uygulama (sunucu tarafı) tarafından oluşturulan bir kullanıcı temsilcisi paylaşılan erişim imzası (SAS) belirteci ile kullanıcı yüklemelerini yetkileyin. Örneğin Azure aşağıdaki SAS özelliklerini sunar:
- Otomatik yedeklilik ve dosya paylaşımı yedeklemesi sağlayın.
- Karşıya yüklemeleri kotalarla sınırlayın. Azure Blob Depolama kotalarının kapsayıcı düzeyinde değil hesap düzeyinde ayarlandığını unutmayın. Ancak Azure Dosyalarının kotaları dosya paylaşımı düzeyindedir ve yükleme sınırları üzerinde daha iyi bir kontrol sağlayabilir. Daha fazla bilgi için bu listenin önceki bölümlerinde bağlantı verilen Azure belgelerine bakın.
- Sunucu tarafı şifrelemesi (SSE) ile dosyaların güvenliğini sağlama.
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:
9.0'dan önceki ASP.NET Core sürümlerinde yerleşik ASP.NET Core bağımlılık ekleme kapsayıcısı yerine Autofac Inversion of Control (IoC) kapsayıcısını kullanma. Sorunu çözmek için, sunucu tarafı bağlantı hattı işleyici hub seçeneklerinde DisableImplicitFromServicesParameters'ı
true
olarak ayarlayın. Daha fazla bilgi için bkz . FileUpload: Ayrılan sürede veri alınmadı (dotnet/aspnetcore
#38842).Akış tamamlanmadan okunmuyor. Bu bir çerçeve sorunu değildir. Özel durumu yakalayın ve yerel ortamınızda/ağınızda daha fazla araştırın.
- Birden çok dosyada tamamlanmadan önce okuma işlemi için sunucu tarafı render etme ve OpenReadStream çağırma kullanma. Sorunu çözmek için bu makalenin
LazyBrowserFileStream
Sunucu tarafı işleme ile dosyaları sunucuya yükleme bölümünde açıklanan sınıfını ve yaklaşımı kullanın.
Ek kaynaklar
ASP.NET Core