Charger des fichiers dans ASP.NET Core

Par Rutger Storm

ASP.NET Core prend en charge le téléchargement d'un ou de plusieurs fichiers à l'aide de la liaison de modèle tamponnée pour les petits fichiers et de la diffusion en continu non tamponnée pour les fichiers plus volumineux.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Considérations relatives à la sécurité

Soyez vigilant lorsque vous fournissez aux utilisateurs la possibilité de charger des fichiers sur un serveur. Les attaquants peuvent tenter de :

  • Exécuter des attaques par déni de service.
  • Charger des virus ou des logiciels malveillants.
  • Compromettre les réseaux et les serveurs d’autres manières.

Les étapes de sécurité qui réduisent la probabilité d’une attaque réussie sont les suivantes :

  • Charger des fichiers dans une zone de chargement de fichiers dédiée, de préférence sur un lecteur non système. Un emplacement dédié facilite l’imposition de restrictions de sécurité sur les fichiers chargés. Désactivez les autorisations d’exécution sur l’emplacement de chargement de fichier.†
  • Ne conservez pas les fichiers chargés dans la même arborescence que celle de l’application.†
  • Utilisez un nom de fichier sécurisé déterminé par l’application. N’utilisez pas de nom de fichier fourni par l’utilisateur ou le nom de fichier non approuvé du fichier chargé.† code HTML le nom de fichier non approuvé lors de son affichage. Par exemple, la journalisation du nom du fichier ou l’affichage dans l’interface utilisateur (Razor code HTML encode automatiquement la sortie).
  • Autorisez uniquement les extensions de fichier approuvées pour la spécification de conception de l’application.†
  • Vérifiez que les vérifications côté client sont effectuées sur le serveur.† Les vérifications côté client sont faciles à contourner.
  • Vérifiez la taille d’un fichier chargé. Définissez une limite de taille maximale pour empêcher les chargements volumineux.†
  • Lorsque les fichiers ne doivent pas être remplacés par un fichier chargé portant le même nom, case activée le nom du fichier par rapport à la base de données ou au stockage physique avant de charger le fichier.
  • Exécutez une analyse de virus/programmes malveillants sur le contenu chargé avant que le fichier ne soit stocké.

†L’exemple d’application illustre une approche qui répond aux critères.

Avertissement

Le chargement d’un code malveillant sur un système est généralement la première étape de l’exécution de code capable de :

  • Prendre complètement le contrôle d’un système.
  • Surcharger un système, ce qui a pour effet de le faire tomber en panne.
  • Compromettre les données utilisateur ou système.
  • Appliquer un graffiti sur une interface utilisateur publique.

Pour plus d’informations sur la réduction de la surface d’attaque quand vous acceptez des fichiers d’utilisateurs, consultez les ressources suivantes :

Pour plus d’informations sur l’implémentation de mesures de sécurité, notamment des exemples de l’exemple d’application, consultez la section Validation.

Scénarios de stockage

Les options de stockage courantes pour les fichiers sont les suivantes :

  • Base de données

    • Pour les petits chargements de fichiers, une base de données est souvent plus rapide que les options de stockage physique (système de fichiers ou partage réseau).
    • Une base de données est souvent plus pratique que les options de stockage physique, car la récupération d’un enregistrement de base de données pour les données utilisateur peut fournir simultanément le contenu du fichier (par exemple, une image d’avatar).
    • Une base de données est potentiellement moins coûteuse que l’utilisation d’un service de stockage de données cloud.
  • Stockage physique (système de fichiers ou partage réseau)

    • Pour les chargements de fichiers volumineux :
      • Les limites de base de données peuvent restreindre la taille du chargement.
      • Le stockage physique est souvent moins économique que le stockage dans une base de données.
    • Un stockage physique est potentiellement moins coûteux que l’utilisation d’un service de stockage de données cloud.
    • Le processus de l’application doit avoir des autorisations de lecture et d’écriture sur l’emplacement de stockage. N’accordez jamais l’autorisation d’exécution.
  • Service de stockage de données cloud, par exemple, Stockage Blob Azure.

    • Les services offrent généralement une scalabilité et une résilience améliorées par rapport aux solutions locales qui sont généralement soumises à des points de défaillance uniques.
    • Les services sont potentiellement moins coûteux dans les scénarios d’infrastructure de stockage volumineux.

    Pour plus d’informations, consultez Démarrage rapide : Utiliser .NET pour créer un objet blob dans le stockage d’objets.

Fichiers petits et grands

La définition des fichiers petits et grands dépend des ressources informatiques disponibles. Les applications doivent évaluer l’approche de stockage utilisée pour s’assurer qu’elles peuvent gérer les tailles attendues. Performances de la mémoire, du processeur, du disque et de la base de données de référence.

Bien que des limites spécifiques ne puissent pas être fournies sur ce qui est petit ou grand pour votre déploiement, voici quelques-unes des valeurs par défaut associées d’AspNetCore pour FormOptions :

  • Par défaut, HttpRequest.Form ne met pas en mémoire tampon l’ensemble du corps de la requête (BufferBody), mais il met en mémoire tampon tous les fichiers de formulaire multipart inclus.
  • MultipartBodyLengthLimit est la taille maximale des fichiers de formulaire mis en mémoire tampon, par défaut à 128 Mo.
  • MemoryBufferThreshold indique la quantité de fichiers à mettre en mémoire tampon avant la transition vers un fichier de mémoire tampon sur le disque, la valeur par défaut est de 64 Ko. MemoryBufferThreshold sert de limite entre les fichiers de petite et grande taille, qui est levée ou abaissée en fonction des ressources et scénarios des applications.

Pour plus d’informations sur FormOptions, consultez le code source.

Scénarios de chargement de fichiers

Deux approches générales pour le chargement de fichiers sont la mise en mémoire tampon et la diffusion en continu.

des réponses

Le fichier entier est lu dans un IFormFile. IFormFile est une représentation C# du fichier utilisé pour traiter ou enregistrer le fichier.

Le disque et la mémoire utilisés par les téléchargements de fichiers dépendent du nombre et de la taille des téléchargements de fichiers simultanés. Si une application tente de mettre en mémoire tampon un trop grand nombre de téléchargements, le site tombe en panne lorsqu'il manque de mémoire ou d'espace disque. Si la taille ou la fréquence des chargements de fichiers épuise les ressources d’application, utilisez la diffusion en continu.

Tout fichier mis en mémoire tampon unique dépassant 64 Ko est déplacé de la mémoire vers un fichier temporaire sur le disque.

Les fichiers temporaires pour les demandes plus volumineuses sont écrits à l’emplacement nommé dans la variable d’environnement ASPNETCORE_TEMP. Si ASPNETCORE_TEMP n’est pas défini, les fichiers sont écrits dans le dossier temporaire de l’utilisateur actuel.

La mise en mémoire tampon de petits fichiers est abordée dans les sections suivantes de cette rubrique :

Streaming

Le fichier est reçu à partir d’une requête en plusieurs parties et directement traité ou enregistré par l’application. La diffusion en continu n’améliore pas sensiblement les performances. La diffusion en continu réduit les demandes de mémoire ou d’espace disque lors du chargement de fichiers.

La diffusion en continu de fichiers volumineux est traitée dans la section Charger des fichiers volumineux avec la diffusion en continu.

Charger de petits fichiers avec liaison de modèle mis en mémoire tampon vers le stockage physique

Pour charger des petits fichiers, utilisez un formulaire en plusieurs parties ou construisez une demande POST avec JavaScript.

L’exemple suivant illustre l’utilisation d’un Razor formulaire Pages pour charger un seul fichier (Pages/BufferedSingleFileUploadPhysical.cshtml dans l’exemple d’application) :

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

L’exemple suivant est analogue à l’exemple précédent, sauf que :

  • JavaScript (API Fetch) est utilisé pour envoyer les données du formulaire.
  • Il n’y a pas de validation.
<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>

Pour effectuer le formulaire POST en JavaScript pour les clients qui ne prennent pas en charge l’API Fetch, utilisez l’une des approches suivantes :

  • Utilisez un polyfill Fetch (récupération) (par exemple, window.fetch polyfill (github/fetch)).

  • Utiliser XMLHttpRequest. Par exemple :

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

Afin de prendre en charge les téléchargements de fichiers, les formulaires HTML doivent spécifier un type d'encodage (enctype) de multipart/form-data.

Pour qu’un files élément d’entrée prend en charge le chargement de plusieurs fichiers, fournissez l’attribut multiple sur l’élément <input> :

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

Les fichiers individuels téléchargés sur le serveur sont accessibles via Liaison de données en utilisant IFormFile. L’exemple d’application illustre plusieurs chargements de fichiers mis en mémoire tampon pour les scénarios de stockage physique et de base de données.

Avertissement

N’utilisez pas la FileName propriété de IFormFile autre que pour l’affichage et la journalisation. Lors de l’affichage ou de la journalisation, html encode le nom du fichier. Un attaquant peut fournir un nom de fichier malveillant, y compris des chemins d’accès complets ou relatifs. Les applications doivent :

  • Supprimez le chemin d’accès du nom de fichier fourni par l’utilisateur.
  • Enregistrez le nom de fichier encodé html et supprimé pour l’interface utilisateur ou la journalisation.
  • Générez un nouveau nom de fichier aléatoire pour le stockage.

Le code suivant supprime le chemin d’accès du nom de fichier :

string untrustedFileName = Path.GetFileName(pathName);

Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :

Lors du chargement de fichiers à l’aide de la liaison de modèle et IFormFile, la méthode d’action peut accepter :

Notes

La liaison correspond aux fichiers de formulaire par nom. Par exemple, la valeur HTML name dans <input type="file" name="formFile"> doit correspondre au paramètre/à la propriété C# lié (FormFile). Pour plus d’informations, consultez la section Correspondance de la valeur de l’attribut de nom au nom de paramètre de la méthode POST.

L’exemple suivant :

  • Effectue une boucle dans un ou plusieurs fichiers chargés.
  • Utilise Path.GetTempFileName pour renvoyer un chemin d’accès complet pour un fichier, y compris le nom du fichier.
  • Enregistre les fichiers dans le système de fichiers local à l’aide d’un nom de fichier généré par l’application.
  • Retourne le nombre total et la taille des fichiers chargés.
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 });
}

Utilisez Path.GetRandomFileName pour générer un nom de fichier sans chemin d’accès. Dans l’exemple suivant, le chemin est obtenu à partir de la configuration :

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

Le chemin d’accès passé à FileStreamdoit inclure le nom de fichier. Si le nom de fichier n’est pas fourni, une exception UnauthorizedAccessException est levée à l’exécution.

Les fichiers téléchargés à l'aide de cette technique IFormFile sont mis en mémoire tampon dans la mémoire ou sur le disque du serveur avant d'être traités. Dans la méthode d’action, le contenu de IFormFile est accessible sous forme de Stream. En plus du système de fichiers local, les fichiers peuvent être enregistrés dans un partage réseau ou dans un service de stockage de fichiers, tel que stockage Blob Azure.

Pour un autre exemple qui boucle plusieurs fichiers pour le chargement et utilise des noms de fichiers fiables, consultez Pages/BufferedMultipleFileUploadPhysical.cshtml.cs dans l’exemple d’application.

Avertissement

Path.GetTempFileName provoque une IOException si plus de 65 535 fichiers sont créés sans que les fichiers temporaires précédents soient supprimés. La limite de 65 535 fichiers est une limite par serveur. Pour plus d’informations sur cette limite sur le système d’exploitation Windows, consultez les remarques dans les rubriques suivantes :

Charger de petits fichiers avec liaison de modèle mis en mémoire tampon à une base de données

Pour stocker les données de fichiers binaires dans une base de données avec Entity Framework, définissez une propriété de type Byte sur l’entité :

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

Spécifiez une propriété de modèle de page pour la classe qui inclut un IFormFile :

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

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

    ...
}

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

Notes

IFormFile peut être utilisé directement comme paramètre d'une méthode d'action ou comme propriété d'un modèle lié. L’exemple précédent utilise une propriété de modèle lié.

FileUpload est utilisé dans le Razor formulaire Pages :

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

Lorsque le formulaire est POSTed sur le serveur, copiez le dans un flux et enregistrez-le IFormFile en tant que tableau d’octets dans la base de données. Dans l’exemple suivant, _dbContext stocke le contexte de base de données de l’application :

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

L’exemple précédent est similaire à un scénario illustré dans l’exemple d’application :

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

Avertissement

Soyez prudent quand vous stockez des données binaires dans des bases de données relationnelles, car cela peut avoir un impact négatif sur les performances.

Ne vous basez pas ou ne faites pas confiance à la propriété FileName de IFormFile sans validation. La propriété FileName ne doit être utilisée qu’à des fins d’affichage et uniquement après l’encodage HTML.

Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :

Charger les fichiers volumineux avec le streaming

L’exemple 3.1 montre comment utiliser JavaScript pour diffuser un fichier vers une action de contrôleur. Le jeton anti-contrefaçon du fichier est généré en utilisant un attribut de filtre personnalisé, et il est passé dans les en-têtes HTTP de client et non pas dans le corps de la requête. Comme la méthode d'action traite directement les données téléchargées, la liaison au modèle de formulaire est désactivée par un autre filtre personnalisé. Dans l’action, le contenu du formulaire est lu en avec MultipartReader, qui lit chaque MultipartSection individuelle, traitant le fichier ou enregistrant le contenu selon ce qui est approprié. Une fois les sections multiparties lues, l’action effectue sa propre liaison de modèle.

La réponse initiale de la page charge le formulaire et enregistre un jeton de sécurité dans un cookie (via l'attribut GenerateAntiforgeryTokenCookieAttribute). L’attribut utilise la prise en charge anti-contrefaçon intégrée d’ASP.NET Core pour définir un cookie avec un jeton de requête :

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 est utilisé pour désactiver la liaison de modèle :

[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)
    {
    }
}

Dans l’exemple d’application, GenerateAntiforgeryTokenCookieAttribute et DisableFormValueModelBindingAttribute sont appliqués en tant que filtres aux modèles d’application de page de /StreamedSingleFileUploadDbet /StreamedSingleFileUploadPhysical dans Startup.ConfigureServices à l’aide deRazorconventions Pages :

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

Étant donné que la liaison de modèle ne lit pas le formulaire, les paramètres liés à partir du formulaire ne sont pas liés (la requête, l’itinéraire et l’en-tête continuent de fonctionner). La méthode d’action fonctionne directement avec la propriété Request. Un MultipartReader est utilisé pour lire chaque section. Les données de clé/valeur sont stockées dans un KeyValueAccumulator. Après la lecture des sections multipartites, le contenu de KeyValueAccumulator est utilisé pour lier les données du formulaire à un type de modèle.

Méthode complète StreamingController.UploadDatabase pour la diffusion en continu vers une base de données avec EF Core :

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

Méthode complète StreamingController.UploadPhysical pour la diffusion en continu vers un emplacement physique :

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

Dans l’exemple d’application, les vérifications de validation sont gérées par FileHelpers.ProcessStreamedFile.

Validation

La classe de l’exemple FileHelpers d’application illustre plusieurs vérifications des chargements de fichiers IFormFile mis en mémoire tampon et en streaming. Pour traiter IFormFile les téléchargements de fichiers mis en mémoire tampon dans l’exemple d’application, consultez la méthode ProcessFormFile dans le fichier Utilities/FileHelpers.cs. Pour le traitement des fichiers diffusés en continu, consultez la méthode ProcessStreamedFile dans le même fichier.

Avertissement

Les méthodes de traitement de validation présentées dans l’exemple d’application n’analysent pas le contenu des fichiers chargés. Dans la plupart des scénarios de production, une API d’analyseur de virus/programmes malveillants est utilisée sur le fichier avant de mettre le fichier à la disposition des utilisateurs ou d’autres systèmes.

Bien que l’exemple de rubrique fournisse un exemple fonctionnel de techniques de validation, n’implémentez pas la classe FileHelpers dans une application de production, sauf si vous :

  • Bien comprendre l’implémentation.
  • Modifiez l’implémentation en fonction de l’environnement et des spécifications de l’application.

N’implémentez jamais sans discrimination le code de sécurité dans une application sans répondre à ces exigences.

Validation du contenu

Utilisez une API d’analyse des virus/programmes malveillants tierce sur le contenu chargé.

L’analyse des fichiers est exigeante pour les ressources du serveur dans les scénarios de volume élevé. Si les performances de traitement des demandes sont réduites en raison de l’analyse des fichiers, envisagez de décharger le travail d’analyse vers un service en arrière-plan, éventuellement un service s’exécutant sur un serveur différent du serveur de l’application. En règle générale, les fichiers chargés sont conservés dans une zone mise en quarantaine jusqu’à ce que l’analyse antivirus en arrière-plan les vérifie. Lorsqu’un fichier passe, le fichier est déplacé vers l’emplacement de stockage normal du fichier. Ces étapes sont généralement effectuées conjointement avec un enregistrement de base de données qui indique l’état d’analyse d’un fichier. En utilisant une telle approche, l’application et le serveur d’applications restent concentrés sur la réponse aux demandes.

Validation de l’extension de fichier

L’extension du fichier chargé doit être vérifiée par rapport à une liste d’extensions autorisées. Par exemple :

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
}

Validation de signature de fichier

La signature d’un fichier est déterminée par les premiers octets au début d’un fichier. Ces octets peuvent être utilisés pour indiquer si l’extension correspond au contenu du fichier. L’exemple d’application vérifie les signatures de fichier pour quelques types de fichiers courants. Dans l’exemple suivant, la signature de fichier d’une image JPEG est vérifiée par rapport au fichier :

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

Pour obtenir des signatures de fichier supplémentaires, utilisez une base de données de signatures de fichier (résultat de recherche Google) et des spécifications de fichier officielles. La consultation des spécifications de fichiers officiels peut s’assurer que les signatures sélectionnées sont valides.

Sécurité des noms de fichier

N’utilisez jamais un nom de fichier fourni par le client pour enregistrer un fichier dans le stockage physique. Créez un nom de fichier sécurisé pour le fichier en utilisant Path.GetRandomFileName ou Path.GetTempFileName pour créer un chemin d’accès complet (y compris le nom de fichier) pour le stockage temporaire.

Razor code automatiquement les valeurs de propriété html pour l’affichage. Le code suivant peut être utilisé en toute sécurité :

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

En dehors de Razor, toujours HtmlEncode le contenu de nom de fichier à partir de la demande d’un utilisateur.

De nombreuses implémentations doivent inclure une vérification que le fichier existe; sinon, le fichier est remplacé par un fichier du même nom. Fournissez une logique supplémentaire pour répondre aux spécifications de votre application.

Validation de la taille

Limitez la taille des fichiers chargés.

Dans l’exemple d’application, la taille du fichier est limitée à 2 Mo (indiqué en octets). La limite est fournie via configuration à partir du fichier appsettings.json :

{
  "FileSizeLimit": 2097152
}

est FileSizeLimit injecté dans des classesPageModel :

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

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

    ...
}

Lorsqu’une taille de fichier dépasse la limite, le fichier est rejeté :

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

Faire correspondre la valeur d’attribut de nom au nom de paramètre de la méthode POST

Dans les formulaires Razor autres que les données de formulaire POST ou qui utilisent directement JavaScript FormData, le nom spécifié dans l’élément du formulaire ou FormData doit correspondre au nom du paramètre dans l’action du contrôleur.

Dans l’exemple suivant :

  • Lors de l’utilisation d’un élément <input>, l’attribut name est défini sur la valeur battlePlans :

    <input type="file" name="battlePlans" multiple>
    
  • Lors de l’utilisation de FormData dans JavaScript, le nom est défini sur la valeur battlePlans :

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

Utilisez un nom correspondant pour le paramètre de la méthode C# (battlePlans) :

  • Pour une Razor méthode de gestionnaire de pages Pages nommée Upload :

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • Pour une méthode d’action de contrôleur POST MVC :

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

Configuration du serveur et de l’application

Limite de longueur de corps en plusieurs parties

MultipartBodyLengthLimit définit la limite de longueur de chaque corps en plusieurs parties. Les sections de formulaire qui dépassent cette limite lèvent un lors de l’analyse InvalidDataException. La valeur par défaut est 134 217 728 (128 Mo). Personnalisez la limite à l’aide du paramètre MultipartBodyLengthLimit dans Startup.ConfigureServices :

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

RequestFormLimitsAttribute est utilisé pour définir le MultipartBodyLengthLimit pour une seule page ou action.

Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices :

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

Dans une Razor application Pages ou une application MVC, appliquez le filtre au modèle de page ou à la méthode d’action :

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

KestrelTaille maximale du corps de la demande

Pour les applications hébergées par Kestrel, la taille maximale par défaut du corps de la requête est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite à l’aide de l’option de serveur MaxRequestBodySizeKestrel :

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

RequestSizeLimitAttribute est utilisé pour définir MaxRequestBodySize pour une seule page ou action.

Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices :

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

Dans une Razor application pages ou une application MVC, appliquez le filtre à la classe ou à la méthode d’action du gestionnaire de pages :

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

Peut RequestSizeLimitAttribute également être appliqué à l’aide de la directive @attributeRazor :

@attribute [RequestSizeLimitAttribute(52428800)]

Autres limites Kestrel

D’autres Kestrel limites peuvent s’appliquer aux applications hébergées par Kestrel :

IIS

La limite de requête par défaut (maxAllowedContentLength) est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite dans le fichier web.config. Dans l’exemple suivant, la limite est définie sur 50 Mo (52 428 800 octets) :

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

Le paramètre maxAllowedContentLength s’applique seulement à IIS. Pour plus d’informations, consultez Limites de requête<requestLimits>.

Dépanner

Voici certains problèmes courants rencontrés avec le chargement de fichiers et leurs solutions possibles.

Erreur Introuvable lors du déploiement sur un serveur IIS

L’erreur suivante indique que le fichier chargé dépasse la longueur de contenu configurée du serveur :

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

Pour plus d’informations, consultez la section IIS.

Échec de connexion

Une erreur de connexion et une réinitialisation de la connexion au serveur indiquent probablement que le fichier chargé dépasse Kestrella taille maximale du corps de la demande. Pour plus d’informations, consultez la section KestrelTaille maximale du corps de la demande. Kestrel Les limites de connexion client peuvent également nécessiter un ajustement.

Exception de référence null avec IFormFile

Si le contrôleur accepte les fichiers téléchargés avec IFormFile mais que la valeur est null, confirmez que le formulaire HTML spécifie une valeur enctype de multipart/form-data. Si cet attribut n’est pas défini sur l’élément <form>, le chargement des fichiers ne se produit pas et les arguments IFormFile liés sont null. Vérifiez également que le nommage de chargement dans les données de formulaire correspond à celui de l’application.

Le flux était trop long

Les exemples de cette rubrique s’appuient sur MemoryStream pour contenir le contenu du fichier chargé. La limite de taille d’un MemoryStream est int.MaxValue. Si le scénario de chargement de fichiers de l’application nécessite que le contenu du fichier dépasse 50 Mo, utilisez une autre approche qui ne repose pas sur un seul MemoryStream pour conserver le contenu d’un fichier chargé.

ASP.NET Core prend en charge le téléchargement d'un ou de plusieurs fichiers à l'aide de la liaison de modèle tamponnée pour les petits fichiers et de la diffusion en continu non tamponnée pour les fichiers plus volumineux.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Considérations relatives à la sécurité

Soyez vigilant lorsque vous fournissez aux utilisateurs la possibilité de charger des fichiers sur un serveur. Les attaquants peuvent tenter de :

  • Exécuter des attaques par déni de service.
  • Charger des virus ou des logiciels malveillants.
  • Compromettre les réseaux et les serveurs d’autres manières.

Les étapes de sécurité qui réduisent la probabilité d’une attaque réussie sont les suivantes :

  • Charger des fichiers dans une zone de chargement de fichiers dédiée, de préférence sur un lecteur non système. Un emplacement dédié facilite l’imposition de restrictions de sécurité sur les fichiers chargés. Désactivez les autorisations d’exécution sur l’emplacement de chargement de fichier.†
  • Ne conservez pas les fichiers chargés dans la même arborescence que celle de l’application.†
  • Utilisez un nom de fichier sécurisé déterminé par l’application. N’utilisez pas de nom de fichier fourni par l’utilisateur ou le nom de fichier non approuvé du fichier chargé.† code HTML le nom de fichier non approuvé lors de son affichage. Par exemple, la journalisation du nom du fichier ou l’affichage dans l’interface utilisateur (Razor code HTML encode automatiquement la sortie).
  • Autorisez uniquement les extensions de fichier approuvées pour la spécification de conception de l’application.†
  • Vérifiez que les vérifications côté client sont effectuées sur le serveur.† Les vérifications côté client sont faciles à contourner.
  • Vérifiez la taille d’un fichier chargé. Définissez une limite de taille maximale pour empêcher les chargements volumineux.†
  • Lorsque les fichiers ne doivent pas être remplacés par un fichier chargé portant le même nom, case activée le nom du fichier par rapport à la base de données ou au stockage physique avant de charger le fichier.
  • Exécutez une analyse de virus/programmes malveillants sur le contenu chargé avant que le fichier ne soit stocké.

†L’exemple d’application illustre une approche qui répond aux critères.

Avertissement

Le chargement d’un code malveillant sur un système est généralement la première étape de l’exécution de code capable de :

  • Prendre complètement le contrôle d’un système.
  • Surcharger un système, ce qui a pour effet de le faire tomber en panne.
  • Compromettre les données utilisateur ou système.
  • Appliquer un graffiti sur une interface utilisateur publique.

Pour plus d’informations sur la réduction de la surface d’attaque quand vous acceptez des fichiers d’utilisateurs, consultez les ressources suivantes :

Pour plus d’informations sur l’implémentation de mesures de sécurité, notamment des exemples de l’exemple d’application, consultez la section Validation.

Scénarios de stockage

Les options de stockage courantes pour les fichiers sont les suivantes :

  • Base de données

    • Pour les petits chargements de fichiers, une base de données est souvent plus rapide que les options de stockage physique (système de fichiers ou partage réseau).
    • Une base de données est souvent plus pratique que les options de stockage physique, car la récupération d’un enregistrement de base de données pour les données utilisateur peut fournir simultanément le contenu du fichier (par exemple, une image d’avatar).
    • Une base de données est potentiellement moins coûteuse que l’utilisation d’un service de stockage de données.
  • Stockage physique (système de fichiers ou partage réseau)

    • Pour les chargements de fichiers volumineux :
      • Les limites de base de données peuvent restreindre la taille du chargement.
      • Le stockage physique est souvent moins économique que le stockage dans une base de données.
    • Un stockage physique est potentiellement moins coûteux que l’utilisation d’un service de stockage de données.
    • Le processus de l’application doit avoir des autorisations de lecture et d’écriture sur l’emplacement de stockage. N’accordez jamais l’autorisation d’exécution.
  • Service de stockage de données, par exemple, Stockage Blob Azure

    • Les services offrent généralement une scalabilité et une résilience améliorées par rapport aux solutions locales qui sont généralement soumises à des points de défaillance uniques.
    • Les services sont potentiellement moins coûteux dans les scénarios d’infrastructure de stockage volumineux.

    Pour plus d’informations, consultez Démarrage rapide : Utiliser .NET pour créer un objet blob dans le stockage d’objets.

Scénarios de chargement de fichiers

Deux approches générales pour le chargement de fichiers sont la mise en mémoire tampon et la diffusion en continu.

des réponses

Le fichier entier est lu dans un IFormFile, qui est une représentation C# du fichier utilisé pour traiter ou enregistrer le fichier.

Les ressources (disque, mémoire) utilisées par les chargements de fichiers varient selon le nombre et la taille des chargements de fichiers simultanés. Si une application tente de mettre en mémoire tampon un trop grand nombre de téléchargements, le site tombe en panne lorsqu'il manque de mémoire ou d'espace disque. Si la taille ou la fréquence des chargements de fichiers épuise les ressources d’application, utilisez la diffusion en continu.

Notes

Tout fichier mis en mémoire tampon unique dépassant 64 Ko est déplacé de la mémoire vers un fichier temporaire sur le disque.

La mise en mémoire tampon de petits fichiers est abordée dans les sections suivantes de cette rubrique :

Streaming

Le fichier est reçu à partir d’une requête en plusieurs parties et directement traité ou enregistré par l’application. La diffusion en continu n’améliore pas sensiblement les performances. La diffusion en continu réduit les demandes de mémoire ou d’espace disque lors du chargement de fichiers.

La diffusion en continu de fichiers volumineux est traitée dans la section Charger des fichiers volumineux avec la diffusion en continu.

Charger de petits fichiers avec liaison de modèle mis en mémoire tampon vers le stockage physique

Pour charger des petits fichiers, utilisez un formulaire en plusieurs parties ou construisez une demande POST avec JavaScript.

L’exemple suivant illustre l’utilisation d’un Razor formulaire Pages pour charger un seul fichier (Pages/BufferedSingleFileUploadPhysical.cshtml dans l’exemple d’application) :

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

L’exemple suivant est analogue à l’exemple précédent, sauf que :

  • JavaScript (API Fetch) est utilisé pour envoyer les données du formulaire.
  • Il n’y a pas de validation.
<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>

Pour effectuer le formulaire POST en JavaScript pour les clients qui ne prennent pas en charge l’API Fetch, utilisez l’une des approches suivantes :

  • Utilisez un polyfill Fetch (récupération) (par exemple, window.fetch polyfill (github/fetch)).

  • Utiliser XMLHttpRequest. Par exemple :

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

Afin de prendre en charge les téléchargements de fichiers, les formulaires HTML doivent spécifier un type d'encodage (enctype) de multipart/form-data.

Pour qu’un files élément d’entrée prend en charge le chargement de plusieurs fichiers, fournissez l’attribut multiple sur l’élément <input> :

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

Les fichiers individuels téléchargés sur le serveur sont accessibles via Liaison de données en utilisant IFormFile. L’exemple d’application illustre plusieurs chargements de fichiers mis en mémoire tampon pour les scénarios de stockage physique et de base de données.

Avertissement

N’utilisez pas la FileName propriété de IFormFile autre que pour l’affichage et la journalisation. Lors de l’affichage ou de la journalisation, html encode le nom du fichier. Un attaquant peut fournir un nom de fichier malveillant, y compris des chemins d’accès complets ou relatifs. Les applications doivent :

  • Supprimez le chemin d’accès du nom de fichier fourni par l’utilisateur.
  • Enregistrez le nom de fichier encodé html et supprimé pour l’interface utilisateur ou la journalisation.
  • Générez un nouveau nom de fichier aléatoire pour le stockage.

Le code suivant supprime le chemin d’accès du nom de fichier :

string untrustedFileName = Path.GetFileName(pathName);

Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :

Lors du chargement de fichiers à l’aide de la liaison de modèle et IFormFile, la méthode d’action peut accepter :

Notes

La liaison correspond aux fichiers de formulaire par nom. Par exemple, la valeur HTML name dans <input type="file" name="formFile"> doit correspondre au paramètre/à la propriété C# lié (FormFile). Pour plus d’informations, consultez la section Correspondance de la valeur de l’attribut de nom au nom de paramètre de la méthode POST.

L’exemple suivant :

  • Effectue une boucle dans un ou plusieurs fichiers chargés.
  • Utilise Path.GetTempFileName pour renvoyer un chemin d’accès complet pour un fichier, y compris le nom du fichier.
  • Enregistre les fichiers dans le système de fichiers local à l’aide d’un nom de fichier généré par l’application.
  • Retourne le nombre total et la taille des fichiers chargés.
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 });
}

Utilisez Path.GetRandomFileName pour générer un nom de fichier sans chemin d’accès. Dans l’exemple suivant, le chemin est obtenu à partir de la configuration :

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

Le chemin d’accès passé à FileStreamdoit inclure le nom de fichier. Si le nom de fichier n’est pas fourni, une exception UnauthorizedAccessException est levée à l’exécution.

Les fichiers téléchargés à l'aide de cette technique IFormFile sont mis en mémoire tampon dans la mémoire ou sur le disque du serveur avant d'être traités. Dans la méthode d’action, le contenu de IFormFile est accessible sous forme de Stream. En plus du système de fichiers local, les fichiers peuvent être enregistrés dans un partage réseau ou dans un service de stockage de fichiers, tel que stockage Blob Azure.

Pour un autre exemple qui boucle plusieurs fichiers pour le chargement et utilise des noms de fichiers fiables, consultez Pages/BufferedMultipleFileUploadPhysical.cshtml.cs dans l’exemple d’application.

Avertissement

Path.GetTempFileName provoque une IOException si plus de 65 535 fichiers sont créés sans que les fichiers temporaires précédents soient supprimés. La limite de 65 535 fichiers est une limite par serveur. Pour plus d’informations sur cette limite sur le système d’exploitation Windows, consultez les remarques dans les rubriques suivantes :

Charger de petits fichiers avec liaison de modèle mis en mémoire tampon à une base de données

Pour stocker les données de fichiers binaires dans une base de données avec Entity Framework, définissez une propriété de type Byte sur l’entité :

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

Spécifiez une propriété de modèle de page pour la classe qui inclut un IFormFile :

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

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

    ...
}

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

Notes

IFormFile peut être utilisé directement comme paramètre d'une méthode d'action ou comme propriété d'un modèle lié. L’exemple précédent utilise une propriété de modèle lié.

FileUpload est utilisé dans le Razor formulaire Pages :

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

Lorsque le formulaire est POSTed sur le serveur, copiez le dans un flux et enregistrez-le IFormFile en tant que tableau d’octets dans la base de données. Dans l’exemple suivant, _dbContext stocke le contexte de base de données de l’application :

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

L’exemple précédent est similaire à un scénario illustré dans l’exemple d’application :

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

Avertissement

Soyez prudent quand vous stockez des données binaires dans des bases de données relationnelles, car cela peut avoir un impact négatif sur les performances.

Ne vous basez pas ou ne faites pas confiance à la propriété FileName de IFormFile sans validation. La propriété FileName ne doit être utilisée qu’à des fins d’affichage et uniquement après l’encodage HTML.

Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :

Charger les fichiers volumineux avec le streaming

L’exemple suivant montre comment utiliser JavaScript pour diffuser un fichier vers une action de contrôleur. Le jeton anti-contrefaçon du fichier est généré en utilisant un attribut de filtre personnalisé, et il est passé dans les en-têtes HTTP de client et non pas dans le corps de la requête. Comme la méthode d'action traite directement les données téléchargées, la liaison au modèle de formulaire est désactivée par un autre filtre personnalisé. Dans l’action, le contenu du formulaire est lu en avec MultipartReader, qui lit chaque MultipartSection individuelle, traitant le fichier ou enregistrant le contenu selon ce qui est approprié. Une fois les sections multiparties lues, l’action effectue sa propre liaison de modèle.

La réponse initiale de la page charge le formulaire et enregistre un jeton de sécurité dans un cookie (via l'attribut GenerateAntiforgeryTokenCookieAttribute). L’attribut utilise la prise en charge anti-contrefaçon intégrée d’ASP.NET Core pour définir un cookie avec un jeton de requête :

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 est utilisé pour désactiver la liaison de modèle :

[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)
    {
    }
}

Dans l’exemple d’application, GenerateAntiforgeryTokenCookieAttribute et DisableFormValueModelBindingAttribute sont appliqués en tant que filtres aux modèles d’application de page de /StreamedSingleFileUploadDbet /StreamedSingleFileUploadPhysical dans Startup.ConfigureServices à l’aide deRazorconventions Pages :

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

Étant donné que la liaison de modèle ne lit pas le formulaire, les paramètres liés à partir du formulaire ne sont pas liés (la requête, l’itinéraire et l’en-tête continuent de fonctionner). La méthode d’action fonctionne directement avec la propriété Request. Un MultipartReader est utilisé pour lire chaque section. Les données de clé/valeur sont stockées dans un KeyValueAccumulator. Après la lecture des sections multipartites, le contenu de KeyValueAccumulator est utilisé pour lier les données du formulaire à un type de modèle.

Méthode complète StreamingController.UploadDatabase pour la diffusion en continu vers une base de données avec EF Core :

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

Méthode complète StreamingController.UploadPhysical pour la diffusion en continu vers un emplacement physique :

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

Dans l’exemple d’application, les vérifications de validation sont gérées par FileHelpers.ProcessStreamedFile.

Validation

La classe de l’exemple FileHelpers d’application illustre plusieurs vérifications des chargements de fichiers IFormFile mis en mémoire tampon et en streaming. Pour traiter IFormFile les téléchargements de fichiers mis en mémoire tampon dans l’exemple d’application, consultez la méthode ProcessFormFile dans le fichier Utilities/FileHelpers.cs. Pour le traitement des fichiers diffusés en continu, consultez la méthode ProcessStreamedFile dans le même fichier.

Avertissement

Les méthodes de traitement de validation présentées dans l’exemple d’application n’analysent pas le contenu des fichiers chargés. Dans la plupart des scénarios de production, une API d’analyseur de virus/programmes malveillants est utilisée sur le fichier avant de mettre le fichier à la disposition des utilisateurs ou d’autres systèmes.

Bien que l’exemple de rubrique fournisse un exemple fonctionnel de techniques de validation, n’implémentez pas la classe FileHelpers dans une application de production, sauf si vous :

  • Bien comprendre l’implémentation.
  • Modifiez l’implémentation en fonction de l’environnement et des spécifications de l’application.

N’implémentez jamais sans discrimination le code de sécurité dans une application sans répondre à ces exigences.

Validation du contenu

Utilisez une API d’analyse des virus/programmes malveillants tierce sur le contenu chargé.

L’analyse des fichiers est exigeante pour les ressources du serveur dans les scénarios de volume élevé. Si les performances de traitement des demandes sont réduites en raison de l’analyse des fichiers, envisagez de décharger le travail d’analyse vers un service en arrière-plan, éventuellement un service s’exécutant sur un serveur différent du serveur de l’application. En règle générale, les fichiers chargés sont conservés dans une zone mise en quarantaine jusqu’à ce que l’analyse antivirus en arrière-plan les vérifie. Lorsqu’un fichier passe, le fichier est déplacé vers l’emplacement de stockage normal du fichier. Ces étapes sont généralement effectuées conjointement avec un enregistrement de base de données qui indique l’état d’analyse d’un fichier. En utilisant une telle approche, l’application et le serveur d’applications restent concentrés sur la réponse aux demandes.

Validation de l’extension de fichier

L’extension du fichier chargé doit être vérifiée par rapport à une liste d’extensions autorisées. Par exemple :

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
}

Validation de signature de fichier

La signature d’un fichier est déterminée par les premiers octets au début d’un fichier. Ces octets peuvent être utilisés pour indiquer si l’extension correspond au contenu du fichier. L’exemple d’application vérifie les signatures de fichier pour quelques types de fichiers courants. Dans l’exemple suivant, la signature de fichier d’une image JPEG est vérifiée par rapport au fichier :

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

Pour obtenir des signatures de fichier supplémentaires, utilisez une base de données de signatures de fichier (résultat de recherche Google) et des spécifications de fichier officielles. La consultation des spécifications de fichiers officiels peut s’assurer que les signatures sélectionnées sont valides.

Sécurité des noms de fichier

N’utilisez jamais un nom de fichier fourni par le client pour enregistrer un fichier dans le stockage physique. Créez un nom de fichier sécurisé pour le fichier en utilisant Path.GetRandomFileName ou Path.GetTempFileName pour créer un chemin d’accès complet (y compris le nom de fichier) pour le stockage temporaire.

Razor code automatiquement les valeurs de propriété html pour l’affichage. Le code suivant peut être utilisé en toute sécurité :

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

En dehors de Razor, toujours HtmlEncode le contenu de nom de fichier à partir de la demande d’un utilisateur.

De nombreuses implémentations doivent inclure une vérification que le fichier existe; sinon, le fichier est remplacé par un fichier du même nom. Fournissez une logique supplémentaire pour répondre aux spécifications de votre application.

Validation de la taille

Limitez la taille des fichiers chargés.

Dans l’exemple d’application, la taille du fichier est limitée à 2 Mo (indiqué en octets). La limite est fournie via configuration à partir du fichier appsettings.json :

{
  "FileSizeLimit": 2097152
}

est FileSizeLimit injecté dans des classesPageModel :

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

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

    ...
}

Lorsqu’une taille de fichier dépasse la limite, le fichier est rejeté :

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

Faire correspondre la valeur d’attribut de nom au nom de paramètre de la méthode POST

Dans les formulaires Razor autres que les données de formulaire POST ou qui utilisent directement JavaScript FormData, le nom spécifié dans l’élément du formulaire ou FormData doit correspondre au nom du paramètre dans l’action du contrôleur.

Dans l’exemple suivant :

  • Lors de l’utilisation d’un élément <input>, l’attribut name est défini sur la valeur battlePlans :

    <input type="file" name="battlePlans" multiple>
    
  • Lors de l’utilisation de FormData dans JavaScript, le nom est défini sur la valeur battlePlans :

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

Utilisez un nom correspondant pour le paramètre de la méthode C# (battlePlans) :

  • Pour une Razor méthode de gestionnaire de pages Pages nommée Upload :

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • Pour une méthode d’action de contrôleur POST MVC :

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

Configuration du serveur et de l’application

Limite de longueur de corps en plusieurs parties

MultipartBodyLengthLimit définit la limite de longueur de chaque corps en plusieurs parties. Les sections de formulaire qui dépassent cette limite lèvent un lors de l’analyse InvalidDataException. La valeur par défaut est 134 217 728 (128 Mo). Personnalisez la limite à l’aide du paramètre MultipartBodyLengthLimit dans Startup.ConfigureServices :

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

RequestFormLimitsAttribute est utilisé pour définir le MultipartBodyLengthLimit pour une seule page ou action.

Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices :

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

Dans une Razor application Pages ou une application MVC, appliquez le filtre au modèle de page ou à la méthode d’action :

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

KestrelTaille maximale du corps de la demande

Pour les applications hébergées par Kestrel, la taille maximale par défaut du corps de la requête est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite à l’aide de l’option de serveur MaxRequestBodySizeKestrel :

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

RequestSizeLimitAttribute est utilisé pour définir MaxRequestBodySize pour une seule page ou action.

Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices :

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

Dans une Razor application pages ou une application MVC, appliquez le filtre à la classe ou à la méthode d’action du gestionnaire de pages :

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

Peut RequestSizeLimitAttribute également être appliqué à l’aide de la directive @attributeRazor :

@attribute [RequestSizeLimitAttribute(52428800)]

Autres limites Kestrel

D’autres Kestrel limites peuvent s’appliquer aux applications hébergées par Kestrel :

IIS

La limite de requête par défaut (maxAllowedContentLength) est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite dans le fichier web.config. Dans l’exemple suivant, la limite est définie sur 50 Mo (52 428 800 octets) :

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

Le paramètre maxAllowedContentLength s’applique seulement à IIS. Pour plus d’informations, consultez Limites de requête<requestLimits>.

Augmentez la taille maximale du corps de la requête HTTP en définissant IISServerOptions.MaxRequestBodySize dans Startup.ConfigureServices. Dans l’exemple suivant, la limite est définie sur 50 Mo (52 428 800 octets) :

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

Pour plus d’informations, consultez Héberger ASP.NET Core sur Windows avec IIS.

Dépanner

Voici certains problèmes courants rencontrés avec le chargement de fichiers et leurs solutions possibles.

Erreur Introuvable lors du déploiement sur un serveur IIS

L’erreur suivante indique que le fichier chargé dépasse la longueur de contenu configurée du serveur :

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

Pour plus d’informations, consultez la section IIS.

Échec de connexion

Une erreur de connexion et une réinitialisation de la connexion au serveur indiquent probablement que le fichier chargé dépasse Kestrella taille maximale du corps de la demande. Pour plus d’informations, consultez la section KestrelTaille maximale du corps de la demande. Kestrel Les limites de connexion client peuvent également nécessiter un ajustement.

Exception de référence null avec IFormFile

Si le contrôleur accepte les fichiers téléchargés avec IFormFile mais que la valeur est null, confirmez que le formulaire HTML spécifie une valeur enctype de multipart/form-data. Si cet attribut n’est pas défini sur l’élément <form>, le chargement des fichiers ne se produit pas et les arguments IFormFile liés sont null. Vérifiez également que le nommage de chargement dans les données de formulaire correspond à celui de l’application.

Le flux était trop long

Les exemples de cette rubrique s’appuient sur MemoryStream pour contenir le contenu du fichier chargé. La limite de taille d’un MemoryStream est int.MaxValue. Si le scénario de chargement de fichiers de l’application nécessite que le contenu du fichier dépasse 50 Mo, utilisez une autre approche qui ne repose pas sur un seul MemoryStream pour conserver le contenu d’un fichier chargé.

ASP.NET Core prend en charge le téléchargement d'un ou de plusieurs fichiers à l'aide de la liaison de modèle tamponnée pour les petits fichiers et de la diffusion en continu non tamponnée pour les fichiers plus volumineux.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Considérations relatives à la sécurité

Soyez vigilant lorsque vous fournissez aux utilisateurs la possibilité de charger des fichiers sur un serveur. Les attaquants peuvent tenter de :

  • Exécuter des attaques par déni de service.
  • Charger des virus ou des logiciels malveillants.
  • Compromettre les réseaux et les serveurs d’autres manières.

Les étapes de sécurité qui réduisent la probabilité d’une attaque réussie sont les suivantes :

  • Charger des fichiers dans une zone de chargement de fichiers dédiée, de préférence sur un lecteur non système. Un emplacement dédié facilite l’imposition de restrictions de sécurité sur les fichiers chargés. Désactivez les autorisations d’exécution sur l’emplacement de chargement de fichier.†
  • Ne conservez pas les fichiers chargés dans la même arborescence que celle de l’application.†
  • Utilisez un nom de fichier sécurisé déterminé par l’application. N’utilisez pas de nom de fichier fourni par l’utilisateur ou le nom de fichier non approuvé du fichier chargé.† code HTML le nom de fichier non approuvé lors de son affichage. Par exemple, la journalisation du nom du fichier ou l’affichage dans l’interface utilisateur (Razor code HTML encode automatiquement la sortie).
  • Autorisez uniquement les extensions de fichier approuvées pour la spécification de conception de l’application.†
  • Vérifiez que les vérifications côté client sont effectuées sur le serveur.† Les vérifications côté client sont faciles à contourner.
  • Vérifiez la taille d’un fichier chargé. Définissez une limite de taille maximale pour empêcher les chargements volumineux.†
  • Lorsque les fichiers ne doivent pas être remplacés par un fichier chargé portant le même nom, case activée le nom du fichier par rapport à la base de données ou au stockage physique avant de charger le fichier.
  • Exécutez une analyse de virus/programmes malveillants sur le contenu chargé avant que le fichier ne soit stocké.

†L’exemple d’application illustre une approche qui répond aux critères.

Avertissement

Le chargement d’un code malveillant sur un système est généralement la première étape de l’exécution de code capable de :

  • Prendre complètement le contrôle d’un système.
  • Surcharger un système, ce qui a pour effet de le faire tomber en panne.
  • Compromettre les données utilisateur ou système.
  • Appliquer un graffiti sur une interface utilisateur publique.

Pour plus d’informations sur la réduction de la surface d’attaque quand vous acceptez des fichiers d’utilisateurs, consultez les ressources suivantes :

Pour plus d’informations sur l’implémentation de mesures de sécurité, notamment des exemples de l’exemple d’application, consultez la section Validation.

Scénarios de stockage

Les options de stockage courantes pour les fichiers sont les suivantes :

  • Base de données

    • Pour les petits chargements de fichiers, une base de données est souvent plus rapide que les options de stockage physique (système de fichiers ou partage réseau).
    • Une base de données est souvent plus pratique que les options de stockage physique, car la récupération d’un enregistrement de base de données pour les données utilisateur peut fournir simultanément le contenu du fichier (par exemple, une image d’avatar).
    • Une base de données est potentiellement moins coûteuse que l’utilisation d’un service de stockage de données.
  • Stockage physique (système de fichiers ou partage réseau)

    • Pour les chargements de fichiers volumineux :
      • Les limites de base de données peuvent restreindre la taille du chargement.
      • Le stockage physique est souvent moins économique que le stockage dans une base de données.
    • Un stockage physique est potentiellement moins coûteux que l’utilisation d’un service de stockage de données.
    • Le processus de l’application doit avoir des autorisations de lecture et d’écriture sur l’emplacement de stockage. N’accordez jamais l’autorisation d’exécution.
  • Service de stockage de données, par exemple, Stockage Blob Azure

    • Les services offrent généralement une scalabilité et une résilience améliorées par rapport aux solutions locales qui sont généralement soumises à des points de défaillance uniques.
    • Les services sont potentiellement moins coûteux dans les scénarios d’infrastructure de stockage volumineux.

    Pour plus d’informations, consultez Démarrage rapide : Utiliser .NET pour créer un objet blob dans le stockage d’objets. La rubrique illustre UploadFromFileAsync, mais UploadFromStreamAsync peut être utilisée pour enregistrer un dans le FileStream stockage blob lors de l’utilisation d’un Stream.

Scénarios de chargement de fichiers

Deux approches générales pour le chargement de fichiers sont la mise en mémoire tampon et la diffusion en continu.

des réponses

Le fichier entier est lu dans un IFormFile, qui est une représentation C# du fichier utilisé pour traiter ou enregistrer le fichier.

Les ressources (disque, mémoire) utilisées par les chargements de fichiers varient selon le nombre et la taille des chargements de fichiers simultanés. Si une application tente de mettre en mémoire tampon un trop grand nombre de téléchargements, le site tombe en panne lorsqu'il manque de mémoire ou d'espace disque. Si la taille ou la fréquence des chargements de fichiers épuise les ressources d’application, utilisez la diffusion en continu.

Notes

Tout fichier mis en mémoire tampon unique dépassant 64 Ko est déplacé de la mémoire vers un fichier temporaire sur le disque.

La mise en mémoire tampon de petits fichiers est abordée dans les sections suivantes de cette rubrique :

Streaming

Le fichier est reçu à partir d’une requête en plusieurs parties et directement traité ou enregistré par l’application. La diffusion en continu n’améliore pas sensiblement les performances. La diffusion en continu réduit les demandes de mémoire ou d’espace disque lors du chargement de fichiers.

La diffusion en continu de fichiers volumineux est traitée dans la section Charger des fichiers volumineux avec la diffusion en continu.

Charger de petits fichiers avec liaison de modèle mis en mémoire tampon vers le stockage physique

Pour charger des petits fichiers, utilisez un formulaire en plusieurs parties ou construisez une demande POST avec JavaScript.

L’exemple suivant illustre l’utilisation d’un Razor formulaire Pages pour charger un seul fichier (Pages/BufferedSingleFileUploadPhysical.cshtml dans l’exemple d’application) :

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

L’exemple suivant est analogue à l’exemple précédent, sauf que :

  • JavaScript (API Fetch) est utilisé pour envoyer les données du formulaire.
  • Il n’y a pas de validation.
<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>

Pour effectuer le formulaire POST en JavaScript pour les clients qui ne prennent pas en charge l’API Fetch, utilisez l’une des approches suivantes :

  • Utilisez un polyfill Fetch (récupération) (par exemple, window.fetch polyfill (github/fetch)).

  • Utiliser XMLHttpRequest. Par exemple :

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

Afin de prendre en charge les téléchargements de fichiers, les formulaires HTML doivent spécifier un type d'encodage (enctype) de multipart/form-data.

Pour qu’un files élément d’entrée prend en charge le chargement de plusieurs fichiers, fournissez l’attribut multiple sur l’élément <input> :

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

Les fichiers individuels téléchargés sur le serveur sont accessibles via Liaison de données en utilisant IFormFile. L’exemple d’application illustre plusieurs chargements de fichiers mis en mémoire tampon pour les scénarios de stockage physique et de base de données.

Avertissement

N’utilisez pas la FileName propriété de IFormFile autre que pour l’affichage et la journalisation. Lors de l’affichage ou de la journalisation, html encode le nom du fichier. Un attaquant peut fournir un nom de fichier malveillant, y compris des chemins d’accès complets ou relatifs. Les applications doivent :

  • Supprimez le chemin d’accès du nom de fichier fourni par l’utilisateur.
  • Enregistrez le nom de fichier encodé html et supprimé pour l’interface utilisateur ou la journalisation.
  • Générez un nouveau nom de fichier aléatoire pour le stockage.

Le code suivant supprime le chemin d’accès du nom de fichier :

string untrustedFileName = Path.GetFileName(pathName);

Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :

Lors du chargement de fichiers à l’aide de la liaison de modèle et IFormFile, la méthode d’action peut accepter :

Notes

La liaison correspond aux fichiers de formulaire par nom. Par exemple, la valeur HTML name dans <input type="file" name="formFile"> doit correspondre au paramètre/à la propriété C# lié (FormFile). Pour plus d’informations, consultez la section Correspondance de la valeur de l’attribut de nom au nom de paramètre de la méthode POST.

L’exemple suivant :

  • Effectue une boucle dans un ou plusieurs fichiers chargés.
  • Utilise Path.GetTempFileName pour renvoyer un chemin d’accès complet pour un fichier, y compris le nom du fichier.
  • Enregistre les fichiers dans le système de fichiers local à l’aide d’un nom de fichier généré par l’application.
  • Retourne le nombre total et la taille des fichiers chargés.
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 });
}

Utilisez Path.GetRandomFileName pour générer un nom de fichier sans chemin d’accès. Dans l’exemple suivant, le chemin est obtenu à partir de la configuration :

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

Le chemin d’accès passé à FileStreamdoit inclure le nom de fichier. Si le nom de fichier n’est pas fourni, une exception UnauthorizedAccessException est levée à l’exécution.

Les fichiers téléchargés à l'aide de cette technique IFormFile sont mis en mémoire tampon dans la mémoire ou sur le disque du serveur avant d'être traités. Dans la méthode d’action, le contenu de IFormFile est accessible sous forme de Stream. En plus du système de fichiers local, les fichiers peuvent être enregistrés dans un partage réseau ou dans un service de stockage de fichiers, tel que stockage Blob Azure.

Pour un autre exemple qui boucle plusieurs fichiers pour le chargement et utilise des noms de fichiers fiables, consultez Pages/BufferedMultipleFileUploadPhysical.cshtml.cs dans l’exemple d’application.

Avertissement

Path.GetTempFileName provoque une IOException si plus de 65 535 fichiers sont créés sans que les fichiers temporaires précédents soient supprimés. La limite de 65 535 fichiers est une limite par serveur. Pour plus d’informations sur cette limite sur le système d’exploitation Windows, consultez les remarques dans les rubriques suivantes :

Charger de petits fichiers avec liaison de modèle mis en mémoire tampon à une base de données

Pour stocker les données de fichiers binaires dans une base de données avec Entity Framework, définissez une propriété de type Byte sur l’entité :

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

Spécifiez une propriété de modèle de page pour la classe qui inclut un IFormFile :

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

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

    ...
}

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

Notes

IFormFile peut être utilisé directement comme paramètre d'une méthode d'action ou comme propriété d'un modèle lié. L’exemple précédent utilise une propriété de modèle lié.

FileUpload est utilisé dans le Razor formulaire Pages :

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

Lorsque le formulaire est POSTed sur le serveur, copiez le dans un flux et enregistrez-le IFormFile en tant que tableau d’octets dans la base de données. Dans l’exemple suivant, _dbContext stocke le contexte de base de données de l’application :

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

L’exemple précédent est similaire à un scénario illustré dans l’exemple d’application :

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

Avertissement

Soyez prudent quand vous stockez des données binaires dans des bases de données relationnelles, car cela peut avoir un impact négatif sur les performances.

Ne vous basez pas ou ne faites pas confiance à la propriété FileName de IFormFile sans validation. La propriété FileName ne doit être utilisée qu’à des fins d’affichage et uniquement après l’encodage HTML.

Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :

Charger les fichiers volumineux avec le streaming

L’exemple suivant montre comment utiliser JavaScript pour diffuser un fichier vers une action de contrôleur. Le jeton anti-contrefaçon du fichier est généré en utilisant un attribut de filtre personnalisé, et il est passé dans les en-têtes HTTP de client et non pas dans le corps de la requête. Comme la méthode d'action traite directement les données téléchargées, la liaison au modèle de formulaire est désactivée par un autre filtre personnalisé. Dans l’action, le contenu du formulaire est lu en avec MultipartReader, qui lit chaque MultipartSection individuelle, traitant le fichier ou enregistrant le contenu selon ce qui est approprié. Une fois les sections multiparties lues, l’action effectue sa propre liaison de modèle.

La réponse initiale de la page charge le formulaire et enregistre un jeton de sécurité dans un cookie (via l'attribut GenerateAntiforgeryTokenCookieAttribute). L’attribut utilise la prise en charge anti-contrefaçon intégrée d’ASP.NET Core pour définir un cookie avec un jeton de requête :

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 est utilisé pour désactiver la liaison de modèle :

[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)
    {
    }
}

Dans l’exemple d’application, GenerateAntiforgeryTokenCookieAttribute et DisableFormValueModelBindingAttribute sont appliqués en tant que filtres aux modèles d’application de page de /StreamedSingleFileUploadDbet /StreamedSingleFileUploadPhysical dans Startup.ConfigureServices à l’aide deRazorconventions Pages :

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

Étant donné que la liaison de modèle ne lit pas le formulaire, les paramètres liés à partir du formulaire ne sont pas liés (la requête, l’itinéraire et l’en-tête continuent de fonctionner). La méthode d’action fonctionne directement avec la propriété Request. Un MultipartReader est utilisé pour lire chaque section. Les données de clé/valeur sont stockées dans un KeyValueAccumulator. Après la lecture des sections multipartites, le contenu de KeyValueAccumulator est utilisé pour lier les données du formulaire à un type de modèle.

Méthode complète StreamingController.UploadDatabase pour la diffusion en continu vers une base de données avec EF Core :

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

Méthode complète StreamingController.UploadPhysical pour la diffusion en continu vers un emplacement physique :

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

Dans l’exemple d’application, les vérifications de validation sont gérées par FileHelpers.ProcessStreamedFile.

Validation

La classe de l’exemple FileHelpers d’application illustre plusieurs vérifications des chargements de fichiers IFormFile mis en mémoire tampon et en streaming. Pour traiter IFormFile les téléchargements de fichiers mis en mémoire tampon dans l’exemple d’application, consultez la méthode ProcessFormFile dans le fichier Utilities/FileHelpers.cs. Pour le traitement des fichiers diffusés en continu, consultez la méthode ProcessStreamedFile dans le même fichier.

Avertissement

Les méthodes de traitement de validation présentées dans l’exemple d’application n’analysent pas le contenu des fichiers chargés. Dans la plupart des scénarios de production, une API d’analyseur de virus/programmes malveillants est utilisée sur le fichier avant de mettre le fichier à la disposition des utilisateurs ou d’autres systèmes.

Bien que l’exemple de rubrique fournisse un exemple fonctionnel de techniques de validation, n’implémentez pas la classe FileHelpers dans une application de production, sauf si vous :

  • Bien comprendre l’implémentation.
  • Modifiez l’implémentation en fonction de l’environnement et des spécifications de l’application.

N’implémentez jamais sans discrimination le code de sécurité dans une application sans répondre à ces exigences.

Validation du contenu

Utilisez une API d’analyse des virus/programmes malveillants tierce sur le contenu chargé.

L’analyse des fichiers est exigeante pour les ressources du serveur dans les scénarios de volume élevé. Si les performances de traitement des demandes sont réduites en raison de l’analyse des fichiers, envisagez de décharger le travail d’analyse vers un service en arrière-plan, éventuellement un service s’exécutant sur un serveur différent du serveur de l’application. En règle générale, les fichiers chargés sont conservés dans une zone mise en quarantaine jusqu’à ce que l’analyse antivirus en arrière-plan les vérifie. Lorsqu’un fichier passe, le fichier est déplacé vers l’emplacement de stockage normal du fichier. Ces étapes sont généralement effectuées conjointement avec un enregistrement de base de données qui indique l’état d’analyse d’un fichier. En utilisant une telle approche, l’application et le serveur d’applications restent concentrés sur la réponse aux demandes.

Validation de l’extension de fichier

L’extension du fichier chargé doit être vérifiée par rapport à une liste d’extensions autorisées. Par exemple :

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
}

Validation de signature de fichier

La signature d’un fichier est déterminée par les premiers octets au début d’un fichier. Ces octets peuvent être utilisés pour indiquer si l’extension correspond au contenu du fichier. L’exemple d’application vérifie les signatures de fichier pour quelques types de fichiers courants. Dans l’exemple suivant, la signature de fichier d’une image JPEG est vérifiée par rapport au fichier :

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

Pour obtenir des signatures de fichier supplémentaires, utilisez une base de données de signatures de fichier (résultat de recherche Google) et des spécifications de fichier officielles. La consultation des spécifications de fichiers officiels peut s’assurer que les signatures sélectionnées sont valides.

Sécurité des noms de fichier

N’utilisez jamais un nom de fichier fourni par le client pour enregistrer un fichier dans le stockage physique. Créez un nom de fichier sécurisé pour le fichier en utilisant Path.GetRandomFileName ou Path.GetTempFileName pour créer un chemin d’accès complet (y compris le nom de fichier) pour le stockage temporaire.

Razor code automatiquement les valeurs de propriété html pour l’affichage. Le code suivant peut être utilisé en toute sécurité :

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

En dehors de Razor, toujours HtmlEncode le contenu de nom de fichier à partir de la demande d’un utilisateur.

De nombreuses implémentations doivent inclure une vérification que le fichier existe; sinon, le fichier est remplacé par un fichier du même nom. Fournissez une logique supplémentaire pour répondre aux spécifications de votre application.

Validation de la taille

Limitez la taille des fichiers chargés.

Dans l’exemple d’application, la taille du fichier est limitée à 2 Mo (indiqué en octets). La limite est fournie via configuration à partir du fichier appsettings.json :

{
  "FileSizeLimit": 2097152
}

est FileSizeLimit injecté dans des classesPageModel :

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

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

    ...
}

Lorsqu’une taille de fichier dépasse la limite, le fichier est rejeté :

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

Faire correspondre la valeur d’attribut de nom au nom de paramètre de la méthode POST

Dans les formulaires Razor autres que les données de formulaire POST ou qui utilisent directement JavaScript FormData, le nom spécifié dans l’élément du formulaire ou FormData doit correspondre au nom du paramètre dans l’action du contrôleur.

Dans l’exemple suivant :

  • Lors de l’utilisation d’un élément <input>, l’attribut name est défini sur la valeur battlePlans :

    <input type="file" name="battlePlans" multiple>
    
  • Lors de l’utilisation de FormData dans JavaScript, le nom est défini sur la valeur battlePlans :

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

Utilisez un nom correspondant pour le paramètre de la méthode C# (battlePlans) :

  • Pour une Razor méthode de gestionnaire de pages Pages nommée Upload :

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • Pour une méthode d’action de contrôleur POST MVC :

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

Configuration du serveur et de l’application

Limite de longueur de corps en plusieurs parties

MultipartBodyLengthLimit définit la limite de longueur de chaque corps en plusieurs parties. Les sections de formulaire qui dépassent cette limite lèvent un lors de l’analyse InvalidDataException. La valeur par défaut est 134 217 728 (128 Mo). Personnalisez la limite à l’aide du paramètre MultipartBodyLengthLimit dans Startup.ConfigureServices :

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

RequestFormLimitsAttribute est utilisé pour définir le MultipartBodyLengthLimit pour une seule page ou action.

Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices :

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

Dans une Razor application Pages ou une application MVC, appliquez le filtre au modèle de page ou à la méthode d’action :

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

KestrelTaille maximale du corps de la demande

Pour les applications hébergées par Kestrel, la taille maximale par défaut du corps de la requête est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite à l’aide de l’option de serveur MaxRequestBodySizeKestrel :

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

RequestSizeLimitAttribute est utilisé pour définir MaxRequestBodySize pour une seule page ou action.

Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices :

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

Dans une Razor application pages ou une application MVC, appliquez le filtre à la classe ou à la méthode d’action du gestionnaire de pages :

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

Autres limites Kestrel

D’autres Kestrel limites peuvent s’appliquer aux applications hébergées par Kestrel :

IIS

La limite de requête par défaut (maxAllowedContentLength) est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite dans le fichier web.config. Dans l’exemple suivant, la limite est définie sur 50 Mo (52 428 800 octets) :

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

Le paramètre maxAllowedContentLength s’applique seulement à IIS. Pour plus d’informations, consultez Limites de requête<requestLimits>.

Augmentez la taille maximale du corps de la requête HTTP en définissant IISServerOptions.MaxRequestBodySize dans Startup.ConfigureServices. Dans l’exemple suivant, la limite est définie sur 50 Mo (52 428 800 octets) :

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

Pour plus d’informations, consultez Héberger ASP.NET Core sur Windows avec IIS.

Dépanner

Voici certains problèmes courants rencontrés avec le chargement de fichiers et leurs solutions possibles.

Erreur Introuvable lors du déploiement sur un serveur IIS

L’erreur suivante indique que le fichier chargé dépasse la longueur de contenu configurée du serveur :

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

Pour plus d’informations, consultez la section IIS.

Échec de connexion

Une erreur de connexion et une réinitialisation de la connexion au serveur indiquent probablement que le fichier chargé dépasse Kestrella taille maximale du corps de la demande. Pour plus d’informations, consultez la section KestrelTaille maximale du corps de la demande. Kestrel Les limites de connexion client peuvent également nécessiter un ajustement.

Exception de référence null avec IFormFile

Si le contrôleur accepte les fichiers téléchargés avec IFormFile mais que la valeur est null, confirmez que le formulaire HTML spécifie une valeur enctype de multipart/form-data. Si cet attribut n’est pas défini sur l’élément <form>, le chargement des fichiers ne se produit pas et les arguments IFormFile liés sont null. Vérifiez également que le nommage de chargement dans les données de formulaire correspond à celui de l’application.

Le flux était trop long

Les exemples de cette rubrique s’appuient sur MemoryStream pour contenir le contenu du fichier chargé. La limite de taille d’un MemoryStream est int.MaxValue. Si le scénario de chargement de fichiers de l’application nécessite que le contenu du fichier dépasse 50 Mo, utilisez une autre approche qui ne repose pas sur un seul MemoryStream pour conserver le contenu d’un fichier chargé.

Ressources supplémentaires