ASP.NET Core'da dosyaları karşıya yükleme

Tarafından Rutger Storm

ASP.NET Core, daha küçük dosyalar için arabelleğe alınan model bağlamasını kullanarak bir veya daha fazla dosya yüklemeyi ve daha büyük dosyalar için kapsayıcısız akışı destekler.

Örnek kodu görüntüleme veya indirme (indirme)

Güvenlik konuları

Kullanıcılara dosyaları bir sunucuya yükleme olanağı sağlarken dikkatli olun. Saldırganlar şu işlemleri yapmaya çalışabilir:

  • Hizmet reddi saldırılarını yürütür.
  • Virüsleri veya kötü amaçlı yazılımları karşıya yükleyin.
  • Ağların ve sunucuların güvenliğini başka şekillerde aşma.

Başarılı bir saldırı olasılığını azaltan güvenlik adımları şunlardır:

  • Dosyaları, tercihen sistem dışı bir sürücüye ayrılmış bir dosya karşıya yükleme alanına yükleyin. Ayrılmış konum, karşıya yüklenen dosyalara güvenlik kısıtlamaları uygulamayı kolaylaştırır. Dosya karşıya yükleme konumu üzerinde yürütme izinlerini devre dışı bırakın.†
  • Karşıya yüklenen dosyaları app.† ile aynı dizin ağacında kalıcı hale getirme
  • Uygulama tarafından belirlenen güvenli bir dosya adı kullanın. Kullanıcı tarafından sağlanan bir dosya adını veya karşıya yüklenen dosyanın güvenilmeyen dosya adını kullanmayın.† HTML, güvenilmeyen dosya adını görüntülerken kodlar. Örneğin, dosya adını günlüğe kaydetme veya kullanıcı arabiriminde görüntüleme (Razor HTML çıktıyı otomatik olarak kodlar).
  • Uygulamanın tasarım belirtimi için yalnızca onaylı dosya uzantılarına izin ver.†
  • server.† İstemci tarafı denetimlerinin kolayca aşıldığını doğrulayın.
  • Karşıya yüklenen dosyanın boyutunu denetleyin. Büyük karşıya yüklemeleri önlemek için boyut üst sınırı ayarlayın.†
  • Karşıya yüklenen bir dosyanın üzerine aynı ada sahip dosyaların üzerine yazılmaması gerektiğinde, dosyayı karşıya yüklemeden önce dosya adını veritabanına veya fiziksel depolama alanına karşı denetleyin.
  • Dosya depolanmadan önce karşıya yüklenen içerikte bir virüs/kötü amaçlı yazılım tarayıcısı çalıştırın.

† Örnek uygulama, ölçütleri karşılayan bir yaklaşım gösterir.

Uyarı

Kötü amaçlı kodu sisteme yüklemek genellikle aşağıdakiler gibi kod yürütmenin ilk adımıdır:

  • Sistemin kontrolünü tamamen ele geçirin.
  • Sistemin kilitlenmesi sonucuyla sistemi aşırı yükleyin.
  • Kullanıcı veya sistem verilerinin güvenliğini aşma.
  • Genel kullanıcı arabirimine grafiti uygulama.

Kullanıcılardan dosya kabul ederken saldırı yüzeyi alanını azaltma hakkında bilgi için aşağıdaki kaynaklara bakın:

Örnek uygulama örnekleri de dahil olmak üzere güvenlik önlemleri uygulama hakkında daha fazla bilgi için Doğrulama bölümüne bakın.

Depolama senaryoları

Dosyalar için yaygın depolama seçenekleri şunlardır:

  • Veritabanı

    • Küçük dosya yüklemeleri için veritabanı genellikle fiziksel depolama (dosya sistemi veya ağ paylaşımı) seçeneklerinden daha hızlıdır.
    • Kullanıcı verileri için veritabanı kaydının alınması aynı anda dosya içeriğini (örneğin, bir avatar görüntüsü) sağlayabildiği için veritabanı genellikle fiziksel depolama seçeneklerinden daha kullanışlıdır.
    • Veritabanı, bulut veri depolama hizmeti kullanmaktan daha düşük maliyetli olabilir.
  • Fiziksel depolama (dosya sistemi veya ağ paylaşımı)

    • Büyük dosya yüklemeleri için:
      • Veritabanı sınırları karşıya yüklemenin boyutunu kısıtlayabilir.
      • Fiziksel depolama genellikle veritabanındaki depolamadan daha az ekonomiktir.
    • Fiziksel depolama, bulut veri depolama hizmeti kullanmaktan daha düşük maliyetli olabilir.
    • Uygulamanın işleminin depolama konumu için okuma ve yazma izinlerine sahip olması gerekir. Hiçbir zaman yürütme izni verme.
  • Bulut veri depolama hizmeti, örneğin Azure Blob Depolama.

    • Hizmetler genellikle tek hata noktalarına tabi olan şirket içi çözümlere göre geliştirilmiş ölçeklenebilirlik ve dayanıklılık sunar.
    • Büyük depolama altyapısı senaryolarında hizmetler potansiyel olarak daha düşük maliyetlidir.

    Daha fazla bilgi için bkz . Hızlı Başlangıç: Nesne depolamada blob oluşturmak için .NET kullanma.

Küçük ve büyük dosyalar

Küçük ve büyük dosyaların tanımı, kullanılabilir bilgi işlem kaynaklarına bağlıdır. Uygulamalar, beklenen boyutları işleyebileceğinden emin olmak için kullanılan depolama yaklaşımını karşılaştırmalıdır. Bellek, CPU, disk ve veritabanı performansını karşılaştırma.

Dağıtımınız için küçük ve büyük olan sınırlarda belirli sınırlar sağlanamıyor olsa da AspNetCore'un FormOptions için ilgili varsayılan değerlerinden bazıları şunlardır:

  • Varsayılan olarak, HttpRequest.Form istek gövdesinin tamamını ()BufferBody arabelleğe almaz, ancak dahil edilen çok bölümlü form dosyalarını arabelleğe alır.
  • MultipartBodyLengthLimit , arabelleğe alınan form dosyalarının en büyük boyutudur ve varsayılan olarak 128 MB'tır.
  • MemoryBufferThreshold diskteki bir arabellek dosyasına geçmeden önce bellekteki dosyaların ne kadar arabelleğe alınacaklarını gösterir ve varsayılan olarak 64 KB'tır. MemoryBufferThreshold uygulama kaynaklarına ve senaryolarına bağlı olarak yükseltilen veya azaltılan küçük ve büyük dosyalar arasında bir sınır işlevi görür.

hakkında FormOptionsdaha fazla bilgi için kaynak koduna bakın.

Dosya yükleme senaryoları

Dosyaları karşıya yüklemeye yönelik iki genel yaklaşım arabelleğe alma ve akıştır.

Tamponlama

Dosyanın tamamı dosyasında IFormFileokunur. IFormFile , dosyayı işlemek veya kaydetmek için kullanılan dosyanın C# gösterimidir.

Dosya yüklemeleri tarafından kullanılan disk ve bellek, eşzamanlı dosya yüklemelerinin sayısına ve boyutuna bağlıdır. Bir uygulama çok fazla karşıya yüklemeyi arabelleğe almayı denerse, bellek veya disk alanı bittiğinde site kilitlenir. Dosya yüklemelerinin boyutu veya sıklığı uygulama kaynaklarını tüketiyorsa akışı kullanın.

64 KB'yi aşan tek bir arabelleğe alınan dosya bellekten disk üzerindeki geçici bir dosyaya taşınır.

Daha büyük istekler için geçici dosyalar, ortam değişkeninde ASPNETCORE_TEMP adlı konuma yazılır. Tanımlanmamışsa ASPNETCORE_TEMP , dosyalar geçerli kullanıcının geçici klasörüne yazılır.

Küçük dosyaları arabelleğe alma, bu konunun aşağıdaki bölümlerinde ele alınmıştır:

Akışlar

Dosya çok parçalı bir istekten alınır ve uygulama tarafından doğrudan işlenir veya kaydedilir. Akış, performansı önemli ölçüde geliştirmez. Akış, dosyaları karşıya yüklerken bellek veya disk alanı taleplerini azaltır.

Büyük dosyaları akışla aktarma, Büyük dosyaları akışla karşıya yükleme bölümünde ele alınmıştır.

Arabelleğe alınan model bağlaması olan küçük dosyaları fiziksel depolamaya yükleme

Küçük dosyaları karşıya yüklemek için çok parçalı bir form kullanın veya JavaScript kullanarak bir POST isteği oluşturun.

Aşağıdaki örnekte, tek bir Razor dosyayı (Pages/BufferedSingleFileUploadPhysical.cshtml örnek uygulamada) karşıya yüklemek için Sayfalar formunun kullanımı gösterilmektedir:

<form enctype="multipart/form-data" method="post">
    <dl>
        <dt>
            <label asp-for="FileUpload.FormFile"></label>
        </dt>
        <dd>
            <input asp-for="FileUpload.FormFile" type="file">
            <span asp-validation-for="FileUpload.FormFile"></span>
        </dd>
    </dl>
    <input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>

Aşağıdaki örnek, aşağıdakiler dışında önceki örne benzerdir:

  • Formun verilerini göndermek için JavaScript'in (Fetch API'si) kullanılır.
  • Doğrulama yok.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload" 
      enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;" 
      method="post">
    <dl>
        <dt>
            <label for="FileUpload_FormFile">File</label>
        </dt>
        <dd>
            <input id="FileUpload_FormFile" type="file" 
                name="FileUpload.FormFile" />
        </dd>
    </dl>

    <input class="btn" type="submit" value="Upload" />

    <div style="margin-top:15px">
        <output name="result"></output>
    </div>
</form>

<script>
  async function AJAXSubmit (oFormElement) {
    var resultElement = oFormElement.elements.namedItem("result");
    const formData = new FormData(oFormElement);

    try {
    const response = await fetch(oFormElement.action, {
      method: 'POST',
      body: formData
    });

    if (response.ok) {
      window.location.href = '/';
    }

    resultElement.value = 'Result: ' + response.status + ' ' + 
      response.statusText;
    } catch (error) {
      console.error('Error:', error);
    }
  }
</script>

Fetch API'sini desteklemeyen istemciler için JavaScript'te POST formunu gerçekleştirmek için aşağıdaki yaklaşımlardan birini kullanın:

  • Bir Fetch Polyfill (örneğin, window.fetch polyfill (github/fetch)) kullanın.

  • XMLHttpRequest adresini kullanın. Örnek:

    <script>
      "use strict";
    
      function AJAXSubmit (oFormElement) {
        var oReq = new XMLHttpRequest();
        oReq.onload = function(e) { 
        oFormElement.elements.namedItem("result").value = 
          'Result: ' + this.status + ' ' + this.statusText;
        };
        oReq.open("post", oFormElement.action);
        oReq.send(new FormData(oFormElement));
      }
    </script>
    

Dosya yüklemelerini desteklemek için HTML formlarının kodlama türünü (enctype) belirtmesi multipart/form-datagerekir.

files Bir giriş öğesinin birden çok dosya yüklemeyi desteklemesi multiple için öğesinde özniteliğini <input> sağlayın:

<input asp-for="FileUpload.FormFiles" type="file" multiple>

Sunucuya yüklenen dosyalara kullanılarak Model BağlamaIFormFilearacılığıyla erişilebilir. Örnek uygulama, veritabanı ve fiziksel depolama senaryoları için birden çok arabelleğe alınan dosya yüklemesini gösterir.

Uyarı

görüntüleme ve günlüğe FileName kaydetme için dışında özelliğini IFormFile kullanmayın. Görüntülerken veya günlüğe kaydetme sırasında, HTML dosya adını kodlar. Saldırgan, tam yollar veya göreli yollar dahil olmak üzere kötü amaçlı bir dosya adı sağlayabilir. Uygulamalar:

  • Kullanıcı tarafından sağlanan dosya adından yolu kaldırın.
  • KULLANıCı arabirimi veya günlük kaydı için HTML ile kodlanmış, yolu kaldırılmış dosya adını kaydedin.
  • Depolama için yeni bir rastgele dosya adı oluşturun.

Aşağıdaki kod dosya adından yolu kaldırır:

string untrustedFileName = Path.GetFileName(pathName);

Şu ana kadar verilen örnekler güvenlikle ilgili dikkate alınmaz. Aşağıdaki bölümler ve örnek uygulama tarafından ek bilgiler sağlanır:

ve model bağlamasını IFormFilekullanarak dosyaları karşıya yüklerken eylem yöntemi şunları kabul edebilir:

Dekont

Bağlama, form dosyalarını ada göre eşleştirir. Örneğin, içindeki <input type="file" name="formFile"> HTML name değeri C# parametresi/özellik sınırı (FormFile) ile eşleşmelidir. Daha fazla bilgi için POST yönteminin parametre adıyla ad özniteliği değerini eşleştirme bölümüne bakın.

Aşağıdaki örnek:

  • Karşıya yüklenen bir veya daha fazla dosyada döngüler oluşturur.
  • Path.GetTempFileName kullanarak dosya adı da dahil olmak üzere bir dosyanın tam yolunu döndürür.
  • Uygulama tarafından oluşturulan bir dosya adını kullanarak dosyaları yerel dosya sistemine kaydeder.
  • Karşıya yüklenen dosyaların toplam sayısını ve boyutunu döndürür.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
    long size = files.Sum(f => f.Length);

    foreach (var formFile in files)
    {
        if (formFile.Length > 0)
        {
            var filePath = Path.GetTempFileName();

            using (var stream = System.IO.File.Create(filePath))
            {
                await formFile.CopyToAsync(stream);
            }
        }
    }

    // Process uploaded files
    // Don't rely on or trust the FileName property without validation.

    return Ok(new { count = files.Count, size });
}

Yolu olmayan bir dosya adı oluşturmak için kullanın Path.GetRandomFileName . Aşağıdaki örnekte, yol yapılandırmadan elde edilir:

foreach (var formFile in files)
{
    if (formFile.Length > 0)
    {
        var filePath = Path.Combine(_config["StoredFilesPath"], 
            Path.GetRandomFileName());

        using (var stream = System.IO.File.Create(filePath))
        {
            await formFile.CopyToAsync(stream);
        }
    }
}

öğesine FileStream geçirilen yol dosya adını içermelidir. Dosya adı sağlanmazsa, çalışma zamanında bir UnauthorizedAccessException oluşturulur.

Tekniği kullanılarak IFormFile karşıya yüklenen dosyalar işlenmeden önce bellekte veya sunucudaki diskte arabelleğe alınıyor. Eylem yönteminin içinde içeriğine IFormFile olarak Streamerişilebilir. Yerel dosya sistemine ek olarak, dosyalar bir ağ paylaşımına veya Azure Blob depolama gibi bir dosya depolama hizmetine kaydedilebilir.

Karşıya yüklemek üzere birden çok dosya üzerinde döngüler oluşturan ve güvenli dosya adlarını kullanan başka bir örnek için örnek uygulamadaki bölümüne bakın Pages/BufferedMultipleFileUploadPhysical.cshtml.cs .

Uyarı

Path.GetTempFileName , önceki geçici dosyalar silinmeden 65.535'ten fazla dosya oluşturulursa bir IOException oluşturur. 65.535 dosya sınırı sunucu başına bir sınırdır. Windows işletim sistemindeki bu sınır hakkında daha fazla bilgi için aşağıdaki konulardaki açıklamalara bakın:

Arabelleğe alınan model bağlaması olan küçük dosyaları veritabanına yükleme

Entity Framework kullanarak ikili dosya verilerini veritabanında depolamak için varlıkta bir Byte dizi özelliği tanımlayın:

public class AppFile
{
    public int Id { get; set; }
    public byte[] Content { get; set; }
}

öğesini içeren sınıfı için bir IFormFilesayfa modeli özelliği belirtin:

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

    [BindProperty]
    public BufferedSingleFileUploadDb FileUpload { get; set; }

    ...
}

public class BufferedSingleFileUploadDb
{
    [Required]
    [Display(Name="File")]
    public IFormFile FormFile { get; set; }
}

Dekont

IFormFile doğrudan bir eylem yöntemi parametresi veya bağlı model özelliği olarak kullanılabilir. Önceki örnekte ilişkili model özelliği kullanılır.

FileUpload, Sayfalar formunda kullanılırRazor:

<form enctype="multipart/form-data" method="post">
    <dl>
        <dt>
            <label asp-for="FileUpload.FormFile"></label>
        </dt>
        <dd>
            <input asp-for="FileUpload.FormFile" type="file">
        </dd>
    </dl>
    <input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>

Form sunucuya POSTed olduğunda, öğesini bir akışa kopyalayın IFormFile ve veritabanında bayt dizisi olarak kaydedin. Aşağıdaki örnekte, _dbContext uygulamanın veritabanı bağlamını depolar:

public async Task<IActionResult> OnPostUploadAsync()
{
    using (var memoryStream = new MemoryStream())
    {
        await FileUpload.FormFile.CopyToAsync(memoryStream);

        // Upload the file if less than 2 MB
        if (memoryStream.Length < 2097152)
        {
            var file = new AppFile()
            {
                Content = memoryStream.ToArray()
            };

            _dbContext.File.Add(file);

            await _dbContext.SaveChangesAsync();
        }
        else
        {
            ModelState.AddModelError("File", "The file is too large.");
        }
    }

    return Page();
}

Yukarıdaki örnek, örnek uygulamada gösterildiği gibi bir senaryoya benzer:

  • Pages/BufferedSingleFileUploadDb.cshtml
  • Pages/BufferedSingleFileUploadDb.cshtml.cs

Uyarı

İkili verileri ilişkisel veritabanlarında depolarken performansı olumsuz etkileyene kadar dikkatli olun.

Doğrulama olmadan özelliğine güvenmeyin veya özelliğine FileNameIFormFile güvenmeyin. FileName özelliği yalnızca görüntüleme amacıyla ve yalnızca HTML kodlamadan sonra kullanılmalıdır.

Sağlanan örneklerde güvenlik konuları dikkate alınmaz. Aşağıdaki bölümler ve örnek uygulama tarafından ek bilgiler sağlanır:

Akışla büyük dosyaları karşıya yükleme

3.1 örneği, bir dosyayı denetleyici eylemine akışla göndermek için JavaScript'in nasıl kullanılacağını gösterir. Dosyanın kötü amaçlı yazılımdan koruma belirteci özel bir filtre özniteliği kullanılarak oluşturulur ve istek gövdesi yerine istemci HTTP üst bilgilerine geçirilir. Eylem yöntemi karşıya yüklenen verileri doğrudan işlediğinden, form modeli bağlaması başka bir özel filtre tarafından devre dışı bırakılır. Eylemin içinde formun içeriği, her bir dosyayı MultipartSectionokuyan, dosyayı işleyen veya içeriği uygun şekilde depolayan bir MultipartReaderkullanılarak okunur. Çok bölümlü bölümler okunduktan sonra eylem kendi model bağlamasını gerçekleştirir.

İlk sayfa yanıtı formu yükler ve bir kötü amaçlı yazılımdan koruma belirtecini GenerateAntiforgeryTokenCookieAttribute (cookieözniteliği aracılığıyla) kaydeder. özniteliği, bir istek belirteci ile ayarlamak cookie için ASP.NET Core'un yerleşik kötü amaçlı yazılımdan koruma desteğini kullanır:

public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();

        // Send the request token as a JavaScript-readable cookie
        var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);

        context.HttpContext.Response.Cookies.Append(
            "RequestVerificationToken",
            tokens.RequestToken,
            new CookieOptions() { HttpOnly = false });
    }

    public override void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

DisableFormValueModelBindingAttribute, model bağlamasını devre dışı bırakmak için kullanılır:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        var factories = context.ValueProviderFactories;
        factories.RemoveType<FormValueProviderFactory>();
        factories.RemoveType<FormFileValueProviderFactory>();
        factories.RemoveType<JQueryFormValueProviderFactory>();
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

Örnek uygulamada GenerateAntiforgeryTokenCookieAttribute ve Sayfa kuralları kullanılarak Razor ve sayfa uygulaması modellerine Startup.ConfigureServices/StreamedSingleFileUploadDb/StreamedSingleFileUploadPhysical filtre olarak uygulanır:DisableFormValueModelBindingAttribute

services.AddRazorPages(options =>
{
    options.Conventions
        .AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
            model =>
            {
                model.Filters.Add(
                    new GenerateAntiforgeryTokenCookieAttribute());
                model.Filters.Add(
                    new DisableFormValueModelBindingAttribute());
            });
    options.Conventions
        .AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
            model =>
            {
                model.Filters.Add(
                    new GenerateAntiforgeryTokenCookieAttribute());
                model.Filters.Add(
                    new DisableFormValueModelBindingAttribute());
            });
});

Model bağlaması formu okumadığından, formdan bağlanan parametreler bağlanmaz (sorgu, yol ve üst bilgi çalışmaya devam eder). Eylem yöntemi doğrudan özelliğiyle Request çalışır. A MultipartReader , her bölümü okumak için kullanılır. Anahtar/değer verileri içinde KeyValueAccumulatordepolanır. Çok bölümlü bölümler okunduktan sonra, KeyValueAccumulator içindekiler form verilerini bir model türüne bağlamak için kullanılır.

ile EF Corebir veritabanına akış yapmak için tam StreamingController.UploadDatabase yöntem:

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    // Accumulate the form data key-value pairs in the request (formAccumulator).
    var formAccumulator = new KeyValueAccumulator();
    var trustedFileNameForDisplay = string.Empty;
    var untrustedFileNameForStorage = string.Empty;
    var streamedFileContent = Array.Empty<byte>();

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);

    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            if (MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                untrustedFileNameForStorage = contentDisposition.FileName.Value;
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);

                streamedFileContent = 
                    await FileHelpers.ProcessStreamedFile(section, contentDisposition, 
                        ModelState, _permittedExtensions, _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }
            }
            else if (MultipartRequestHelper
                .HasFormDataContentDisposition(contentDisposition))
            {
                // Don't limit the key name length because the 
                // multipart headers length limit is already in effect.
                var key = HeaderUtilities
                    .RemoveQuotes(contentDisposition.Name).Value;
                var encoding = GetEncoding(section);

                if (encoding == null)
                {
                    ModelState.AddModelError("File", 
                        $"The request couldn't be processed (Error 2).");
                    // Log error

                    return BadRequest(ModelState);
                }

                using (var streamReader = new StreamReader(
                    section.Body,
                    encoding,
                    detectEncodingFromByteOrderMarks: true,
                    bufferSize: 1024,
                    leaveOpen: true))
                {
                    // The value length limit is enforced by 
                    // MultipartBodyLengthLimit
                    var value = await streamReader.ReadToEndAsync();

                    if (string.Equals(value, "undefined", 
                        StringComparison.OrdinalIgnoreCase))
                    {
                        value = string.Empty;
                    }

                    formAccumulator.Append(key, value);

                    if (formAccumulator.ValueCount > 
                        _defaultFormOptions.ValueCountLimit)
                    {
                        // Form key count limit of 
                        // _defaultFormOptions.ValueCountLimit 
                        // is exceeded.
                        ModelState.AddModelError("File", 
                            $"The request couldn't be processed (Error 3).");
                        // Log error

                        return BadRequest(ModelState);
                    }
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    // Bind form data to the model
    var formData = new FormData();
    var formValueProvider = new FormValueProvider(
        BindingSource.Form,
        new FormCollection(formAccumulator.GetResults()),
        CultureInfo.CurrentCulture);
    var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
        valueProvider: formValueProvider);

    if (!bindingSuccessful)
    {
        ModelState.AddModelError("File", 
            "The request couldn't be processed (Error 5).");
        // Log error

        return BadRequest(ModelState);
    }

    // **WARNING!**
    // In the following example, the file is saved without
    // scanning the file's contents. In most production
    // scenarios, an anti-virus/anti-malware scanner API
    // is used on the file before making the file available
    // for download or for use by other systems. 
    // For more information, see the topic that accompanies 
    // this sample app.

    var file = new AppFile()
    {
        Content = streamedFileContent,
        UntrustedName = untrustedFileNameForStorage,
        Note = formData.Note,
        Size = streamedFileContent.Length, 
        UploadDT = DateTime.UtcNow
    };

    _context.File.Add(file);
    await _context.SaveChangesAsync();

    return Created(nameof(StreamingController), null);
}

MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):

using System;
using System.IO;
using Microsoft.Net.Http.Headers;

namespace SampleApp.Utilities
{
    public static class MultipartRequestHelper
    {
        // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
        // The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
        public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
        {
            var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;

            if (string.IsNullOrWhiteSpace(boundary))
            {
                throw new InvalidDataException("Missing content-type boundary.");
            }

            if (boundary.Length > lengthLimit)
            {
                throw new InvalidDataException(
                    $"Multipart boundary length limit {lengthLimit} exceeded.");
            }

            return boundary;
        }

        public static bool IsMultipartContentType(string contentType)
        {
            return !string.IsNullOrEmpty(contentType)
                   && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
        }

        public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="key";
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && string.IsNullOrEmpty(contentDisposition.FileName.Value)
                && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
        }

        public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
                    || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
        }
    }
}

Fiziksel bir konuma akış için tam StreamingController.UploadPhysical yöntem:

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);
    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            // This check assumes that there's a file
            // present without form data. If form data
            // is present, this method immediately fails
            // and returns the model error.
            if (!MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                ModelState.AddModelError("File", 
                    $"The request couldn't be processed (Error 2).");
                // Log error

                return BadRequest(ModelState);
            }
            else
            {
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                var trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);
                var trustedFileNameForFileStorage = Path.GetRandomFileName();

                // **WARNING!**
                // In the following example, the file is saved without
                // scanning the file's contents. In most production
                // scenarios, an anti-virus/anti-malware scanner API
                // is used on the file before making the file available
                // for download or for use by other systems. 
                // For more information, see the topic that accompanies 
                // this sample.

                var streamedFileContent = await FileHelpers.ProcessStreamedFile(
                    section, contentDisposition, ModelState, 
                    _permittedExtensions, _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }

                using (var targetStream = System.IO.File.Create(
                    Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
                {
                    await targetStream.WriteAsync(streamedFileContent);

                    _logger.LogInformation(
                        "Uploaded file '{TrustedFileNameForDisplay}' saved to " +
                        "'{TargetFilePath}' as {TrustedFileNameForFileStorage}", 
                        trustedFileNameForDisplay, _targetFilePath, 
                        trustedFileNameForFileStorage);
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    return Created(nameof(StreamingController), null);
}

Örnek uygulamada doğrulama denetimleri tarafından FileHelpers.ProcessStreamedFileişlenir.

Doğrulama

Örnek uygulamanın FileHelpers sınıfı, arabelleğe alınan ve akışa alınan IFormFile dosya yüklemeleri için çeşitli denetimler gösterir. Örnek uygulamada arabelleğe alınan dosya yüklemelerini işlemek IFormFile için dosyadaki ProcessFormFile yöntemine Utilities/FileHelpers.cs bakın. Akışlı dosyaları işlemek için aynı dosyadaki yöntemine bakın ProcessStreamedFile .

Uyarı

Örnek uygulamada görüntülenen doğrulama işleme yöntemleri, karşıya yüklenen dosyaların içeriğini taramaz. Çoğu üretim senaryosunda, dosyayı kullanıcıların veya diğer sistemlerin kullanımına açmadan önce dosyada bir virüs/kötü amaçlı yazılım tarayıcı API'si kullanılır.

Konu örneği doğrulama tekniklerinin çalışan bir örneğini sağlasa da, aşağıdakileri yapmazsanız sınıfı bir üretim uygulamasında uygulamayın FileHelpers :

  • Uygulamayı tam olarak anlayın.
  • Uygulamayı uygulamanın ortamına ve belirtimlerine uygun şekilde değiştirin.

Bu gereksinimleri karşılamadan bir uygulamada güvenlik kodunu hiçbir zaman ayrım gözetmeksizin uygulamayın.

İçerik doğrulama

Karşıya yüklenen içerikte üçüncü taraf bir virüs/kötü amaçlı yazılım tarama API'si kullanın.

Dosyaları taramak, yüksek hacimli senaryolarda sunucu kaynaklarında talepte bulunur. Dosya tarama nedeniyle istek işleme performansı azaldıysa, tarama çalışmasını muhtemelen uygulamanın sunucusundan farklı bir sunucuda çalışan bir hizmet olan bir arka plan hizmetine devredebilirsiniz. Genellikle karşıya yüklenen dosyalar, arka plan virüs tarayıcısı bunları denetleyene kadar karantinaya alınmış bir alanda tutulur. Bir dosya geçtiğinde, dosya normal dosya depolama konumuna taşınır. Bu adımlar genellikle bir dosyanın tarama durumunu gösteren bir veritabanı kaydıyla birlikte gerçekleştirilir. Uygulama ve uygulama sunucusu böyle bir yaklaşım kullanarak isteklere yanıt vermeye odaklanmış durumda kalır.

Dosya uzantısı doğrulama

Karşıya yüklenen dosyanın uzantısı, izin verilen uzantılar listesinde denetlenmelidir. Örnek:

private string[] permittedExtensions = { ".txt", ".pdf" };

var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();

if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
    // The extension is invalid ... discontinue processing the file
}

Dosya imzası doğrulama

Bir dosyanın imzası, dosyanın başındaki ilk birkaç bayt tarafından belirlenir. Bu baytlar, uzantının dosyanın içeriğiyle eşleşip eşleşmediğini belirtmek için kullanılabilir. Örnek uygulama, birkaç yaygın dosya türü için dosya imzalarını denetler. Aşağıdaki örnekte, JPEG görüntüsünün dosya imzası dosyaya göre denetleniyor:

private static readonly Dictionary<string, List<byte[]>> _fileSignature = 
    new Dictionary<string, List<byte[]>>
{
    { ".jpeg", new List<byte[]>
        {
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
        }
    },
};

using (var reader = new BinaryReader(uploadedFileData))
{
    var signatures = _fileSignature[ext];
    var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));

    return signatures.Any(signature => 
        headerBytes.Take(signature.Length).SequenceEqual(signature));
}

Ek dosya imzaları almak için bir dosya imzaları veritabanı (Google arama sonucu) ve resmi dosya belirtimleri kullanın. Danışmanlık resmi dosya belirtimleri, seçilen imzaların geçerli olmasını güvence altına alabilir.

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 için 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, her zaman HtmlEncode kullanıcının isteğinden dosya adı içeriği.

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.

Boyut doğrulama

Karşıya yüklenen dosyaların boyutunu sınırlayın.

Örnek uygulamada, dosyanın boyutu 2 MB ile sınırlıdır (bayt cinsinden gösterilir). Sınır, dosyadan appsettings.json Yapılandırma aracılığıyla sağlanır:

{
  "FileSizeLimit": 2097152
}

FileSizeLimit sınıflara PageModel eklenir:

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

    public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
    {
        _fileSizeLimit = config.GetValue<long>("FileSizeLimit");
    }

    ...
}

Dosya boyutu sınırı aştığında, dosya reddedilir:

if (formFile.Length > _fileSizeLimit)
{
    // The file is too large ... discontinue processing the file
}

Ad özniteliği değerini POST yönteminin parametre adıyla eşleştir

POST form verilerini içeren veya JavaScript'in FormData doğrudan kullandığı olmayanRazor formlarda, formun öğesinde belirtilen ad veya FormData denetleyicinin eylemindeki parametrenin adıyla eşleşmelidir.

Aşağıdaki örnekte:

  • Bir <input> öğe name kullanılırken özniteliği değerine battlePlansayarlanır:

    <input type="file" name="battlePlans" multiple>
    
  • JavaScript'te kullanırken FormData , ad değerine battlePlansayarlanır:

    var formData = new FormData();
    
    for (var file in files) {
      formData.append("battlePlans", file, file.name);
    }
    

C# yöntemininbattlePlans parametresi () için eşleşen bir ad kullanın:

  • adlı Uploadbir Razor Pages sayfa işleyici yöntemi için:

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • MVC POST denetleyicisi eylem yöntemi için:

    public async Task<IActionResult> Post(List<IFormFile> battlePlans)
    

Sunucu ve uygulama yapılandırması

Çok parçalı gövde uzunluğu sınırı

MultipartBodyLengthLimit her çok parçalı gövdenin uzunluğu için sınırı ayarlar. Bu sınırı aşan form bölümleri ayrıştırıldığında bir InvalidDataException oluşturur. Varsayılan değer 134.217.728 'dir (128 MB). içindeki Startup.ConfigureServicesayarını kullanarak MultipartBodyLengthLimit sınırı özelleştirin:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<FormOptions>(options =>
    {
        // Set the limit to 256 MB
        options.MultipartBodyLengthLimit = 268435456;
    });
}

RequestFormLimitsAttribute , tek bir sayfa veya eylem için öğesini ayarlamak MultipartBodyLengthLimit için kullanılır.

Razor Sayfalar uygulamasında, filtreyi içinde Startup.ConfigureServicesbir kuralla uygulayın:

services.AddRazorPages(options =>
{
    options.Conventions
        .AddPageApplicationModelConvention("/FileUploadPage",
            model.Filters.Add(
                new RequestFormLimitsAttribute()
                {
                    // Set the limit to 256 MB
                    MultipartBodyLengthLimit = 268435456
                });
});

Razor Sayfalar uygulamasında veya MVC uygulamasında, filtreyi sayfa modeline veya eylem yöntemine uygulayın:

// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    ...
}

Kestrel maksimum istek gövdesi boyutu

tarafından Kestrelbarındırılan uygulamalar için varsayılan istek gövdesi boyutu üst sınırı 30.000.000 bayttır ve bu da yaklaşık 28,6 MB'tır. MaxRequestBodySizeKestrel sunucusu seçeneğini kullanarak sınırı özelleştirin:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel((context, options) =>
            {
                // Handle requests up to 50 MB
                options.Limits.MaxRequestBodySize = 52428800;
            })
            .UseStartup<Startup>();
        });

RequestSizeLimitAttributetek bir sayfa veya eylem için MaxRequestBodySize ayarlamak için kullanılır.

Razor Sayfalar uygulamasında, filtreyi içinde Startup.ConfigureServicesbir kuralla uygulayın:

services.AddRazorPages(options =>
{
    options.Conventions
        .AddPageApplicationModelConvention("/FileUploadPage",
            model =>
            {
                // Handle requests up to 50 MB
                model.Filters.Add(
                    new RequestSizeLimitAttribute(52428800));
            });
});

Sayfalar Razor uygulamasında veya MVC uygulamasında, filtreyi sayfa işleyici sınıfına veya eylem yöntemine uygulayın:

// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    ...
}

yönergesi RequestSizeLimitAttribute@attributeRazor kullanılarak da uygulanabilir:

@attribute [RequestSizeLimitAttribute(52428800)]

Diğer Kestrel sınırlar

tarafından Kestrelbarındırılan uygulamalar için diğer Kestrel sınırlar geçerli olabilir:

IIS

Varsayılan istek sınırı (maxAllowedContentLength), yaklaşık 28,6 MB olan 30.000.000 bayttır. Dosyadaki sınırı özelleştirin web.config . Aşağıdaki örnekte sınır 50 MB (52.428.800 bayt) olarak ayarlanmıştır:

<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="52428800" />
    </requestFiltering>
  </security>
</system.webServer>

Bu maxAllowedContentLength ayar yalnızca IIS için geçerlidir. Daha fazla bilgi için bkz . İstek Sınırları <requestLimits>.

Sorun giderme

Dosyaları karşıya yüklerken karşılaşılan bazı yaygın sorunlar ve bunların olası çözümleri aşağıdadır.

IIS sunucusuna dağıtıldığında Bulunamadı hatası

Aşağıdaki hata, karşıya yüklenen dosyanın sunucunun yapılandırılmış içerik uzunluğunu aştığını gösterir:

HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.

Daha fazla bilgi için IIS bölümüne bakın.

Bağlantı hatası

Bağlantı hatası ve sıfırlama sunucusu bağlantısı büyük olasılıkla karşıya yüklenen dosyanın istek gövdesi boyutunun üst sınırını aştığını Kestrelgösterir. Daha fazla bilgi için en fazla istek gövdesi boyutu bölümüne bakın.Kestrel Kestrel istemci bağlantı sınırları da ayarlama gerektirebilir.

IFormFile ile Null Başvuru Özel Durumu

Denetleyici kullanarak IFormFile karşıya yüklenen dosyaları kabul ediyor ancak değeri ise null, HTML formunun değerini enctype belirttiğini multipart/form-dataonaylayın. Bu öznitelik öğesinde <form> ayarlanmadıysa, dosya karşıya yükleme gerçekleşmez ve ilişkili IFormFile bağımsız değişkenler olur null. Ayrıca form verilerindeki karşıya yükleme adlandırmasının uygulamanın adlandırması ile eşleştiğinden emin olun.

Akış çok uzundu

Bu konudaki örnekler, karşıya yüklenen dosyanın içeriğini tutmak için temel MemoryStream alır. boyutunun MemoryStream boyut sınırı şeklindedir int.MaxValue. Uygulamanın dosya yükleme senaryosunda dosya içeriğinin 50 MB'tan büyük tutulması gerekiyorsa, karşıya yüklenen dosyanın içeriğini tutmak için tek MemoryStream bir öğeye dayanmayan alternatif bir yaklaşım kullanın.

ASP.NET Core, daha küçük dosyalar için arabelleğe alınan model bağlamasını kullanarak bir veya daha fazla dosya yüklemeyi ve daha büyük dosyalar için kapsayıcısız akışı destekler.

Örnek kodu görüntüleme veya indirme (indirme)

Güvenlik konuları

Kullanıcılara dosyaları bir sunucuya yükleme olanağı sağlarken dikkatli olun. Saldırganlar şu işlemleri yapmaya çalışabilir:

  • Hizmet reddi saldırılarını yürütür.
  • Virüsleri veya kötü amaçlı yazılımları karşıya yükleyin.
  • Ağların ve sunucuların güvenliğini başka şekillerde aşma.

Başarılı bir saldırı olasılığını azaltan güvenlik adımları şunlardır:

  • Dosyaları, tercihen sistem dışı bir sürücüye ayrılmış bir dosya karşıya yükleme alanına yükleyin. Ayrılmış konum, karşıya yüklenen dosyalara güvenlik kısıtlamaları uygulamayı kolaylaştırır. Dosya karşıya yükleme konumu üzerinde yürütme izinlerini devre dışı bırakın.†
  • Karşıya yüklenen dosyaları app.† ile aynı dizin ağacında kalıcı hale getirme
  • Uygulama tarafından belirlenen güvenli bir dosya adı kullanın. Kullanıcı tarafından sağlanan bir dosya adını veya karşıya yüklenen dosyanın güvenilmeyen dosya adını kullanmayın.† HTML, güvenilmeyen dosya adını görüntülerken kodlar. Örneğin, dosya adını günlüğe kaydetme veya kullanıcı arabiriminde görüntüleme (Razor HTML çıktıyı otomatik olarak kodlar).
  • Uygulamanın tasarım belirtimi için yalnızca onaylı dosya uzantılarına izin ver.†
  • server.† İstemci tarafı denetimlerinin kolayca aşıldığını doğrulayın.
  • Karşıya yüklenen dosyanın boyutunu denetleyin. Büyük karşıya yüklemeleri önlemek için boyut üst sınırı ayarlayın.†
  • Karşıya yüklenen bir dosyanın üzerine aynı ada sahip dosyaların üzerine yazılmaması gerektiğinde, dosyayı karşıya yüklemeden önce dosya adını veritabanına veya fiziksel depolama alanına karşı denetleyin.
  • Dosya depolanmadan önce karşıya yüklenen içerikte bir virüs/kötü amaçlı yazılım tarayıcısı çalıştırın.

† Örnek uygulama, ölçütleri karşılayan bir yaklaşım gösterir.

Uyarı

Kötü amaçlı kodu sisteme yüklemek genellikle aşağıdakiler gibi kod yürütmenin ilk adımıdır:

  • Sistemin kontrolünü tamamen ele geçirin.
  • Sistemin kilitlenmesi sonucuyla sistemi aşırı yükleyin.
  • Kullanıcı veya sistem verilerinin güvenliğini aşma.
  • Genel kullanıcı arabirimine grafiti uygulama.

Kullanıcılardan dosya kabul ederken saldırı yüzeyi alanını azaltma hakkında bilgi için aşağıdaki kaynaklara bakın:

Örnek uygulama örnekleri de dahil olmak üzere güvenlik önlemleri uygulama hakkında daha fazla bilgi için Doğrulama bölümüne bakın.

Depolama senaryoları

Dosyalar için yaygın depolama seçenekleri şunlardır:

  • Veritabanı

    • Küçük dosya yüklemeleri için veritabanı genellikle fiziksel depolama (dosya sistemi veya ağ paylaşımı) seçeneklerinden daha hızlıdır.
    • Kullanıcı verileri için veritabanı kaydının alınması aynı anda dosya içeriğini (örneğin, bir avatar görüntüsü) sağlayabildiği için veritabanı genellikle fiziksel depolama seçeneklerinden daha kullanışlıdır.
    • Veritabanı, veri depolama hizmeti kullanmaktan daha düşük maliyetli olabilir.
  • Fiziksel depolama (dosya sistemi veya ağ paylaşımı)

    • Büyük dosya yüklemeleri için:
      • Veritabanı sınırları karşıya yüklemenin boyutunu kısıtlayabilir.
      • Fiziksel depolama genellikle veritabanındaki depolamadan daha az ekonomiktir.
    • Fiziksel depolama, veri depolama hizmeti kullanmaktan daha düşük maliyetli olabilir.
    • Uygulamanın işleminin depolama konumu için okuma ve yazma izinlerine sahip olması gerekir. Hiçbir zaman yürütme izni verme.
  • Veri depolama hizmeti (örneğin, Azure Blob Depolama)

    • Hizmetler genellikle tek hata noktalarına tabi olan şirket içi çözümlere göre geliştirilmiş ölçeklenebilirlik ve dayanıklılık sunar.
    • Büyük depolama altyapısı senaryolarında hizmetler potansiyel olarak daha düşük maliyetlidir.

    Daha fazla bilgi için bkz . Hızlı Başlangıç: Nesne depolamada blob oluşturmak için .NET kullanma.

Dosya yükleme senaryoları

Dosyaları karşıya yüklemeye yönelik iki genel yaklaşım arabelleğe alma ve akıştır.

Tamponlama

Dosyanın tamamı, dosyasını işlemek veya kaydetmek için kullanılan dosyanın C# gösterimi olan dosyasında IFormFileokunur.

Dosya yüklemeleri tarafından kullanılan kaynaklar (disk, bellek), eşzamanlı dosya yüklemelerinin sayısına ve boyutuna bağlıdır. Bir uygulama çok fazla karşıya yüklemeyi arabelleğe almayı denerse, bellek veya disk alanı bittiğinde site kilitlenir. Dosya yüklemelerinin boyutu veya sıklığı uygulama kaynaklarını tüketiyorsa akışı kullanın.

Dekont

64 KB'yi aşan tek bir arabelleğe alınan dosya bellekten disk üzerindeki geçici bir dosyaya taşınır.

Küçük dosyaları arabelleğe alma, bu konunun aşağıdaki bölümlerinde ele alınmıştır:

Akışlar

Dosya çok parçalı bir istekten alınır ve uygulama tarafından doğrudan işlenir veya kaydedilir. Akış, performansı önemli ölçüde geliştirmez. Akış, dosyaları karşıya yüklerken bellek veya disk alanı taleplerini azaltır.

Büyük dosyaları akışla aktarma, Büyük dosyaları akışla karşıya yükleme bölümünde ele alınmıştır.

Arabelleğe alınan model bağlaması olan küçük dosyaları fiziksel depolamaya yükleme

Küçük dosyaları karşıya yüklemek için çok parçalı bir form kullanın veya JavaScript kullanarak bir POST isteği oluşturun.

Aşağıdaki örnekte, tek bir Razor dosyayı (Pages/BufferedSingleFileUploadPhysical.cshtml örnek uygulamada) karşıya yüklemek için Sayfalar formunun kullanımı gösterilmektedir:

<form enctype="multipart/form-data" method="post">
    <dl>
        <dt>
            <label asp-for="FileUpload.FormFile"></label>
        </dt>
        <dd>
            <input asp-for="FileUpload.FormFile" type="file">
            <span asp-validation-for="FileUpload.FormFile"></span>
        </dd>
    </dl>
    <input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>

Aşağıdaki örnek, aşağıdakiler dışında önceki örne benzerdir:

  • Formun verilerini göndermek için JavaScript'in (Fetch API'si) kullanılır.
  • Doğrulama yok.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload" 
      enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;" 
      method="post">
    <dl>
        <dt>
            <label for="FileUpload_FormFile">File</label>
        </dt>
        <dd>
            <input id="FileUpload_FormFile" type="file" 
                name="FileUpload.FormFile" />
        </dd>
    </dl>

    <input class="btn" type="submit" value="Upload" />

    <div style="margin-top:15px">
        <output name="result"></output>
    </div>
</form>

<script>
  async function AJAXSubmit (oFormElement) {
    var resultElement = oFormElement.elements.namedItem("result");
    const formData = new FormData(oFormElement);

    try {
    const response = await fetch(oFormElement.action, {
      method: 'POST',
      body: formData
    });

    if (response.ok) {
      window.location.href = '/';
    }

    resultElement.value = 'Result: ' + response.status + ' ' + 
      response.statusText;
    } catch (error) {
      console.error('Error:', error);
    }
  }
</script>

Fetch API'sini desteklemeyen istemciler için JavaScript'te POST formunu gerçekleştirmek için aşağıdaki yaklaşımlardan birini kullanın:

  • Bir Fetch Polyfill (örneğin, window.fetch polyfill (github/fetch)) kullanın.

  • XMLHttpRequest adresini kullanın. Örnek:

    <script>
      "use strict";
    
      function AJAXSubmit (oFormElement) {
        var oReq = new XMLHttpRequest();
        oReq.onload = function(e) { 
        oFormElement.elements.namedItem("result").value = 
          'Result: ' + this.status + ' ' + this.statusText;
        };
        oReq.open("post", oFormElement.action);
        oReq.send(new FormData(oFormElement));
      }
    </script>
    

Dosya yüklemelerini desteklemek için HTML formlarının kodlama türünü (enctype) belirtmesi multipart/form-datagerekir.

files Bir giriş öğesinin birden çok dosya yüklemeyi desteklemesi multiple için öğesinde özniteliğini <input> sağlayın:

<input asp-for="FileUpload.FormFiles" type="file" multiple>

Sunucuya yüklenen dosyalara kullanılarak Model BağlamaIFormFilearacılığıyla erişilebilir. Örnek uygulama, veritabanı ve fiziksel depolama senaryoları için birden çok arabelleğe alınan dosya yüklemesini gösterir.

Uyarı

görüntüleme ve günlüğe FileName kaydetme için dışında özelliğini IFormFile kullanmayın. Görüntülerken veya günlüğe kaydetme sırasında, HTML dosya adını kodlar. Saldırgan, tam yollar veya göreli yollar dahil olmak üzere kötü amaçlı bir dosya adı sağlayabilir. Uygulamalar:

  • Kullanıcı tarafından sağlanan dosya adından yolu kaldırın.
  • KULLANıCı arabirimi veya günlük kaydı için HTML ile kodlanmış, yolu kaldırılmış dosya adını kaydedin.
  • Depolama için yeni bir rastgele dosya adı oluşturun.

Aşağıdaki kod dosya adından yolu kaldırır:

string untrustedFileName = Path.GetFileName(pathName);

Şu ana kadar verilen örnekler güvenlikle ilgili dikkate alınmaz. Aşağıdaki bölümler ve örnek uygulama tarafından ek bilgiler sağlanır:

ve model bağlamasını IFormFilekullanarak dosyaları karşıya yüklerken eylem yöntemi şunları kabul edebilir:

Dekont

Bağlama, form dosyalarını ada göre eşleştirir. Örneğin, içindeki <input type="file" name="formFile"> HTML name değeri C# parametresi/özellik sınırı (FormFile) ile eşleşmelidir. Daha fazla bilgi için POST yönteminin parametre adıyla ad özniteliği değerini eşleştirme bölümüne bakın.

Aşağıdaki örnek:

  • Karşıya yüklenen bir veya daha fazla dosyada döngüler oluşturur.
  • Path.GetTempFileName kullanarak dosya adı da dahil olmak üzere bir dosyanın tam yolunu döndürür.
  • Uygulama tarafından oluşturulan bir dosya adını kullanarak dosyaları yerel dosya sistemine kaydeder.
  • Karşıya yüklenen dosyaların toplam sayısını ve boyutunu döndürür.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
    long size = files.Sum(f => f.Length);

    foreach (var formFile in files)
    {
        if (formFile.Length > 0)
        {
            var filePath = Path.GetTempFileName();

            using (var stream = System.IO.File.Create(filePath))
            {
                await formFile.CopyToAsync(stream);
            }
        }
    }

    // Process uploaded files
    // Don't rely on or trust the FileName property without validation.

    return Ok(new { count = files.Count, size });
}

Yolu olmayan bir dosya adı oluşturmak için kullanın Path.GetRandomFileName . Aşağıdaki örnekte, yol yapılandırmadan elde edilir:

foreach (var formFile in files)
{
    if (formFile.Length > 0)
    {
        var filePath = Path.Combine(_config["StoredFilesPath"], 
            Path.GetRandomFileName());

        using (var stream = System.IO.File.Create(filePath))
        {
            await formFile.CopyToAsync(stream);
        }
    }
}

öğesine FileStream geçirilen yol dosya adını içermelidir. Dosya adı sağlanmazsa, çalışma zamanında bir UnauthorizedAccessException oluşturulur.

Tekniği kullanılarak IFormFile karşıya yüklenen dosyalar işlenmeden önce bellekte veya sunucudaki diskte arabelleğe alınıyor. Eylem yönteminin içinde içeriğine IFormFile olarak Streamerişilebilir. Yerel dosya sistemine ek olarak, dosyalar bir ağ paylaşımına veya Azure Blob depolama gibi bir dosya depolama hizmetine kaydedilebilir.

Karşıya yüklemek üzere birden çok dosya üzerinde döngüler oluşturan ve güvenli dosya adlarını kullanan başka bir örnek için örnek uygulamadaki bölümüne bakın Pages/BufferedMultipleFileUploadPhysical.cshtml.cs .

Uyarı

Path.GetTempFileName , önceki geçici dosyalar silinmeden 65.535'ten fazla dosya oluşturulursa bir IOException oluşturur. 65.535 dosya sınırı sunucu başına bir sınırdır. Windows işletim sistemindeki bu sınır hakkında daha fazla bilgi için aşağıdaki konulardaki açıklamalara bakın:

Arabelleğe alınan model bağlaması olan küçük dosyaları veritabanına yükleme

Entity Framework kullanarak ikili dosya verilerini veritabanında depolamak için varlıkta bir Byte dizi özelliği tanımlayın:

public class AppFile
{
    public int Id { get; set; }
    public byte[] Content { get; set; }
}

öğesini içeren sınıfı için bir IFormFilesayfa modeli özelliği belirtin:

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

    [BindProperty]
    public BufferedSingleFileUploadDb FileUpload { get; set; }

    ...
}

public class BufferedSingleFileUploadDb
{
    [Required]
    [Display(Name="File")]
    public IFormFile FormFile { get; set; }
}

Dekont

IFormFile doğrudan bir eylem yöntemi parametresi veya bağlı model özelliği olarak kullanılabilir. Önceki örnekte ilişkili model özelliği kullanılır.

FileUpload, Sayfalar formunda kullanılırRazor:

<form enctype="multipart/form-data" method="post">
    <dl>
        <dt>
            <label asp-for="FileUpload.FormFile"></label>
        </dt>
        <dd>
            <input asp-for="FileUpload.FormFile" type="file">
        </dd>
    </dl>
    <input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>

Form sunucuya POSTed olduğunda, öğesini bir akışa kopyalayın IFormFile ve veritabanında bayt dizisi olarak kaydedin. Aşağıdaki örnekte, _dbContext uygulamanın veritabanı bağlamını depolar:

public async Task<IActionResult> OnPostUploadAsync()
{
    using (var memoryStream = new MemoryStream())
    {
        await FileUpload.FormFile.CopyToAsync(memoryStream);

        // Upload the file if less than 2 MB
        if (memoryStream.Length < 2097152)
        {
            var file = new AppFile()
            {
                Content = memoryStream.ToArray()
            };

            _dbContext.File.Add(file);

            await _dbContext.SaveChangesAsync();
        }
        else
        {
            ModelState.AddModelError("File", "The file is too large.");
        }
    }

    return Page();
}

Yukarıdaki örnek, örnek uygulamada gösterildiği gibi bir senaryoya benzer:

  • Pages/BufferedSingleFileUploadDb.cshtml
  • Pages/BufferedSingleFileUploadDb.cshtml.cs

Uyarı

İkili verileri ilişkisel veritabanlarında depolarken performansı olumsuz etkileyene kadar dikkatli olun.

Doğrulama olmadan özelliğine güvenmeyin veya özelliğine FileNameIFormFile güvenmeyin. FileName özelliği yalnızca görüntüleme amacıyla ve yalnızca HTML kodlamadan sonra kullanılmalıdır.

Sağlanan örneklerde güvenlik konuları dikkate alınmaz. Aşağıdaki bölümler ve örnek uygulama tarafından ek bilgiler sağlanır:

Akışla büyük dosyaları karşıya yükleme

Aşağıdaki örnekte, bir dosyayı denetleyici eylemine akışla göndermek için JavaScript'in nasıl kullanılacağı gösterilmektedir. Dosyanın kötü amaçlı yazılımdan koruma belirteci özel bir filtre özniteliği kullanılarak oluşturulur ve istek gövdesi yerine istemci HTTP üst bilgilerine geçirilir. Eylem yöntemi karşıya yüklenen verileri doğrudan işlediğinden, form modeli bağlaması başka bir özel filtre tarafından devre dışı bırakılır. Eylemin içinde formun içeriği, her bir dosyayı MultipartSectionokuyan, dosyayı işleyen veya içeriği uygun şekilde depolayan bir MultipartReaderkullanılarak okunur. Çok bölümlü bölümler okunduktan sonra eylem kendi model bağlamasını gerçekleştirir.

İlk sayfa yanıtı formu yükler ve bir kötü amaçlı yazılımdan koruma belirtecini GenerateAntiforgeryTokenCookieAttribute (cookieözniteliği aracılığıyla) kaydeder. özniteliği, bir istek belirteci ile ayarlamak cookie için ASP.NET Core'un yerleşik kötü amaçlı yazılımdan koruma desteğini kullanır:

public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();

        // Send the request token as a JavaScript-readable cookie
        var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);

        context.HttpContext.Response.Cookies.Append(
            "RequestVerificationToken",
            tokens.RequestToken,
            new CookieOptions() { HttpOnly = false });
    }

    public override void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

DisableFormValueModelBindingAttribute, model bağlamasını devre dışı bırakmak için kullanılır:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        var factories = context.ValueProviderFactories;
        factories.RemoveType<FormValueProviderFactory>();
        factories.RemoveType<FormFileValueProviderFactory>();
        factories.RemoveType<JQueryFormValueProviderFactory>();
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

Örnek uygulamada GenerateAntiforgeryTokenCookieAttribute ve Sayfa kuralları kullanılarak Razor ve sayfa uygulaması modellerine Startup.ConfigureServices/StreamedSingleFileUploadDb/StreamedSingleFileUploadPhysical filtre olarak uygulanır:DisableFormValueModelBindingAttribute

services.AddRazorPages(options =>
{
    options.Conventions
        .AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
            model =>
            {
                model.Filters.Add(
                    new GenerateAntiforgeryTokenCookieAttribute());
                model.Filters.Add(
                    new DisableFormValueModelBindingAttribute());
            });
    options.Conventions
        .AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
            model =>
            {
                model.Filters.Add(
                    new GenerateAntiforgeryTokenCookieAttribute());
                model.Filters.Add(
                    new DisableFormValueModelBindingAttribute());
            });
});

Model bağlaması formu okumadığından, formdan bağlanan parametreler bağlanmaz (sorgu, yol ve üst bilgi çalışmaya devam eder). Eylem yöntemi doğrudan özelliğiyle Request çalışır. A MultipartReader , her bölümü okumak için kullanılır. Anahtar/değer verileri içinde KeyValueAccumulatordepolanır. Çok bölümlü bölümler okunduktan sonra, KeyValueAccumulator içindekiler form verilerini bir model türüne bağlamak için kullanılır.

ile EF Corebir veritabanına akış yapmak için tam StreamingController.UploadDatabase yöntem:

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    // Accumulate the form data key-value pairs in the request (formAccumulator).
    var formAccumulator = new KeyValueAccumulator();
    var trustedFileNameForDisplay = string.Empty;
    var untrustedFileNameForStorage = string.Empty;
    var streamedFileContent = Array.Empty<byte>();

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);

    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            if (MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                untrustedFileNameForStorage = contentDisposition.FileName.Value;
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);

                streamedFileContent = 
                    await FileHelpers.ProcessStreamedFile(section, contentDisposition, 
                        ModelState, _permittedExtensions, _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }
            }
            else if (MultipartRequestHelper
                .HasFormDataContentDisposition(contentDisposition))
            {
                // Don't limit the key name length because the 
                // multipart headers length limit is already in effect.
                var key = HeaderUtilities
                    .RemoveQuotes(contentDisposition.Name).Value;
                var encoding = GetEncoding(section);

                if (encoding == null)
                {
                    ModelState.AddModelError("File", 
                        $"The request couldn't be processed (Error 2).");
                    // Log error

                    return BadRequest(ModelState);
                }

                using (var streamReader = new StreamReader(
                    section.Body,
                    encoding,
                    detectEncodingFromByteOrderMarks: true,
                    bufferSize: 1024,
                    leaveOpen: true))
                {
                    // The value length limit is enforced by 
                    // MultipartBodyLengthLimit
                    var value = await streamReader.ReadToEndAsync();

                    if (string.Equals(value, "undefined", 
                        StringComparison.OrdinalIgnoreCase))
                    {
                        value = string.Empty;
                    }

                    formAccumulator.Append(key, value);

                    if (formAccumulator.ValueCount > 
                        _defaultFormOptions.ValueCountLimit)
                    {
                        // Form key count limit of 
                        // _defaultFormOptions.ValueCountLimit 
                        // is exceeded.
                        ModelState.AddModelError("File", 
                            $"The request couldn't be processed (Error 3).");
                        // Log error

                        return BadRequest(ModelState);
                    }
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    // Bind form data to the model
    var formData = new FormData();
    var formValueProvider = new FormValueProvider(
        BindingSource.Form,
        new FormCollection(formAccumulator.GetResults()),
        CultureInfo.CurrentCulture);
    var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
        valueProvider: formValueProvider);

    if (!bindingSuccessful)
    {
        ModelState.AddModelError("File", 
            "The request couldn't be processed (Error 5).");
        // Log error

        return BadRequest(ModelState);
    }

    // **WARNING!**
    // In the following example, the file is saved without
    // scanning the file's contents. In most production
    // scenarios, an anti-virus/anti-malware scanner API
    // is used on the file before making the file available
    // for download or for use by other systems. 
    // For more information, see the topic that accompanies 
    // this sample app.

    var file = new AppFile()
    {
        Content = streamedFileContent,
        UntrustedName = untrustedFileNameForStorage,
        Note = formData.Note,
        Size = streamedFileContent.Length, 
        UploadDT = DateTime.UtcNow
    };

    _context.File.Add(file);
    await _context.SaveChangesAsync();

    return Created(nameof(StreamingController), null);
}

MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):

using System;
using System.IO;
using Microsoft.Net.Http.Headers;

namespace SampleApp.Utilities
{
    public static class MultipartRequestHelper
    {
        // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
        // The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
        public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
        {
            var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;

            if (string.IsNullOrWhiteSpace(boundary))
            {
                throw new InvalidDataException("Missing content-type boundary.");
            }

            if (boundary.Length > lengthLimit)
            {
                throw new InvalidDataException(
                    $"Multipart boundary length limit {lengthLimit} exceeded.");
            }

            return boundary;
        }

        public static bool IsMultipartContentType(string contentType)
        {
            return !string.IsNullOrEmpty(contentType)
                   && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
        }

        public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="key";
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && string.IsNullOrEmpty(contentDisposition.FileName.Value)
                && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
        }

        public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
                    || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
        }
    }
}

Fiziksel bir konuma akış için tam StreamingController.UploadPhysical yöntem:

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);
    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            // This check assumes that there's a file
            // present without form data. If form data
            // is present, this method immediately fails
            // and returns the model error.
            if (!MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                ModelState.AddModelError("File", 
                    $"The request couldn't be processed (Error 2).");
                // Log error

                return BadRequest(ModelState);
            }
            else
            {
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                var trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);
                var trustedFileNameForFileStorage = Path.GetRandomFileName();

                // **WARNING!**
                // In the following example, the file is saved without
                // scanning the file's contents. In most production
                // scenarios, an anti-virus/anti-malware scanner API
                // is used on the file before making the file available
                // for download or for use by other systems. 
                // For more information, see the topic that accompanies 
                // this sample.

                var streamedFileContent = await FileHelpers.ProcessStreamedFile(
                    section, contentDisposition, ModelState, 
                    _permittedExtensions, _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }

                using (var targetStream = System.IO.File.Create(
                    Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
                {
                    await targetStream.WriteAsync(streamedFileContent);

                    _logger.LogInformation(
                        "Uploaded file '{TrustedFileNameForDisplay}' saved to " +
                        "'{TargetFilePath}' as {TrustedFileNameForFileStorage}", 
                        trustedFileNameForDisplay, _targetFilePath, 
                        trustedFileNameForFileStorage);
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    return Created(nameof(StreamingController), null);
}

Örnek uygulamada doğrulama denetimleri tarafından FileHelpers.ProcessStreamedFileişlenir.

Doğrulama

Örnek uygulamanın FileHelpers sınıfı, arabelleğe alınan ve akışa alınan IFormFile dosya yüklemeleri için çeşitli denetimler gösterir. Örnek uygulamada arabelleğe alınan dosya yüklemelerini işlemek IFormFile için dosyadaki ProcessFormFile yöntemine Utilities/FileHelpers.cs bakın. Akışlı dosyaları işlemek için aynı dosyadaki yöntemine bakın ProcessStreamedFile .

Uyarı

Örnek uygulamada görüntülenen doğrulama işleme yöntemleri, karşıya yüklenen dosyaların içeriğini taramaz. Çoğu üretim senaryosunda, dosyayı kullanıcıların veya diğer sistemlerin kullanımına açmadan önce dosyada bir virüs/kötü amaçlı yazılım tarayıcı API'si kullanılır.

Konu örneği doğrulama tekniklerinin çalışan bir örneğini sağlasa da, aşağıdakileri yapmazsanız sınıfı bir üretim uygulamasında uygulamayın FileHelpers :

  • Uygulamayı tam olarak anlayın.
  • Uygulamayı uygulamanın ortamına ve belirtimlerine uygun şekilde değiştirin.

Bu gereksinimleri karşılamadan bir uygulamada güvenlik kodunu hiçbir zaman ayrım gözetmeksizin uygulamayın.

İçerik doğrulama

Karşıya yüklenen içerikte üçüncü taraf bir virüs/kötü amaçlı yazılım tarama API'si kullanın.

Dosyaları taramak, yüksek hacimli senaryolarda sunucu kaynaklarında talepte bulunur. Dosya tarama nedeniyle istek işleme performansı azaldıysa, tarama çalışmasını muhtemelen uygulamanın sunucusundan farklı bir sunucuda çalışan bir hizmet olan bir arka plan hizmetine devredebilirsiniz. Genellikle karşıya yüklenen dosyalar, arka plan virüs tarayıcısı bunları denetleyene kadar karantinaya alınmış bir alanda tutulur. Bir dosya geçtiğinde, dosya normal dosya depolama konumuna taşınır. Bu adımlar genellikle bir dosyanın tarama durumunu gösteren bir veritabanı kaydıyla birlikte gerçekleştirilir. Uygulama ve uygulama sunucusu böyle bir yaklaşım kullanarak isteklere yanıt vermeye odaklanmış durumda kalır.

Dosya uzantısı doğrulama

Karşıya yüklenen dosyanın uzantısı, izin verilen uzantılar listesinde denetlenmelidir. Örnek:

private string[] permittedExtensions = { ".txt", ".pdf" };

var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();

if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
    // The extension is invalid ... discontinue processing the file
}

Dosya imzası doğrulama

Bir dosyanın imzası, dosyanın başındaki ilk birkaç bayt tarafından belirlenir. Bu baytlar, uzantının dosyanın içeriğiyle eşleşip eşleşmediğini belirtmek için kullanılabilir. Örnek uygulama, birkaç yaygın dosya türü için dosya imzalarını denetler. Aşağıdaki örnekte, JPEG görüntüsünün dosya imzası dosyaya göre denetleniyor:

private static readonly Dictionary<string, List<byte[]>> _fileSignature = 
    new Dictionary<string, List<byte[]>>
{
    { ".jpeg", new List<byte[]>
        {
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
        }
    },
};

using (var reader = new BinaryReader(uploadedFileData))
{
    var signatures = _fileSignature[ext];
    var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));

    return signatures.Any(signature => 
        headerBytes.Take(signature.Length).SequenceEqual(signature));
}

Ek dosya imzaları almak için bir dosya imzaları veritabanı (Google arama sonucu) ve resmi dosya belirtimleri kullanın. Danışmanlık resmi dosya belirtimleri, seçilen imzaların geçerli olmasını güvence altına alabilir.

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 için 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, her zaman HtmlEncode kullanıcının isteğinden dosya adı içeriği.

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.

Boyut doğrulama

Karşıya yüklenen dosyaların boyutunu sınırlayın.

Örnek uygulamada, dosyanın boyutu 2 MB ile sınırlıdır (bayt cinsinden gösterilir). Sınır, dosyadan appsettings.json Yapılandırma aracılığıyla sağlanır:

{
  "FileSizeLimit": 2097152
}

FileSizeLimit sınıflara PageModel eklenir:

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

    public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
    {
        _fileSizeLimit = config.GetValue<long>("FileSizeLimit");
    }

    ...
}

Dosya boyutu sınırı aştığında, dosya reddedilir:

if (formFile.Length > _fileSizeLimit)
{
    // The file is too large ... discontinue processing the file
}

Ad özniteliği değerini POST yönteminin parametre adıyla eşleştir

POST form verilerini içeren veya JavaScript'in FormData doğrudan kullandığı olmayanRazor formlarda, formun öğesinde belirtilen ad veya FormData denetleyicinin eylemindeki parametrenin adıyla eşleşmelidir.

Aşağıdaki örnekte:

  • Bir <input> öğe name kullanılırken özniteliği değerine battlePlansayarlanır:

    <input type="file" name="battlePlans" multiple>
    
  • JavaScript'te kullanırken FormData , ad değerine battlePlansayarlanır:

    var formData = new FormData();
    
    for (var file in files) {
      formData.append("battlePlans", file, file.name);
    }
    

C# yöntemininbattlePlans parametresi () için eşleşen bir ad kullanın:

  • adlı Uploadbir Razor Pages sayfa işleyici yöntemi için:

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • MVC POST denetleyicisi eylem yöntemi için:

    public async Task<IActionResult> Post(List<IFormFile> battlePlans)
    

Sunucu ve uygulama yapılandırması

Çok parçalı gövde uzunluğu sınırı

MultipartBodyLengthLimit her çok parçalı gövdenin uzunluğu için sınırı ayarlar. Bu sınırı aşan form bölümleri ayrıştırıldığında bir InvalidDataException oluşturur. Varsayılan değer 134.217.728 'dir (128 MB). içindeki Startup.ConfigureServicesayarını kullanarak MultipartBodyLengthLimit sınırı özelleştirin:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<FormOptions>(options =>
    {
        // Set the limit to 256 MB
        options.MultipartBodyLengthLimit = 268435456;
    });
}

RequestFormLimitsAttribute , tek bir sayfa veya eylem için öğesini ayarlamak MultipartBodyLengthLimit için kullanılır.

Razor Sayfalar uygulamasında, filtreyi içinde Startup.ConfigureServicesbir kuralla uygulayın:

services.AddRazorPages(options =>
{
    options.Conventions
        .AddPageApplicationModelConvention("/FileUploadPage",
            model.Filters.Add(
                new RequestFormLimitsAttribute()
                {
                    // Set the limit to 256 MB
                    MultipartBodyLengthLimit = 268435456
                });
});

Razor Sayfalar uygulamasında veya MVC uygulamasında, filtreyi sayfa modeline veya eylem yöntemine uygulayın:

// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    ...
}

Kestrel maksimum istek gövdesi boyutu

tarafından Kestrelbarındırılan uygulamalar için varsayılan istek gövdesi boyutu üst sınırı 30.000.000 bayttır ve bu da yaklaşık 28,6 MB'tır. MaxRequestBodySizeKestrel sunucusu seçeneğini kullanarak sınırı özelleştirin:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel((context, options) =>
            {
                // Handle requests up to 50 MB
                options.Limits.MaxRequestBodySize = 52428800;
            })
            .UseStartup<Startup>();
        });

RequestSizeLimitAttributetek bir sayfa veya eylem için MaxRequestBodySize ayarlamak için kullanılır.

Razor Sayfalar uygulamasında, filtreyi içinde Startup.ConfigureServicesbir kuralla uygulayın:

services.AddRazorPages(options =>
{
    options.Conventions
        .AddPageApplicationModelConvention("/FileUploadPage",
            model =>
            {
                // Handle requests up to 50 MB
                model.Filters.Add(
                    new RequestSizeLimitAttribute(52428800));
            });
});

Sayfalar Razor uygulamasında veya MVC uygulamasında, filtreyi sayfa işleyici sınıfına veya eylem yöntemine uygulayın:

// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    ...
}

yönergesi RequestSizeLimitAttribute@attributeRazor kullanılarak da uygulanabilir:

@attribute [RequestSizeLimitAttribute(52428800)]

Diğer Kestrel sınırlar

tarafından Kestrelbarındırılan uygulamalar için diğer Kestrel sınırlar geçerli olabilir:

IIS

Varsayılan istek sınırı (maxAllowedContentLength), yaklaşık 28,6 MB olan 30.000.000 bayttır. Dosyadaki sınırı özelleştirin web.config . Aşağıdaki örnekte sınır 50 MB (52.428.800 bayt) olarak ayarlanmıştır:

<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="52428800" />
    </requestFiltering>
  </security>
</system.webServer>

Bu maxAllowedContentLength ayar yalnızca IIS için geçerlidir. Daha fazla bilgi için bkz . İstek Sınırları <requestLimits>.

içinde ayarlayarak IISServerOptions.MaxRequestBodySizeStartup.ConfigureServicesHTTP isteği için en büyük istek gövdesi boyutunu artırın. Aşağıdaki örnekte sınır 50 MB (52.428.800 bayt) olarak ayarlanmıştır:

services.Configure<IISServerOptions>(options =>
{
    options.MaxRequestBodySize = 52428800;
});

Daha fazla bilgi için bkz . IIS ile Windows'ta Konak ASP.NET Core.

Sorun giderme

Dosyaları karşıya yüklerken karşılaşılan bazı yaygın sorunlar ve bunların olası çözümleri aşağıdadır.

IIS sunucusuna dağıtıldığında Bulunamadı hatası

Aşağıdaki hata, karşıya yüklenen dosyanın sunucunun yapılandırılmış içerik uzunluğunu aştığını gösterir:

HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.

Daha fazla bilgi için IIS bölümüne bakın.

Bağlantı hatası

Bağlantı hatası ve sıfırlama sunucusu bağlantısı büyük olasılıkla karşıya yüklenen dosyanın istek gövdesi boyutunun üst sınırını aştığını Kestrelgösterir. Daha fazla bilgi için en fazla istek gövdesi boyutu bölümüne bakın.Kestrel Kestrel istemci bağlantı sınırları da ayarlama gerektirebilir.

IFormFile ile Null Başvuru Özel Durumu

Denetleyici kullanarak IFormFile karşıya yüklenen dosyaları kabul ediyor ancak değeri ise null, HTML formunun değerini enctype belirttiğini multipart/form-dataonaylayın. Bu öznitelik öğesinde <form> ayarlanmadıysa, dosya karşıya yükleme gerçekleşmez ve ilişkili IFormFile bağımsız değişkenler olur null. Ayrıca form verilerindeki karşıya yükleme adlandırmasının uygulamanın adlandırması ile eşleştiğinden emin olun.

Akış çok uzundu

Bu konudaki örnekler, karşıya yüklenen dosyanın içeriğini tutmak için temel MemoryStream alır. boyutunun MemoryStream boyut sınırı şeklindedir int.MaxValue. Uygulamanın dosya yükleme senaryosunda dosya içeriğinin 50 MB'tan büyük tutulması gerekiyorsa, karşıya yüklenen dosyanın içeriğini tutmak için tek MemoryStream bir öğeye dayanmayan alternatif bir yaklaşım kullanın.

ASP.NET Core, daha küçük dosyalar için arabelleğe alınan model bağlamasını kullanarak bir veya daha fazla dosya yüklemeyi ve daha büyük dosyalar için kapsayıcısız akışı destekler.

Örnek kodu görüntüleme veya indirme (indirme)

Güvenlik konuları

Kullanıcılara dosyaları bir sunucuya yükleme olanağı sağlarken dikkatli olun. Saldırganlar şu işlemleri yapmaya çalışabilir:

  • Hizmet reddi saldırılarını yürütür.
  • Virüsleri veya kötü amaçlı yazılımları karşıya yükleyin.
  • Ağların ve sunucuların güvenliğini başka şekillerde aşma.

Başarılı bir saldırı olasılığını azaltan güvenlik adımları şunlardır:

  • Dosyaları, tercihen sistem dışı bir sürücüye ayrılmış bir dosya karşıya yükleme alanına yükleyin. Ayrılmış konum, karşıya yüklenen dosyalara güvenlik kısıtlamaları uygulamayı kolaylaştırır. Dosya karşıya yükleme konumu üzerinde yürütme izinlerini devre dışı bırakın.†
  • Karşıya yüklenen dosyaları app.† ile aynı dizin ağacında kalıcı hale getirme
  • Uygulama tarafından belirlenen güvenli bir dosya adı kullanın. Kullanıcı tarafından sağlanan bir dosya adını veya karşıya yüklenen dosyanın güvenilmeyen dosya adını kullanmayın.† HTML, güvenilmeyen dosya adını görüntülerken kodlar. Örneğin, dosya adını günlüğe kaydetme veya kullanıcı arabiriminde görüntüleme (Razor HTML çıktıyı otomatik olarak kodlar).
  • Uygulamanın tasarım belirtimi için yalnızca onaylı dosya uzantılarına izin ver.†
  • server.† İstemci tarafı denetimlerinin kolayca aşıldığını doğrulayın.
  • Karşıya yüklenen dosyanın boyutunu denetleyin. Büyük karşıya yüklemeleri önlemek için boyut üst sınırı ayarlayın.†
  • Karşıya yüklenen bir dosyanın üzerine aynı ada sahip dosyaların üzerine yazılmaması gerektiğinde, dosyayı karşıya yüklemeden önce dosya adını veritabanına veya fiziksel depolama alanına karşı denetleyin.
  • Dosya depolanmadan önce karşıya yüklenen içerikte bir virüs/kötü amaçlı yazılım tarayıcısı çalıştırın.

† Örnek uygulama, ölçütleri karşılayan bir yaklaşım gösterir.

Uyarı

Kötü amaçlı kodu sisteme yüklemek genellikle aşağıdakiler gibi kod yürütmenin ilk adımıdır:

  • Sistemin kontrolünü tamamen ele geçirin.
  • Sistemin kilitlenmesi sonucuyla sistemi aşırı yükleyin.
  • Kullanıcı veya sistem verilerinin güvenliğini aşma.
  • Genel kullanıcı arabirimine grafiti uygulama.

Kullanıcılardan dosya kabul ederken saldırı yüzeyi alanını azaltma hakkında bilgi için aşağıdaki kaynaklara bakın:

Örnek uygulama örnekleri de dahil olmak üzere güvenlik önlemleri uygulama hakkında daha fazla bilgi için Doğrulama bölümüne bakın.

Depolama senaryoları

Dosyalar için yaygın depolama seçenekleri şunlardır:

  • Veritabanı

    • Küçük dosya yüklemeleri için veritabanı genellikle fiziksel depolama (dosya sistemi veya ağ paylaşımı) seçeneklerinden daha hızlıdır.
    • Kullanıcı verileri için veritabanı kaydının alınması aynı anda dosya içeriğini (örneğin, bir avatar görüntüsü) sağlayabildiği için veritabanı genellikle fiziksel depolama seçeneklerinden daha kullanışlıdır.
    • Veritabanı, veri depolama hizmeti kullanmaktan daha düşük maliyetli olabilir.
  • Fiziksel depolama (dosya sistemi veya ağ paylaşımı)

    • Büyük dosya yüklemeleri için:
      • Veritabanı sınırları karşıya yüklemenin boyutunu kısıtlayabilir.
      • Fiziksel depolama genellikle veritabanındaki depolamadan daha az ekonomiktir.
    • Fiziksel depolama, veri depolama hizmeti kullanmaktan daha düşük maliyetli olabilir.
    • Uygulamanın işleminin depolama konumu için okuma ve yazma izinlerine sahip olması gerekir. Hiçbir zaman yürütme izni verme.
  • Veri depolama hizmeti (örneğin, Azure Blob Depolama)

    • Hizmetler genellikle tek hata noktalarına tabi olan şirket içi çözümlere göre geliştirilmiş ölçeklenebilirlik ve dayanıklılık sunar.
    • Büyük depolama altyapısı senaryolarında hizmetler potansiyel olarak daha düşük maliyetlidir.

    Daha fazla bilgi için bkz . Hızlı Başlangıç: Nesne depolamada blob oluşturmak için .NET kullanma. Konu başlığında gösterilmektedirUploadFromFileAsync, ancak UploadFromStreamAsync ile Streamçalışırken blob depolama alanına kaydetmek FileStream için kullanılabilir.

Dosya yükleme senaryoları

Dosyaları karşıya yüklemeye yönelik iki genel yaklaşım arabelleğe alma ve akıştır.

Tamponlama

Dosyanın tamamı, dosyasını işlemek veya kaydetmek için kullanılan dosyanın C# gösterimi olan dosyasında IFormFileokunur.

Dosya yüklemeleri tarafından kullanılan kaynaklar (disk, bellek), eşzamanlı dosya yüklemelerinin sayısına ve boyutuna bağlıdır. Bir uygulama çok fazla karşıya yüklemeyi arabelleğe almayı denerse, bellek veya disk alanı bittiğinde site kilitlenir. Dosya yüklemelerinin boyutu veya sıklığı uygulama kaynaklarını tüketiyorsa akışı kullanın.

Dekont

64 KB'yi aşan tek bir arabelleğe alınan dosya bellekten disk üzerindeki geçici bir dosyaya taşınır.

Küçük dosyaları arabelleğe alma, bu konunun aşağıdaki bölümlerinde ele alınmıştır:

Akışlar

Dosya çok parçalı bir istekten alınır ve uygulama tarafından doğrudan işlenir veya kaydedilir. Akış, performansı önemli ölçüde geliştirmez. Akış, dosyaları karşıya yüklerken bellek veya disk alanı taleplerini azaltır.

Büyük dosyaları akışla aktarma, Büyük dosyaları akışla karşıya yükleme bölümünde ele alınmıştır.

Arabelleğe alınan model bağlaması olan küçük dosyaları fiziksel depolamaya yükleme

Küçük dosyaları karşıya yüklemek için çok parçalı bir form kullanın veya JavaScript kullanarak bir POST isteği oluşturun.

Aşağıdaki örnekte, tek bir Razor dosyayı (Pages/BufferedSingleFileUploadPhysical.cshtml örnek uygulamada) karşıya yüklemek için Sayfalar formunun kullanımı gösterilmektedir:

<form enctype="multipart/form-data" method="post">
    <dl>
        <dt>
            <label asp-for="FileUpload.FormFile"></label>
        </dt>
        <dd>
            <input asp-for="FileUpload.FormFile" type="file">
            <span asp-validation-for="FileUpload.FormFile"></span>
        </dd>
    </dl>
    <input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>

Aşağıdaki örnek, aşağıdakiler dışında önceki örne benzerdir:

  • Formun verilerini göndermek için JavaScript'in (Fetch API'si) kullanılır.
  • Doğrulama yok.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload" 
      enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;" 
      method="post">
    <dl>
        <dt>
            <label for="FileUpload_FormFile">File</label>
        </dt>
        <dd>
            <input id="FileUpload_FormFile" type="file" 
                name="FileUpload.FormFile" />
        </dd>
    </dl>

    <input class="btn" type="submit" value="Upload" />

    <div style="margin-top:15px">
        <output name="result"></output>
    </div>
</form>

<script>
  async function AJAXSubmit (oFormElement) {
    var resultElement = oFormElement.elements.namedItem("result");
    const formData = new FormData(oFormElement);

    try {
    const response = await fetch(oFormElement.action, {
      method: 'POST',
      body: formData
    });

    if (response.ok) {
      window.location.href = '/';
    }

    resultElement.value = 'Result: ' + response.status + ' ' + 
      response.statusText;
    } catch (error) {
      console.error('Error:', error);
    }
  }
</script>

Fetch API'sini desteklemeyen istemciler için JavaScript'te POST formunu gerçekleştirmek için aşağıdaki yaklaşımlardan birini kullanın:

  • Bir Fetch Polyfill (örneğin, window.fetch polyfill (github/fetch)) kullanın.

  • XMLHttpRequest adresini kullanın. Örnek:

    <script>
      "use strict";
    
      function AJAXSubmit (oFormElement) {
        var oReq = new XMLHttpRequest();
        oReq.onload = function(e) { 
        oFormElement.elements.namedItem("result").value = 
          'Result: ' + this.status + ' ' + this.statusText;
        };
        oReq.open("post", oFormElement.action);
        oReq.send(new FormData(oFormElement));
      }
    </script>
    

Dosya yüklemelerini desteklemek için HTML formlarının kodlama türünü (enctype) belirtmesi multipart/form-datagerekir.

files Bir giriş öğesinin birden çok dosya yüklemeyi desteklemesi multiple için öğesinde özniteliğini <input> sağlayın:

<input asp-for="FileUpload.FormFiles" type="file" multiple>

Sunucuya yüklenen dosyalara kullanılarak Model BağlamaIFormFilearacılığıyla erişilebilir. Örnek uygulama, veritabanı ve fiziksel depolama senaryoları için birden çok arabelleğe alınan dosya yüklemesini gösterir.

Uyarı

görüntüleme ve günlüğe FileName kaydetme için dışında özelliğini IFormFile kullanmayın. Görüntülerken veya günlüğe kaydetme sırasında, HTML dosya adını kodlar. Saldırgan, tam yollar veya göreli yollar dahil olmak üzere kötü amaçlı bir dosya adı sağlayabilir. Uygulamalar:

  • Kullanıcı tarafından sağlanan dosya adından yolu kaldırın.
  • KULLANıCı arabirimi veya günlük kaydı için HTML ile kodlanmış, yolu kaldırılmış dosya adını kaydedin.
  • Depolama için yeni bir rastgele dosya adı oluşturun.

Aşağıdaki kod dosya adından yolu kaldırır:

string untrustedFileName = Path.GetFileName(pathName);

Şu ana kadar verilen örnekler güvenlikle ilgili dikkate alınmaz. Aşağıdaki bölümler ve örnek uygulama tarafından ek bilgiler sağlanır:

ve model bağlamasını IFormFilekullanarak dosyaları karşıya yüklerken eylem yöntemi şunları kabul edebilir:

Dekont

Bağlama, form dosyalarını ada göre eşleştirir. Örneğin, içindeki <input type="file" name="formFile"> HTML name değeri C# parametresi/özellik sınırı (FormFile) ile eşleşmelidir. Daha fazla bilgi için POST yönteminin parametre adıyla ad özniteliği değerini eşleştirme bölümüne bakın.

Aşağıdaki örnek:

  • Karşıya yüklenen bir veya daha fazla dosyada döngüler oluşturur.
  • Path.GetTempFileName kullanarak dosya adı da dahil olmak üzere bir dosyanın tam yolunu döndürür.
  • Uygulama tarafından oluşturulan bir dosya adını kullanarak dosyaları yerel dosya sistemine kaydeder.
  • Karşıya yüklenen dosyaların toplam sayısını ve boyutunu döndürür.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
    long size = files.Sum(f => f.Length);

    foreach (var formFile in files)
    {
        if (formFile.Length > 0)
        {
            var filePath = Path.GetTempFileName();

            using (var stream = System.IO.File.Create(filePath))
            {
                await formFile.CopyToAsync(stream);
            }
        }
    }

    // Process uploaded files
    // Don't rely on or trust the FileName property without validation.

    return Ok(new { count = files.Count, size });
}

Yolu olmayan bir dosya adı oluşturmak için kullanın Path.GetRandomFileName . Aşağıdaki örnekte, yol yapılandırmadan elde edilir:

foreach (var formFile in files)
{
    if (formFile.Length > 0)
    {
        var filePath = Path.Combine(_config["StoredFilesPath"], 
            Path.GetRandomFileName());

        using (var stream = System.IO.File.Create(filePath))
        {
            await formFile.CopyToAsync(stream);
        }
    }
}

öğesine FileStream geçirilen yol dosya adını içermelidir. Dosya adı sağlanmazsa, çalışma zamanında bir UnauthorizedAccessException oluşturulur.

Tekniği kullanılarak IFormFile karşıya yüklenen dosyalar işlenmeden önce bellekte veya sunucudaki diskte arabelleğe alınıyor. Eylem yönteminin içinde içeriğine IFormFile olarak Streamerişilebilir. Yerel dosya sistemine ek olarak, dosyalar bir ağ paylaşımına veya Azure Blob depolama gibi bir dosya depolama hizmetine kaydedilebilir.

Karşıya yüklemek üzere birden çok dosya üzerinde döngüler oluşturan ve güvenli dosya adlarını kullanan başka bir örnek için örnek uygulamadaki bölümüne bakın Pages/BufferedMultipleFileUploadPhysical.cshtml.cs .

Uyarı

Path.GetTempFileName , önceki geçici dosyalar silinmeden 65.535'ten fazla dosya oluşturulursa bir IOException oluşturur. 65.535 dosya sınırı sunucu başına bir sınırdır. Windows işletim sistemindeki bu sınır hakkında daha fazla bilgi için aşağıdaki konulardaki açıklamalara bakın:

Arabelleğe alınan model bağlaması olan küçük dosyaları veritabanına yükleme

Entity Framework kullanarak ikili dosya verilerini veritabanında depolamak için varlıkta bir Byte dizi özelliği tanımlayın:

public class AppFile
{
    public int Id { get; set; }
    public byte[] Content { get; set; }
}

öğesini içeren sınıfı için bir IFormFilesayfa modeli özelliği belirtin:

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

    [BindProperty]
    public BufferedSingleFileUploadDb FileUpload { get; set; }

    ...
}

public class BufferedSingleFileUploadDb
{
    [Required]
    [Display(Name="File")]
    public IFormFile FormFile { get; set; }
}

Dekont

IFormFile doğrudan bir eylem yöntemi parametresi veya bağlı model özelliği olarak kullanılabilir. Önceki örnekte ilişkili model özelliği kullanılır.

FileUpload, Sayfalar formunda kullanılırRazor:

<form enctype="multipart/form-data" method="post">
    <dl>
        <dt>
            <label asp-for="FileUpload.FormFile"></label>
        </dt>
        <dd>
            <input asp-for="FileUpload.FormFile" type="file">
        </dd>
    </dl>
    <input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>

Form sunucuya POSTed olduğunda, öğesini bir akışa kopyalayın IFormFile ve veritabanında bayt dizisi olarak kaydedin. Aşağıdaki örnekte, _dbContext uygulamanın veritabanı bağlamını depolar:

public async Task<IActionResult> OnPostUploadAsync()
{
    using (var memoryStream = new MemoryStream())
    {
        await FileUpload.FormFile.CopyToAsync(memoryStream);

        // Upload the file if less than 2 MB
        if (memoryStream.Length < 2097152)
        {
            var file = new AppFile()
            {
                Content = memoryStream.ToArray()
            };

            _dbContext.File.Add(file);

            await _dbContext.SaveChangesAsync();
        }
        else
        {
            ModelState.AddModelError("File", "The file is too large.");
        }
    }

    return Page();
}

Yukarıdaki örnek, örnek uygulamada gösterildiği gibi bir senaryoya benzer:

  • Pages/BufferedSingleFileUploadDb.cshtml
  • Pages/BufferedSingleFileUploadDb.cshtml.cs

Uyarı

İkili verileri ilişkisel veritabanlarında depolarken performansı olumsuz etkileyene kadar dikkatli olun.

Doğrulama olmadan özelliğine güvenmeyin veya özelliğine FileNameIFormFile güvenmeyin. FileName özelliği yalnızca görüntüleme amacıyla ve yalnızca HTML kodlamadan sonra kullanılmalıdır.

Sağlanan örneklerde güvenlik konuları dikkate alınmaz. Aşağıdaki bölümler ve örnek uygulama tarafından ek bilgiler sağlanır:

Akışla büyük dosyaları karşıya yükleme

Aşağıdaki örnekte, bir dosyayı denetleyici eylemine akışla göndermek için JavaScript'in nasıl kullanılacağı gösterilmektedir. Dosyanın kötü amaçlı yazılımdan koruma belirteci özel bir filtre özniteliği kullanılarak oluşturulur ve istek gövdesi yerine istemci HTTP üst bilgilerine geçirilir. Eylem yöntemi karşıya yüklenen verileri doğrudan işlediğinden, form modeli bağlaması başka bir özel filtre tarafından devre dışı bırakılır. Eylemin içinde formun içeriği, her bir dosyayı MultipartSectionokuyan, dosyayı işleyen veya içeriği uygun şekilde depolayan bir MultipartReaderkullanılarak okunur. Çok bölümlü bölümler okunduktan sonra eylem kendi model bağlamasını gerçekleştirir.

İlk sayfa yanıtı formu yükler ve bir kötü amaçlı yazılımdan koruma belirtecini GenerateAntiforgeryTokenCookieAttribute (cookieözniteliği aracılığıyla) kaydeder. özniteliği, bir istek belirteci ile ayarlamak cookie için ASP.NET Core'un yerleşik kötü amaçlı yazılımdan koruma desteğini kullanır:

public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();

        // Send the request token as a JavaScript-readable cookie
        var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);

        context.HttpContext.Response.Cookies.Append(
            "RequestVerificationToken",
            tokens.RequestToken,
            new CookieOptions() { HttpOnly = false });
    }

    public override void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

DisableFormValueModelBindingAttribute, model bağlamasını devre dışı bırakmak için kullanılır:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        var factories = context.ValueProviderFactories;
        factories.RemoveType<FormValueProviderFactory>();
        factories.RemoveType<JQueryFormValueProviderFactory>();
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

Örnek uygulamada GenerateAntiforgeryTokenCookieAttribute ve Sayfa kuralları kullanılarak Razor ve sayfa uygulaması modellerine Startup.ConfigureServices/StreamedSingleFileUploadDb/StreamedSingleFileUploadPhysical filtre olarak uygulanır:DisableFormValueModelBindingAttribute

services.AddMvc()
    .AddRazorPagesOptions(options =>
        {
            options.Conventions
                .AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
                    model =>
                    {
                        model.Filters.Add(
                            new GenerateAntiforgeryTokenCookieAttribute());
                        model.Filters.Add(
                            new DisableFormValueModelBindingAttribute());
                    });
            options.Conventions
                .AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
                    model =>
                    {
                        model.Filters.Add(
                            new GenerateAntiforgeryTokenCookieAttribute());
                        model.Filters.Add(
                            new DisableFormValueModelBindingAttribute());
                    });
        })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Model bağlaması formu okumadığından, formdan bağlanan parametreler bağlanmaz (sorgu, yol ve üst bilgi çalışmaya devam eder). Eylem yöntemi doğrudan özelliğiyle Request çalışır. A MultipartReader , her bölümü okumak için kullanılır. Anahtar/değer verileri içinde KeyValueAccumulatordepolanır. Çok bölümlü bölümler okunduktan sonra, KeyValueAccumulator içindekiler form verilerini bir model türüne bağlamak için kullanılır.

ile EF Corebir veritabanına akış yapmak için tam StreamingController.UploadDatabase yöntem:

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    // Accumulate the form data key-value pairs in the request (formAccumulator).
    var formAccumulator = new KeyValueAccumulator();
    var trustedFileNameForDisplay = string.Empty;
    var untrustedFileNameForStorage = string.Empty;
    var streamedFileContent = Array.Empty<byte>();

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);

    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            if (MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                untrustedFileNameForStorage = contentDisposition.FileName.Value;
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);

                streamedFileContent = 
                    await FileHelpers.ProcessStreamedFile(section, contentDisposition, 
                        ModelState, _permittedExtensions, _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }
            }
            else if (MultipartRequestHelper
                .HasFormDataContentDisposition(contentDisposition))
            {
                // Don't limit the key name length because the 
                // multipart headers length limit is already in effect.
                var key = HeaderUtilities
                    .RemoveQuotes(contentDisposition.Name).Value;
                var encoding = GetEncoding(section);

                if (encoding == null)
                {
                    ModelState.AddModelError("File", 
                        $"The request couldn't be processed (Error 2).");
                    // Log error

                    return BadRequest(ModelState);
                }

                using (var streamReader = new StreamReader(
                    section.Body,
                    encoding,
                    detectEncodingFromByteOrderMarks: true,
                    bufferSize: 1024,
                    leaveOpen: true))
                {
                    // The value length limit is enforced by 
                    // MultipartBodyLengthLimit
                    var value = await streamReader.ReadToEndAsync();

                    if (string.Equals(value, "undefined", 
                        StringComparison.OrdinalIgnoreCase))
                    {
                        value = string.Empty;
                    }

                    formAccumulator.Append(key, value);

                    if (formAccumulator.ValueCount > 
                        _defaultFormOptions.ValueCountLimit)
                    {
                        // Form key count limit of 
                        // _defaultFormOptions.ValueCountLimit 
                        // is exceeded.
                        ModelState.AddModelError("File", 
                            $"The request couldn't be processed (Error 3).");
                        // Log error

                        return BadRequest(ModelState);
                    }
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    // Bind form data to the model
    var formData = new FormData();
    var formValueProvider = new FormValueProvider(
        BindingSource.Form,
        new FormCollection(formAccumulator.GetResults()),
        CultureInfo.CurrentCulture);
    var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
        valueProvider: formValueProvider);

    if (!bindingSuccessful)
    {
        ModelState.AddModelError("File", 
            "The request couldn't be processed (Error 5).");
        // Log error

        return BadRequest(ModelState);
    }

    // **WARNING!**
    // In the following example, the file is saved without
    // scanning the file's contents. In most production
    // scenarios, an anti-virus/anti-malware scanner API
    // is used on the file before making the file available
    // for download or for use by other systems. 
    // For more information, see the topic that accompanies 
    // this sample app.

    var file = new AppFile()
    {
        Content = streamedFileContent,
        UntrustedName = untrustedFileNameForStorage,
        Note = formData.Note,
        Size = streamedFileContent.Length, 
        UploadDT = DateTime.UtcNow
    };

    _context.File.Add(file);
    await _context.SaveChangesAsync();

    return Created(nameof(StreamingController), null);
}

MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):

using System;
using System.IO;
using Microsoft.Net.Http.Headers;

namespace SampleApp.Utilities
{
    public static class MultipartRequestHelper
    {
        // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
        // The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
        public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
        {
            var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;

            if (string.IsNullOrWhiteSpace(boundary))
            {
                throw new InvalidDataException("Missing content-type boundary.");
            }

            if (boundary.Length > lengthLimit)
            {
                throw new InvalidDataException(
                    $"Multipart boundary length limit {lengthLimit} exceeded.");
            }

            return boundary;
        }

        public static bool IsMultipartContentType(string contentType)
        {
            return !string.IsNullOrEmpty(contentType)
                   && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
        }

        public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="key";
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && string.IsNullOrEmpty(contentDisposition.FileName.Value)
                && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
        }

        public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
                    || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
        }
    }
}

Fiziksel bir konuma akış için tam StreamingController.UploadPhysical yöntem:

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);
    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            // This check assumes that there's a file
            // present without form data. If form data
            // is present, this method immediately fails
            // and returns the model error.
            if (!MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                ModelState.AddModelError("File", 
                    $"The request couldn't be processed (Error 2).");
                // Log error

                return BadRequest(ModelState);
            }
            else
            {
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                var trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);
                var trustedFileNameForFileStorage = Path.GetRandomFileName();

                // **WARNING!**
                // In the following example, the file is saved without
                // scanning the file's contents. In most production
                // scenarios, an anti-virus/anti-malware scanner API
                // is used on the file before making the file available
                // for download or for use by other systems. 
                // For more information, see the topic that accompanies 
                // this sample.

                var streamedFileContent = await FileHelpers.ProcessStreamedFile(
                    section, contentDisposition, ModelState, 
                    _permittedExtensions, _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }

                using (var targetStream = System.IO.File.Create(
                    Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
                {
                    await targetStream.WriteAsync(streamedFileContent);

                    _logger.LogInformation(
                        "Uploaded file '{TrustedFileNameForDisplay}' saved to " +
                        "'{TargetFilePath}' as {TrustedFileNameForFileStorage}", 
                        trustedFileNameForDisplay, _targetFilePath, 
                        trustedFileNameForFileStorage);
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    return Created(nameof(StreamingController), null);
}

Örnek uygulamada doğrulama denetimleri tarafından FileHelpers.ProcessStreamedFileişlenir.

Doğrulama

Örnek uygulamanın FileHelpers sınıfı, arabelleğe alınan ve akışa alınan IFormFile dosya yüklemeleri için çeşitli denetimler gösterir. Örnek uygulamada arabelleğe alınan dosya yüklemelerini işlemek IFormFile için dosyadaki ProcessFormFile yöntemine Utilities/FileHelpers.cs bakın. Akışlı dosyaları işlemek için aynı dosyadaki yöntemine bakın ProcessStreamedFile .

Uyarı

Örnek uygulamada görüntülenen doğrulama işleme yöntemleri, karşıya yüklenen dosyaların içeriğini taramaz. Çoğu üretim senaryosunda, dosyayı kullanıcıların veya diğer sistemlerin kullanımına açmadan önce dosyada bir virüs/kötü amaçlı yazılım tarayıcı API'si kullanılır.

Konu örneği doğrulama tekniklerinin çalışan bir örneğini sağlasa da, aşağıdakileri yapmazsanız sınıfı bir üretim uygulamasında uygulamayın FileHelpers :

  • Uygulamayı tam olarak anlayın.
  • Uygulamayı uygulamanın ortamına ve belirtimlerine uygun şekilde değiştirin.

Bu gereksinimleri karşılamadan bir uygulamada güvenlik kodunu hiçbir zaman ayrım gözetmeksizin uygulamayın.

İçerik doğrulama

Karşıya yüklenen içerikte üçüncü taraf bir virüs/kötü amaçlı yazılım tarama API'si kullanın.

Dosyaları taramak, yüksek hacimli senaryolarda sunucu kaynaklarında talepte bulunur. Dosya tarama nedeniyle istek işleme performansı azaldıysa, tarama çalışmasını muhtemelen uygulamanın sunucusundan farklı bir sunucuda çalışan bir hizmet olan bir arka plan hizmetine devredebilirsiniz. Genellikle karşıya yüklenen dosyalar, arka plan virüs tarayıcısı bunları denetleyene kadar karantinaya alınmış bir alanda tutulur. Bir dosya geçtiğinde, dosya normal dosya depolama konumuna taşınır. Bu adımlar genellikle bir dosyanın tarama durumunu gösteren bir veritabanı kaydıyla birlikte gerçekleştirilir. Uygulama ve uygulama sunucusu böyle bir yaklaşım kullanarak isteklere yanıt vermeye odaklanmış durumda kalır.

Dosya uzantısı doğrulama

Karşıya yüklenen dosyanın uzantısı, izin verilen uzantılar listesinde denetlenmelidir. Örnek:

private string[] permittedExtensions = { ".txt", ".pdf" };

var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();

if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
    // The extension is invalid ... discontinue processing the file
}

Dosya imzası doğrulama

Bir dosyanın imzası, dosyanın başındaki ilk birkaç bayt tarafından belirlenir. Bu baytlar, uzantının dosyanın içeriğiyle eşleşip eşleşmediğini belirtmek için kullanılabilir. Örnek uygulama, birkaç yaygın dosya türü için dosya imzalarını denetler. Aşağıdaki örnekte, JPEG görüntüsünün dosya imzası dosyaya göre denetleniyor:

private static readonly Dictionary<string, List<byte[]>> _fileSignature = 
    new Dictionary<string, List<byte[]>>
{
    { ".jpeg", new List<byte[]>
        {
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
        }
    },
};

using (var reader = new BinaryReader(uploadedFileData))
{
    var signatures = _fileSignature[ext];
    var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));

    return signatures.Any(signature => 
        headerBytes.Take(signature.Length).SequenceEqual(signature));
}

Ek dosya imzaları almak için bir dosya imzaları veritabanı (Google arama sonucu) ve resmi dosya belirtimleri kullanın. Danışmanlık resmi dosya belirtimleri, seçilen imzaların geçerli olmasını güvence altına alabilir.

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 için 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, her zaman HtmlEncode kullanıcının isteğinden dosya adı içeriği.

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.

Boyut doğrulama

Karşıya yüklenen dosyaların boyutunu sınırlayın.

Örnek uygulamada, dosyanın boyutu 2 MB ile sınırlıdır (bayt cinsinden gösterilir). Sınır, dosyadan appsettings.json Yapılandırma aracılığıyla sağlanır:

{
  "FileSizeLimit": 2097152
}

FileSizeLimit sınıflara PageModel eklenir:

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

    public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
    {
        _fileSizeLimit = config.GetValue<long>("FileSizeLimit");
    }

    ...
}

Dosya boyutu sınırı aştığında, dosya reddedilir:

if (formFile.Length > _fileSizeLimit)
{
    // The file is too large ... discontinue processing the file
}

Ad özniteliği değerini POST yönteminin parametre adıyla eşleştir

POST form verilerini içeren veya JavaScript'in FormData doğrudan kullandığı olmayanRazor formlarda, formun öğesinde belirtilen ad veya FormData denetleyicinin eylemindeki parametrenin adıyla eşleşmelidir.

Aşağıdaki örnekte:

  • Bir <input> öğe name kullanılırken özniteliği değerine battlePlansayarlanır:

    <input type="file" name="battlePlans" multiple>
    
  • JavaScript'te kullanırken FormData , ad değerine battlePlansayarlanır:

    var formData = new FormData();
    
    for (var file in files) {
      formData.append("battlePlans", file, file.name);
    }
    

C# yöntemininbattlePlans parametresi () için eşleşen bir ad kullanın:

  • adlı Uploadbir Razor Pages sayfa işleyici yöntemi için:

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • MVC POST denetleyicisi eylem yöntemi için:

    public async Task<IActionResult> Post(List<IFormFile> battlePlans)
    

Sunucu ve uygulama yapılandırması

Çok parçalı gövde uzunluğu sınırı

MultipartBodyLengthLimit her çok parçalı gövdenin uzunluğu için sınırı ayarlar. Bu sınırı aşan form bölümleri ayrıştırıldığında bir InvalidDataException oluşturur. Varsayılan değer 134.217.728 'dir (128 MB). içindeki Startup.ConfigureServicesayarını kullanarak MultipartBodyLengthLimit sınırı özelleştirin:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<FormOptions>(options =>
    {
        // Set the limit to 256 MB
        options.MultipartBodyLengthLimit = 268435456;
    });
}

RequestFormLimitsAttribute , tek bir sayfa veya eylem için öğesini ayarlamak MultipartBodyLengthLimit için kullanılır.

Razor Sayfalar uygulamasında, filtreyi içinde Startup.ConfigureServicesbir kuralla uygulayın:

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        options.Conventions
            .AddPageApplicationModelConvention("/FileUploadPage",
                model.Filters.Add(
                    new RequestFormLimitsAttribute()
                    {
                        // Set the limit to 256 MB
                        MultipartBodyLengthLimit = 268435456
                    });
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Razor Sayfalar uygulamasında veya MVC uygulamasında, filtreyi sayfa modeline veya eylem yöntemine uygulayın:

// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    ...
}

Kestrel maksimum istek gövdesi boyutu

tarafından Kestrelbarındırılan uygulamalar için varsayılan istek gövdesi boyutu üst sınırı 30.000.000 bayttır ve bu da yaklaşık 28,6 MB'tır. MaxRequestBodySizeKestrel sunucusu seçeneğini kullanarak sınırı özelleştirin:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel((context, options) =>
        {
            // Handle requests up to 50 MB
            options.Limits.MaxRequestBodySize = 52428800;
        });

RequestSizeLimitAttributetek bir sayfa veya eylem için MaxRequestBodySize ayarlamak için kullanılır.

Razor Sayfalar uygulamasında, filtreyi içinde Startup.ConfigureServicesbir kuralla uygulayın:

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        options.Conventions
            .AddPageApplicationModelConvention("/FileUploadPage",
                model =>
                {
                    // Handle requests up to 50 MB
                    model.Filters.Add(
                        new RequestSizeLimitAttribute(52428800));
                });
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Sayfalar Razor uygulamasında veya MVC uygulamasında, filtreyi sayfa işleyici sınıfına veya eylem yöntemine uygulayın:

// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    ...
}

Diğer Kestrel sınırlar

tarafından Kestrelbarındırılan uygulamalar için diğer Kestrel sınırlar geçerli olabilir:

IIS

Varsayılan istek sınırı (maxAllowedContentLength), yaklaşık 28,6 MB olan 30.000.000 bayttır. Dosyadaki sınırı özelleştirin web.config . Aşağıdaki örnekte sınır 50 MB (52.428.800 bayt) olarak ayarlanmıştır:

<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="52428800" />
    </requestFiltering>
  </security>
</system.webServer>

Bu maxAllowedContentLength ayar yalnızca IIS için geçerlidir. Daha fazla bilgi için bkz . İstek Sınırları <requestLimits>.

içinde ayarlayarak IISServerOptions.MaxRequestBodySizeStartup.ConfigureServicesHTTP isteği için en büyük istek gövdesi boyutunu artırın. Aşağıdaki örnekte sınır 50 MB (52.428.800 bayt) olarak ayarlanmıştır:

services.Configure<IISServerOptions>(options =>
{
    options.MaxRequestBodySize = 52428800;
});

Daha fazla bilgi için bkz . IIS ile Windows'ta Konak ASP.NET Core.

Sorun giderme

Dosyaları karşıya yüklerken karşılaşılan bazı yaygın sorunlar ve bunların olası çözümleri aşağıdadır.

IIS sunucusuna dağıtıldığında Bulunamadı hatası

Aşağıdaki hata, karşıya yüklenen dosyanın sunucunun yapılandırılmış içerik uzunluğunu aştığını gösterir:

HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.

Daha fazla bilgi için IIS bölümüne bakın.

Bağlantı hatası

Bağlantı hatası ve sıfırlama sunucusu bağlantısı büyük olasılıkla karşıya yüklenen dosyanın istek gövdesi boyutunun üst sınırını aştığını Kestrelgösterir. Daha fazla bilgi için en fazla istek gövdesi boyutu bölümüne bakın.Kestrel Kestrel istemci bağlantı sınırları da ayarlama gerektirebilir.

IFormFile ile Null Başvuru Özel Durumu

Denetleyici kullanarak IFormFile karşıya yüklenen dosyaları kabul ediyor ancak değeri ise null, HTML formunun değerini enctype belirttiğini multipart/form-dataonaylayın. Bu öznitelik öğesinde <form> ayarlanmadıysa, dosya karşıya yükleme gerçekleşmez ve ilişkili IFormFile bağımsız değişkenler olur null. Ayrıca form verilerindeki karşıya yükleme adlandırmasının uygulamanın adlandırması ile eşleştiğinden emin olun.

Akış çok uzundu

Bu konudaki örnekler, karşıya yüklenen dosyanın içeriğini tutmak için temel MemoryStream alır. boyutunun MemoryStream boyut sınırı şeklindedir int.MaxValue. Uygulamanın dosya yükleme senaryosunda dosya içeriğinin 50 MB'tan büyük tutulması gerekiyorsa, karşıya yüklenen dosyanın içeriğini tutmak için tek MemoryStream bir öğeye dayanmayan alternatif bir yaklaşım kullanın.

Ek kaynaklar