Dela via


Ladda upp filer i ASP.NET Core

Av Rutger Storm

ASP.NET Core stöder uppladdning av en eller flera filer med buffrad modellbindning för mindre filer och obufferderad direktuppspelning för större filer.

Visa eller ladda ned exempelkod (hur du laddar ned)

Säkerhetshänsyn

Var försiktig när du ger användarna möjlighet att ladda upp filer till en server. Cyberattacker kan försöka:

  • Utför överbelastningsattacker.
  • Ladda upp virus eller skadlig kod.
  • Kompromettera nätverk och servrar på andra sätt.

Säkerhetssteg som minskar sannolikheten för ett lyckat angrepp är:

  • Ladda upp filer till ett dedikerat filuppladdningsområde, helst till en enhet som inte är en systemenhet. En dedikerad plats gör det enklare att införa säkerhetsbegränsningar för uppladdade filer. Inaktivera körningsbehörigheter på filuppladdningsplatsen.†
  • Spara inte uppladdade filer i samma katalogträd som appen.
  • Använd ett säkert filnamn som bestäms av appen. Använd inte ett filnamn som tillhandahålls av användaren eller det obetrodda filnamnet för den uppladdade filen.† HTML-koda det ej betrodda filnamnet när du visar det. Du kan till exempel logga filnamnet eller visa det i användargränssnittet (Razor automatiskt HTML-kodar utdata).
  • Tillåt endast godkända filnamnstillägg för appens designspecifikation.†
  • Kontrollera att kontroller på klientsidan utförs på server.† Kontroller på klientsidan är lätta att kringgå.
  • Kontrollera storleken på en uppladdad fil. Ange en maximal storleksgräns för att förhindra stora uppladdningar.†
  • När filer inte ska skrivas över av en uppladdad fil med samma namn kontrollerar du filnamnet mot databasen eller den fysiska lagringen innan du laddar upp filen.
  • Kör en virus-/skadlig skanner på uppladdat innehåll innan filen lagras.

†Exempelappen visar en metod som uppfyller kriterierna.

Varning

Att ladda upp skadlig kod till ett system är ofta det första steget för att köra kod som kan:

  • Få fullständig kontroll över ett system.
  • Överbelasta ett system med resultatet att systemet kraschar.
  • Kompromettera användar- eller systemdata.
  • Tillämpa graffiti på ett offentligt användargränssnitt.

Information om hur du minskar sårbarheter när du tar emot filer från användare finns i följande resurser:

Mer information om hur du implementerar säkerhetsåtgärder, inklusive exempel från exempelappen, finns i avsnittet Validering.

Lagringsscenarier

Vanliga lagringsalternativ för filer är:

  • Databas

    • För små filuppladdningar är en databas ofta snabbare än lagring på fysiska enheter (t.ex. filsystem eller delade nätverksresurser).
    • En databas är ofta mer praktisk än fysiska lagringsalternativ eftersom hämtning av en databaspost för användardata samtidigt kan tillhandahålla filinnehållet (till exempel en avatarbild).
    • En databas är potentiellt billigare än att använda en molndatalagringstjänst.
  • Fysisk lagring (filsystem eller nätverksresurs)

    • För stora filuppladdningar:
      • Databasbegränsningar kan begränsa uppladdningens storlek.
      • Fysisk lagring är ofta mindre ekonomiskt än lagring i en databas.
    • Fysisk lagring är potentiellt billigare än att använda en molndatalagringstjänst.
    • Appens process måste ha läs- och skrivbehörighet till lagringsplatsen. Bevilja aldrig exekveringsbehörighet.
  • Molndatalagringstjänst, till exempel Azure Blob Storage.

    • Tjänsterna erbjuder vanligtvis bättre skalbarhet och återhämtning jämfört med lokala lösningar som vanligtvis utsätts för enskilda felpunkter.
    • Tjänster är potentiellt lägre kostnader i scenarier med stor lagringsinfrastruktur.

    Mer information finns i Snabbstart: Använd .NET för att skapa en blob i objektlagring.

Små och stora filer

Definitionen av små och stora filer beror på vilka databehandlingsresurser som är tillgängliga. Appar bör jämföra den lagringsmetod som används för att säkerställa att den kan hantera de förväntade storlekarna. Prestandamått för minne, CPU, disk och databasprestanda.

Vissa gränser kan inte anges för vad som är litet jämfört med stort för distributionen, men här är några av ASP.NET Cores relaterade standardvärden för FormOptions (API-dokumentation):

  • Som standard buffar HttpRequest.Form inte hela begärandetexten (BufferBody), men buffertar alla formulärfiler för flera delar som ingår.
  • MultipartBodyLengthLimit är den maximala storleken för buffrade formulärfiler (standard: 128 MB).
  • MemoryBufferThreshold anger tröskelvärdet för buffring i minnet innan du övergår till en buffertfil på disk (standard: 64 KB). MemoryBufferThreshold fungerar som en gräns mellan små och stora filer, som höjs eller sänks beroende på appresurser och scenarier.

Mer information om FormOptionsfinns i FormOptions-klassen i referenskällan ASP.NET Core.

Obs

Dokumentationslänkar till .NET-referenskällan läser vanligtvis in lagringsplatsens standardgren, vilket representerar den aktuella utvecklingen för nästa version av .NET. För att välja en tagg för en specifik version använder du rullgardinsmenyn Växla grenar eller taggar. Mer information finns i Så här väljer du en versionstagg för ASP.NET Core-källkod (dotnet/AspNetCore.Docs #26205).

Scenarier för filuppladdning

Två allmänna metoder för att ladda upp filer är buffring och strömning.

Buffring

Hela filen läss in i en IFormFile. IFormFile är en C#-representation av filen som används för att bearbeta eller spara filen.

Vilken disk och minne som används vid filuppladdningar beror på antalet och storleken på samtidiga filuppladdningar. Om en app försöker buffrar för många uppladdningar kraschar webbplatsen när minnet eller diskutrymmet tar slut. Om storleken eller frekvensen för filuppladdningar uttömmer appresurser använder du strömning.

En enskild buffrad fil som överstiger 64 KB flyttas från minnet till en temporär fil på disken.

Temporära filer för större begäranden skrivs till platsen med namnet i ASPNETCORE_TEMP miljövariabeln. Om ASPNETCORE_TEMP inte har definierats skrivs filerna till den aktuella användarens temporära mapp.

Buffring av små filer beskrivs i följande avsnitt i det här avsnittet:

Streaming

Filen tas emot från en begäran i flera delar och bearbetas eller sparas direkt av appen. Direktuppspelning förbättrar inte prestanda nämnvärt. Direktuppspelning minskar kraven på minne eller diskutrymme vid uppladdning av filer.

Strömning av stora filer beskrivs i avsnittet Ladda upp stora filer med strömning.

Ladda upp små filer med buffrad modellbindning till fysisk lagring

Om du vill ladda upp små filer använder du ett formulär med flera delar eller skapar en POST-begäran med Hjälp av JavaScript.

I följande exempel visas hur ett Razor Pages-formulär används för att ladda upp en enda fil (Pages/BufferedSingleFileUploadPhysical.cshtml i exempelappen):

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

Följande exempel är analogt med föregående exempel förutom att:

  • JavaScripts (Fetch API) används för att skicka formulärets data.
  • Det finns ingen validering.
<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>

Om du vill utföra postformuläret i JavaScript för klienter som inte stöder Fetch API-använder du någon av följande metoder:

  • Använd en Fetch Polyfill (till exempel window.fetch polyfill (github/fetch)).

  • Använd XMLHttpRequest. Till exempel:

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

För att stöda filuppladdningar måste HTML-formulär ange en kodningstyp (enctype) av multipart/form-data.

För ett files indataelement som stöder uppladdning av flera filer anger du attributet multiple på elementet <input>:

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

De enskilda filer som laddas upp till servern kan nås via modellbindning med hjälp av IFormFile. Exempelappen visar flera buffrade filuppladdningar för databas- och fysiska lagringsscenarier.

Varning

Använd inte egenskapen FileName för IFormFile annat än för visning och loggning. När du visar eller loggar kodar HTML filnamnet. En cyberattacker kan ange ett skadligt filnamn, inklusive fullständiga sökvägar eller relativa sökvägar. Ansökningar bör:

  • Ta bort sökvägen från det filnamn som användaren har angett.
  • Spara det HTML-kodade filnamnet utan sökväg för användargränssnittet eller loggning.
  • Generera ett nytt slumpmässigt filnamn för lagring.

Följande kod tar bort sökvägen från filnamnet:

string untrustedFileName = Path.GetFileName(pathName);

Exemplen som har tillhandahållits hittills tar inte hänsyn till säkerhetsöverväganden. Ytterligare information finns i följande avsnitt och exempelappen:

När du laddar upp filer med hjälp av modellbindning och IFormFilekan åtgärdsmetoden acceptera:

Not

Bindningen matchar formulärfiler efter namn. Html-name-värdet i <input type="file" name="formFile"> måste till exempel matcha C#-parametern/egenskapsgränsen (FormFile). Mer information finns i avsnittet Matcha namnattribut till parameternamnet för POST-metoden.

Följande exempel:

  • Loopar genom en eller flera uppladdade filer.
  • Använder Path.GetTempFileName för att returnera en fullständig sökväg för en fil, inklusive filnamnet.
  • Sparar filerna i det lokala filsystemet med hjälp av ett filnamn som genereras av appen.
  • Returnerar det totala antalet och storleken på filer som laddats upp.
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 });
}

Använd Path.GetRandomFileName för att generera ett filnamn utan sökväg. I följande exempel hämtas sökvägen från konfigurationen:

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

Sökvägen som skickas till FileStreammåste innehålla filnamnet. Om filnamnet inte anges genereras en UnauthorizedAccessException vid körning.

Filer som laddas upp med hjälp av tekniken IFormFile buffrade i minnet eller på disken på servern innan de bearbetas. I åtgärdsmetoden är IFormFile-innehållet tillgängligt som en Stream. Förutom det lokala filsystemet kan filer sparas på en nätverksdelning eller till en fillagringstjänst, till exempel Azure Blob Storage.

Ett annat exempel som loopar över flera filer för uppladdning och använder säkra filnamn finns i Pages/BufferedMultipleFileUploadPhysical.cshtml.cs i exempelappen.

Varning

I .NET 7 och tidigare versioner genererar Path.GetTempFileName en IOException när fler än 65 535 filer skapas utan att tidigare temporära filer tas bort. Gränsen på 65 535 filer är en gräns per server. Mer information om den här gränsen för Windows-operativsystem finns i kommentarerna i följande artiklar:

Ladda upp små filer med buffrad modellbindning till en databas

Om du vill lagra binära fildata i en databas med Entity Frameworkdefinierar du en Byte matrisegenskap på entiteten:

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

Ange en sidmodellegenskap för klassen som innehåller en IFormFile:

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

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

    ...
}

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

Obs

IFormFile kan användas direkt som en åtgärdsmetodparameter eller som en bunden modellegenskap. I föregående exempel används en bunden modellegenskap.

FileUpload används i formuläret Razor 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>

När formuläret skickas till servern kopierar du IFormFile till en dataström och sparar det som en bytematris i databasen. I följande exempel lagrar _dbContext appens databaskontext:

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

Föregående exempel liknar ett scenario som visas i exempelappen:

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

Varning

Var försiktig när du lagrar binära data i relationsdatabaser, eftersom det kan påverka prestanda negativt.

Förlita dig inte på egenskapen FileName för IFormFile utan validering. Egenskapen FileName ska endast användas i visningssyfte och endast efter HTML-kodning.

Exemplen som tillhandahålls tar inte hänsyn till säkerhetsöverväganden. Ytterligare information finns i följande avsnitt och exempelappen:

Ladda upp stora filer med direktuppspelning

3.1-exemplet visar hur du använder JavaScript för att strömma en fil till en kontrollantåtgärd. Filens antiforgerytoken genereras med ett anpassat filterattribut och skickas till http-klienthuvudena i stället för i begärandetexten. Eftersom åtgärdsmetoden bearbetar uppladdade data direkt inaktiveras formmodellbindningen av ett annat anpassat filter. I åtgärden läss formulärets innehåll med hjälp av en MultipartReader, som läser varje enskild MultipartSection, bearbetar filen eller lagrar innehållet efter behov. När flerdelsavsnitten har lästs utför åtgärden en egen modellbindning.

Det första sidsvaret läser in formuläret och sparar en antiförfalsknings-token i en cookie (via attributet GenerateAntiforgeryTokenCookieAttribute). Attributet använder ASP.NET Cores inbyggda antiforgery-stöd för att ange en cookie med en begärandetoken:

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 används för att inaktivera modellbindning:

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

I exempelappen tillämpas GenerateAntiforgeryTokenCookieAttribute och DisableFormValueModelBindingAttribute som filter på sidprogrammodellerna för /StreamedSingleFileUploadDb och /StreamedSingleFileUploadPhysical i Startup.ConfigureServices med hjälp av Razor Pages-konventioner:

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

Eftersom modellbindningen inte läser formuläret binds inte parametrar som är bundna från formuläret (fråga, väg och rubrik fortsätter att fungera). Åtgärdsmetoden fungerar direkt med egenskapen Request. En MultipartReader används för att läsa varje avsnitt. Nyckel-/värdedata lagras i en KeyValueAccumulator. När avsnitten i flera delar har lästs används innehållet i KeyValueAccumulator för att binda formulärdata till en modelltyp.

Den fullständiga StreamingController.UploadDatabase-metoden för direktuppspelning till en databas med 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));
        }
    }
}

Den fullständiga StreamingController.UploadPhysical-metoden för direktuppspelning till en fysisk plats:

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

I exempelappen hanteras verifieringskontroller av FileHelpers.ProcessStreamedFile.

Validering

Exempelappens FileHelpers-klass visar flera kontroller för buffrade IFormFile och strömmade filuppladdningar. Information om hur du bearbetar IFormFile buffrade filuppladdningar i exempelappen finns i metoden ProcessFormFile i filen Utilities/FileHelpers.cs. Information om hur du bearbetar strömmade filer finns i metoden ProcessStreamedFile i samma fil.

Varning

De valideringsmetoder som visas i exempelappen söker inte igenom innehållet i uppladdade filer. I de flesta produktionsscenarier används ett API för virus/skadlig kodskanner i filen innan filen blir tillgänglig för användare eller andra system.

Även om ämnesexemplet innehåller ett fungerande exempel på valideringstekniker ska du inte implementera klassen FileHelpers i en produktionsapp om du inte:

  • Förstå implementeringen fullt ut.
  • Ändra implementeringen efter behov för appens miljö och specifikationer.

Implementera aldrig urskillningslöst säkerhetskod i en app utan att uppfylla dessa krav.

Innehållsverifiering

Använd ett API för virus/skadlig kod från tredje part på uppladdat innehåll.

Genomsökning av filer kräver serverresurser i scenarier med hög volym. Om prestanda för bearbetning av begäranden minskar på grund av filgenomsökning bör du överväga att avlasta genomsökningen till en bakgrundstjänst, eventuellt en tjänst som körs på en annan server än appens server. Vanligtvis lagras uppladdade filer i ett karantänområde tills bakgrundsvirusskannern kontrollerar dem. När en fil passerar flyttas filen till den normala fillagringsplatsen. De här stegen utförs vanligtvis tillsammans med en databaspost som anger genomsökningsstatus för en fil. Genom att använda en sådan metod fortsätter appen och appservern att fokusera på att svara på begäranden.

Validering av filnamnstillägg

Filnamnstillägget för den uppladdade filen bör kontrolleras mot en lista över tillåtna tillägg. Till exempel:

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
}

Validering av filsignatur

En fils signatur bestäms av de första byteen i början av en fil. Dessa byte kan användas för att ange om tillägget matchar innehållet i filen. Exempelappen kontrollerar filsignaturer efter några vanliga filtyper. I följande exempel kontrolleras filsignaturen för en JPEG-bild mot filen:

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

Om du vill hämta ytterligare filsignaturer använder du en databas för filsignaturer (Google-sökresultat) och officiella filspecifikationer. Samråd med officiella filspecifikationer kan säkerställa att de valda signaturerna är giltiga.

Säkerhet för filnamn

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

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

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

Utanför RazorHtmlEncode alltid filnamnsinnehåll från en användares begäran.

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

Storleksvalidering

Begränsa storleken på uppladdade filer.

I exempelappen är storleken på filen begränsad till 2 MB (anges i byte). Gränsen anges via Configuration från filen appsettings.json:

{
  "FileSizeLimit": 2097152
}

FileSizeLimit injiceras i PageModel klasser:

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

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

    ...
}

När en filstorlek överskrider gränsen avvisas filen:

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

Matcha namnattributvärdet med parameternamnet för POST-metoden

I icke-Razor formulär som POSTar formulärdata eller använder JavaScripts FormData direkt, måste namnet som anges i formulärets element eller FormData matcha namnet på parametern i kontrollerns åtgärd.

I följande exempel:

  • När du använder ett <input>-element anges attributet name till värdet battlePlans:

    <input type="file" name="battlePlans" multiple>
    
  • När du använder FormData i JavaScript anges namnet till värdet battlePlans:

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

Använd ett matchande namn för parametern för C#-metoden (battlePlans):

  • För en sidhanterarmetod för Razor Pages med namnet Upload:

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • För en åtgärdsmetod för MVC POST-styrenhet:

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

Server- och appkonfiguration

Längdgräns för flera delar

MultipartBodyLengthLimit anger gränsen för längden på varje flerdelstext. Formuläravsnitt som överskrider den här gränsen genererar en InvalidDataException när de parsas. Standardvärdet är 134 217 728 (128 MB). Anpassa gränsen med inställningen MultipartBodyLengthLimit i Startup.ConfigureServices:

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

RequestFormLimitsAttribute används för att ange MultipartBodyLengthLimit för en enda sida eller åtgärd.

I Razor Pages-appen, tillämpa filtret med en konvention i Startup.ConfigureServices:

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

I en Razor Pages-app eller en MVC-app använder du filtret på sidmodellen eller åtgärdsmetoden:

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

Kestrel maximal storlek för begärandetext

För appar som hanteras av Kestrelär standardstorleken för begärandetexten 30 000 000 byte, vilket är cirka 28,6 MB. Anpassa gränsen med hjälp av serveralternativet 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 används för att ange MaxRequestBodySize- för en enda sida eller åtgärd.

I en Razor Pages-app använder du filtret med en konvention i Startup.ConfigureServices:

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

I en Razor pages-app eller en MVC-app använder du filtret på sidhanterarklassen eller åtgärdsmetoden:

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

RequestSizeLimitAttribute kan också tillämpas med hjälp av @attributeRazor-direktivet:

@attribute [RequestSizeLimitAttribute(52428800)]

Andra Kestrel gränser

Andra Kestrel begränsningar kan gälla för appar som hanteras av Kestrel:

IIS

Standardgränsen för begäran (maxAllowedContentLength) är 30 000 000 byte, vilket är cirka 28,6 MB. Anpassa gränsen i filen web.config. I följande exempel är gränsen inställd på 50 MB (52 428 800 byte):

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

Inställningen maxAllowedContentLength gäller endast för IIS. Mer information finns i Förfrågningsgränser <requestLimits>.

Felsöka

Nedan visas några vanliga problem som uppstår när du arbetar med att ladda upp filer och deras möjliga lösningar.

Felmeddelande "Ej hittad" när det distribueras till en IIS-server

Följande fel anger att den uppladdade filen överskrider serverns konfigurerade innehållslängd:

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

Mer information finns i avsnittet IIS.

Anslutningsfel

Ett anslutningsfel och en återställning av serveranslutning indikerar förmodligen att den uppladdade filen överskrider Kestrel:s maximala storlek för begärandetext. För mer information, se avsnittet Kestrel maximal storlek på begärandetexten. Kestrel klientanslutningsgränser kan också kräva justering.

Null-referensfel med IFormFile

Om kontrollanten accepterar uppladdade filer med IFormFile men värdet är nullkontrollerar du att HTML-formuläret anger ett enctype värde för multipart/form-data. Om det här attributet inte har angetts för elementet <form> sker inte filuppladdningen och eventuella bundna IFormFile argument null. Bekräfta också att namngivningen för uppladdningen i formulärdata matchar appens namngivning.

Strömmen var för lång

Exemplen i det här avsnittet förlitar sig på MemoryStream för att lagra den uppladdade filens innehåll. Storleksgränsen för en MemoryStream är int.MaxValue. Om appens scenario för filuppladdning kräver att filinnehållet är större än 50 MB använder du en alternativ metod som inte förlitar sig på en enda MemoryStream för att lagra en uppladdad fils innehåll.

ASP.NET Core stöder uppladdning av en eller flera filer med buffrad modellbindning för mindre filer och obufferderad direktuppspelning för större filer.

Visa eller ladda ned exempelkod (hur du laddar ned)

Säkerhetshänsyn

Var försiktig när du ger användarna möjlighet att ladda upp filer till en server. Cyberattacker kan försöka:

  • Utför överbelastningsattacker.
  • Ladda upp virus eller skadlig kod.
  • Kompromettera nätverk och servrar på andra sätt.

Säkerhetssteg som minskar sannolikheten för ett lyckat angrepp är:

  • Ladda upp filer till ett dedikerat filuppladdningsområde, helst till en enhet som inte är en systemenhet. En dedikerad plats gör det enklare att införa säkerhetsbegränsningar för uppladdade filer. Avaktivera exekveringsbehörigheter på filuppladdningsmappen.†
  • Spara inte uppladdade filer i samma mappstruktur som applikationen.
  • Använd ett säkert filnamn som bestäms av appen. Använd inte ett filnamn som tillhandahålls av användaren eller det obetrodda filnamnet för den uppladdade filen.† HTML-koda det ej betrodda filnamnet när du visar det. Du kan till exempel logga filnamnet eller visa det i användargränssnittet (Razor automatiskt HTML-kodar utdata).
  • Tillåt endast godkända filnamnstillägg för appens designspecifikation.†
  • Kontrollera att kontroller på klientsidan utförs på server.† Kontroller på klientsidan är lätta att kringgå.
  • Kontrollera storleken på en uppladdad fil. Ange en maximal storleksgräns för att förhindra stora uppladdningar.†
  • När filer inte ska skrivas över av en uppladdad fil med samma namn kontrollerar du filnamnet mot databasen eller den fysiska lagringen innan du laddar upp filen.
  • Kör en virus-/skadlig skanner på uppladdat innehåll innan filen lagras.

†Exempelappen visar en metod som uppfyller kriterierna.

Varning

Att ladda upp skadlig kod till ett system är ofta det första steget för att köra kod som kan:

  • Få fullständig kontroll över ett system.
  • Överbelasta ett system med resultatet att systemet kraschar.
  • Kompromettera användar- eller systemdata.
  • Tillämpa graffiti på ett offentligt användargränssnitt.

Information om hur du minskar sårbarheter när du tar emot filer från användare finns i följande resurser:

Mer information om hur du implementerar säkerhetsåtgärder, inklusive exempel från exempelappen, finns i avsnittet Validering.

Lagringsscenarier

Vanliga lagringsalternativ för filer är:

  • Databas

    • För små filuppladdningar är en databas ofta snabbare än alternativen för fysisk lagring (filsystem eller nätverksresurs).
    • En databas är ofta mer praktisk än fysiska lagringsalternativ eftersom hämtning av en databaspost för användardata samtidigt kan tillhandahålla filinnehållet (till exempel en avatarbild).
    • En databas är potentiellt billigare än att använda en datalagringstjänst.
  • Fysisk lagring (filsystem eller nätverksresurs)

    • För stora filuppladdningar:
      • Databasbegränsningar kan begränsa uppladdningens storlek.
      • Fysisk lagring är ofta mindre ekonomiskt än lagring i en databas.
    • Fysisk lagring är potentiellt billigare än att använda en datalagringstjänst.
    • Appens process måste ha läs- och skrivbehörighet till lagringsplatsen. Bevilja aldrig körningsbehörighet.
  • Datalagringstjänst (till exempel Azure Blob Storage)

    • Tjänsterna erbjuder vanligtvis bättre skalbarhet och återhämtning jämfört med lokala lösningar som vanligtvis utsätts för enskilda felpunkter.
    • Tjänster är potentiellt lägre kostnader i scenarier med stor lagringsinfrastruktur.

    Mer information finns i Snabbstart: Använd .NET för att skapa en blob i objektlagring.

Scenarier för filuppladdning

Två allmänna metoder för att ladda upp filer är buffring och strömning.

Buffring

Hela filen läss in i en IFormFile, som är en C#-representation av filen som används för att bearbeta eller spara filen.

Vilka resurser (disk, minne) som används av filuppladdningar beror på antalet och storleken på samtidiga filuppladdningar. Om en app försöker buffrar för många uppladdningar kraschar webbplatsen när minnet eller diskutrymmet tar slut. Om storleken eller frekvensen för filuppladdningar uttömmer appresurser använder du strömning.

Note

En enskild buffrad fil som överstiger 64 KB flyttas från minnet till en temporär fil på disken.

Buffring av små filer beskrivs i följande avsnitt i det här avsnittet:

Streaming

Filen tas emot från en begäran i flera delar och bearbetas eller sparas direkt av appen. Direktuppspelning förbättrar inte prestanda nämnvärt. Direktuppspelning minskar kraven på minne eller diskutrymme vid uppladdning av filer.

Att strömma stora filer beskrivs i avsnittet Strömma uppladdningen av stora filer.

Ladda upp små filer med buffrad modellbindning till fysisk lagring

Om du vill ladda upp små filer använder du ett formulär med flera delar eller skapar en POST-begäran med Hjälp av JavaScript.

I följande exempel visas hur ett Razor Pages-formulär används för att ladda upp en enda fil (Pages/BufferedSingleFileUploadPhysical.cshtml i exempelappen):

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

Följande exempel är analogt med föregående exempel förutom att:

  • JavaScripts (Fetch API) används för att skicka formulärets data.
  • Det finns ingen validering.
<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>

Om du vill utföra postformuläret i JavaScript för klienter som inte stöder Fetch API-använder du någon av följande metoder:

  • Använd en Fetch Polyfill (till exempel window.fetch polyfill (github/fetch)).

  • Använd XMLHttpRequest. Till exempel:

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

För att stöda filuppladdningar måste HTML-formulär ange en kodningstyp (enctype) av multipart/form-data.

För ett files indataelement som stöder uppladdning av flera filer anger du attributet multiple på elementet <input>:

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

De enskilda filer som laddas upp till servern kan nås via modellbindning med hjälp av IFormFile. Exempelappen visar flera buffrade filuppladdningar för databas- och fysiska lagringsscenarier.

Varning

Använd inte använda egenskapen FileName för IFormFile annat än för visning och loggning. När du visar eller loggar kodar HTML filnamnet. En cyberattacker kan ange ett skadligt filnamn, inklusive fullständiga sökvägar eller relativa sökvägar. Program bör:

  • Ta bort sökvägen från det filnamn som användaren har angett.
  • Spara det HTML-kodade filnamnet med borttagen sökväg för användargränssnittet eller loggning.
  • Generera ett nytt slumpmässigt filnamn för lagring.

Följande kod tar bort sökvägen från filnamnet:

string untrustedFileName = Path.GetFileName(pathName);

Exemplen som har tillhandahållits hittills tar inte hänsyn till säkerhetsöverväganden. Ytterligare information finns i följande avsnitt och exempelappen :

När du laddar upp filer med hjälp av modellbindning och IFormFilekan åtgärdsmetoden acceptera:

Obs

Bindningen matchar formulärfiler efter namn. Html-name-värdet i <input type="file" name="formFile"> måste till exempel matcha C#-parametern/egenskapsgränsen (FormFile). Mer information finns i avsnittet Matcha namnattribut till parameternamnet för POST-metoden.

Följande exempel:

  • Loopar genom en eller flera uppladdade filer.
  • Använder Path.GetTempFileName för att returnera en fullständig sökväg för en fil, inklusive filnamnet.
  • Sparar filerna i det lokala filsystemet med hjälp av ett filnamn som genereras av appen.
  • Returnerar det totala antalet och storleken på filer som laddats upp.
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 });
}

Använd Path.GetRandomFileName för att generera ett filnamn utan sökväg. I följande exempel hämtas sökvägen från konfigurationen:

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

Sökvägen som skickas till FileStreammåste innehålla filnamnet. Om filnamnet inte anges genereras en UnauthorizedAccessException vid körning.

Filer som laddas upp med hjälp av tekniken IFormFile buffrade i minnet eller på disken på servern innan de bearbetas. Inuti åtgärdsmetoden är IFormFile innehållet tillgängligt som en Stream. Förutom det lokala filsystemet kan filer sparas till en nätverksandel eller en fillagringstjänst, såsom Azure Blob Storage, till exempel .

Ett annat exempel som loopar över flera filer för uppladdning och använder säkra filnamn finns i Pages/BufferedMultipleFileUploadPhysical.cshtml.cs i exempelappen.

Varning

Path.GetTempFileName genererar en IOException om fler än 65 535 filer skapas utan att tidigare temporära filer tas bort. Gränsen på 65 535 filer är en gräns per server. Mer information om den här gränsen för Windows OS finns i kommentarerna i följande avsnitt:

Ladda upp små filer med buffrad modellbindning till en databas

Om du vill lagra binära fildata i en databas med Entity Frameworkdefinierar du en Byte matrisegenskap på entiteten:

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

Ange en sidmodellegenskap för klassen som innehåller en IFormFile:

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

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

    ...
}

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

Not

IFormFile kan användas direkt som en åtgärdsmetodparameter eller som en bunden modellegenskap. I föregående exempel används en bunden modellegenskap.

FileUpload används i formuläret Razor 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>

När formuläret skickas till servern kopierar du IFormFile till en dataström och sparar det som en bytematris i databasen. I följande exempel lagrar _dbContext appens databaskontext:

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

Föregående exempel liknar ett scenario som visas i exempelappen:

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

Varning

Var försiktig när du lagrar binära data i relationsdatabaser, eftersom det kan påverka prestanda negativt.

Förlita dig inte på egenskapen FileName för IFormFile utan validering. Egenskapen FileName ska endast användas i visningssyfte och endast efter HTML-kodning.

Exemplen som tillhandahålls tar inte hänsyn till säkerhetsöverväganden. Ytterligare information ges av följande avsnitt och exempelappen :

Ladda upp stora filer med direktuppspelning

I följande exempel visas hur du använder JavaScript för att strömma en fil till en kontrollantåtgärd. Filens antiforgerytoken genereras med ett anpassat filterattribut och skickas till http-klienthuvudena i stället för i begärandetexten. Eftersom åtgärdsmetoden bearbetar uppladdade data direkt inaktiveras formmodellbindningen av ett annat anpassat filter. I åtgärden läss formulärets innehåll med hjälp av en MultipartReader, som läser varje enskild MultipartSection, bearbetar filen eller lagrar innehållet efter behov. När flerdelsavsnitten har lästs utför åtgärden en egen modellbindning.

Det första sidsvaret läser in formuläret och sparar en antiförfalskningstoken i en cookie (via attributet GenerateAntiforgeryTokenCookieAttribute). Attributet använder ASP.NET Cores inbyggda antiforgery-stöd för att ställa in ett cookie med en begärandetoken.

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 används för att inaktivera modellbindning:

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

I exempelappen tillämpas GenerateAntiforgeryTokenCookieAttribute och DisableFormValueModelBindingAttribute som filter på sidprogrammodellerna för /StreamedSingleFileUploadDb och /StreamedSingleFileUploadPhysical i Startup.ConfigureServices med hjälp av Razor Pages-konventioner:

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

Eftersom modellbindningen inte läser formuläret binds inte parametrar som är bundna från formuläret (fråga, väg och rubrik fortsätter att fungera). Åtgärdsmetoden fungerar direkt med egenskapen Request. En MultipartReader används för att läsa varje avsnitt. Nyckel-/värdedata lagras i en KeyValueAccumulator. När avsnitten i flera delar har lästs används innehållet i KeyValueAccumulator för att binda formulärdata till en modelltyp.

Den fullständiga StreamingController.UploadDatabase-metoden för direktuppspelning till en databas med 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));
        }
    }
}

Den fullständiga StreamingController.UploadPhysical-metoden för direktuppspelning till en fysisk plats:

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

I exempelappen hanteras verifieringskontroller av FileHelpers.ProcessStreamedFile.

Validering

Exempelappens FileHelpers-klass visar flera kontroller för buffrade IFormFile och strömmade filuppladdningar. Information om hur du bearbetar IFormFile buffrade filuppladdningar i exempelappen finns i metoden ProcessFormFile i filen Utilities/FileHelpers.cs. Information om hur du bearbetar strömmade filer finns i metoden ProcessStreamedFile i samma fil.

Varning

De valideringsmetoder som visas i exempelappen söker inte igenom innehållet i uppladdade filer. I de flesta produktionsscenarier används ett API för virus/skadlig kodskanner i filen innan filen blir tillgänglig för användare eller andra system.

Även om ämnesexemplet innehåller ett fungerande exempel på valideringstekniker ska du inte implementera klassen FileHelpers i en produktionsapp om du inte:

  • Förstå implementeringen fullt ut.
  • Ändra implementeringen efter behov för appens miljö och specifikationer.

Implementera aldrig urskillningslöst säkerhetskod i en app utan att uppfylla dessa krav.

Innehållsverifiering

Använd ett API för virus/skadlig kod från tredje part på uppladdat innehåll.

Genomsökning av filer kräver serverresurser i scenarier med hög volym. Om prestanda för bearbetning av begäranden minskar på grund av filgenomsökning bör du överväga att avlasta genomsökningen till en bakgrundstjänst, eventuellt en tjänst som körs på en annan server än appens server. Vanligtvis lagras uppladdade filer i ett karantänområde tills bakgrundsvirusskannern kontrollerar dem. När en fil passerar flyttas filen till den normala fillagringsplatsen. De här stegen utförs vanligtvis tillsammans med en databaspost som anger genomsökningsstatus för en fil. Genom att använda en sådan metod fortsätter appen och appservern att fokusera på att svara på begäranden.

Validering av filnamnstillägg

Filnamnstillägget för den uppladdade filen bör kontrolleras mot en lista över tillåtna tillägg. Till exempel:

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
}

Validering av filsignatur

En fils signatur bestäms av de första byteen i början av en fil. Dessa byte kan användas för att ange om tillägget matchar innehållet i filen. Exempelappen kontrollerar filsignaturer efter några vanliga filtyper. I följande exempel kontrolleras filsignaturen för en JPEG-bild mot filen:

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

Om du vill hämta ytterligare filsignaturer använder du en databas för filsignaturer (Google-sökresultat) och officiella filspecifikationer. Samråd med officiella filspecifikationer kan säkerställa att de valda signaturerna är giltiga.

Säkerhet för filnamn

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

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

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

Utanför Razor, se alltid till att filnamnsinnehåll från en användares begäran placeras i HtmlEncode.

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

Storleksvalidering

Begränsa storleken på uppladdade filer.

I exempelappen är storleken på filen begränsad till 2 MB (anges i byte). Gränsen anges via Configuration från filen appsettings.json:

{
  "FileSizeLimit": 2097152
}

FileSizeLimit injiceras i PageModel klasser:

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

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

    ...
}

När en filstorlek överskrider gränsen avvisas filen:

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

Matcha namnattributvärdet med parameternamnet för POST-metoden

I icke-Razor formulär som skickar POST-data eller använder JavaScripts FormData direkt måste namnet som anges i formulärets element eller FormData matcha namnet på parametern i kontrollerns metod.

I följande exempel:

  • När du använder ett <input>-element anges attributet name till värdet battlePlans:

    <input type="file" name="battlePlans" multiple>
    
  • När du använder FormData i JavaScript anges namnet till värdet battlePlans:

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

Använd ett matchande namn för parametern för C#-metoden (battlePlans):

  • För en sidhanterarmetod för Razor Pages med namnet Upload:

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • För en åtgärdsmetod för MVC POST-styrenhet:

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

Server- och appkonfiguration

Gräns för längd på mångdelad kropp

MultipartBodyLengthLimit anger gränsen för längden på varje flerdelstext. Formuläravsnitt som överskrider den här gränsen genererar en InvalidDataException när de parsas. Standardvärdet är 134 217 728 (128 MB). Anpassa gränsen med inställningen MultipartBodyLengthLimit i Startup.ConfigureServices:

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

RequestFormLimitsAttribute används för att ange MultipartBodyLengthLimit för en enda sida eller åtgärd.

I Pages-appen Razor, applicera filtret med konventionen i Startup.ConfigureServices:

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

I en Razor Pages-app eller en MVC-app använder du filtret på sidmodellen eller åtgärdsmetoden:

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

Kestrel maximal storlek för begärandetext

För appar som hanteras av Kestrelär standardstorleken för begärandetexten 30 000 000 byte, vilket är cirka 28,6 MB. Anpassa gränsen med hjälp av serveralternativet 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 används för att ange MaxRequestBodySize- för en enda sida eller åtgärd.

I en Razor Pages-app, tillämpa filtret med konventionen i Startup.ConfigureServices:

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

I en Razor pages-app eller en MVC-app använder du filtret på sidhanterarklassen eller åtgärdsmetoden:

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

RequestSizeLimitAttribute kan också tillämpas med hjälp av @attributeRazor-direktivet:

@attribute [RequestSizeLimitAttribute(52428800)]

Andra Kestrel gränser

Andra Kestrel begränsningar kan gälla för appar som hanteras av Kestrel:

IIS

Standardgränsen för begäran (maxAllowedContentLength) är 30 000 000 byte, vilket är cirka 28,6 MB. Anpassa gränsen i filen web.config. I följande exempel är gränsen inställd på 50 MB (52 428 800 byte):

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

Inställningen maxAllowedContentLength gäller endast för IIS. Mer information finns i Förfrågningsgränser <requestLimits>.

Öka den maximala storleken på begärandetexten för HTTP-begäran genom att ange IISServerOptions.MaxRequestBodySize i Startup.ConfigureServices. I följande exempel är gränsen inställd på 50 MB (52 428 800 byte):

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

Mer information finns i Host ASP.NET Core i Windows med IIS.

Felsöka

Nedan visas några vanliga problem som uppstår när du arbetar med att ladda upp filer och deras möjliga lösningar.

Felmeddelandet "Not Found" vid distribution till en IIS-server

Följande fel anger att den uppladdade filen överskrider serverns konfigurerade innehållslängd:

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

Mer information finns i avsnittet IIS.

Anslutningsfel

Ett anslutningsfel och en återställd serveranslutning indikerar förmodligen att den uppladdade filen överskrider den maximala storleken på begärandetexten för Kestrel. För mer information, se avsnittet Kestrel maximal storlek för begärandetext. Kestrel klientanslutningsgränser kan också kräva justering.

Nullreferensundantag med IFormFile

Om kontrollanten accepterar uppladdade filer med IFormFile men värdet är nullkontrollerar du att HTML-formuläret anger ett enctype värde för multipart/form-data. Om det här attributet inte har angetts för elementet <form> sker inte filuppladdningen och eventuella bundna IFormFile argument null. Bekräfta också att namngivningen i formulärdata för uppladdning matchar appens namngivning.

Dataströmmen var för lång

Exemplen i det här avsnittet förlitar sig på MemoryStream för att lagra den uppladdade filens innehåll. Storleksgränsen för en MemoryStream är int.MaxValue. Om appens scenario för filuppladdning kräver att filinnehållet är större än 50 MB använder du en alternativ metod som inte förlitar sig på en enda MemoryStream för att lagra en uppladdad fils innehåll.

ASP.NET Core stöder uppladdning av en eller flera filer med buffrad modellbindning för mindre filer och obufferderad direktuppspelning för större filer.

Visa eller ladda ned exempelkod (hur du laddar ned)

Säkerhetshänsyn

Var försiktig när du ger användarna möjlighet att ladda upp filer till en server. Cyberattacker kan försöka:

  • Utför överbelastningsattacker.
  • Ladda upp virus eller skadlig kod.
  • Kompromettera nätverk och servrar på andra sätt.

Säkerhetssteg som minskar sannolikheten för ett lyckat angrepp är:

  • Ladda upp filer till ett dedikerat filuppladdningsområde, helst till en enhet som inte är en systemenhet. En dedikerad plats gör det enklare att införa säkerhetsbegränsningar för uppladdade filer. Inaktivera körningsbehörigheter på filuppladdningsplatsen.†
  • Spara inte uppladdade filer i samma katalogträd som appen.†
  • Använd ett säkert filnamn som bestäms av appen. Använd inte ett filnamn som tillhandahålls av användaren eller det obetrodda filnamnet för den uppladdade filen.† HTML-koda det ej betrodda filnamnet när du visar det. Du kan till exempel logga filnamnet eller visa det i användargränssnittet (Razor automatiskt HTML-kodar utdata).
  • Tillåt endast godkända filnamnstillägg för appens designspecifikation.†
  • Kontrollera att kontroller på klientsidan utförs på server.† Kontroller på klientsidan är lätta att kringgå.
  • Kontrollera storleken på en uppladdad fil. Ange en maximal storleksgräns för att förhindra stora uppladdningar.†
  • När filer inte ska skrivas över av en uppladdad fil med samma namn kontrollerar du filnamnet mot databasen eller den fysiska lagringen innan du laddar upp filen.
  • Kör en virus-/skadlig skanner på uppladdat innehåll innan filen lagras.

†Exempelappen visar en metod som uppfyller kriterierna.

Varning

Att ladda upp skadlig kod till ett system är ofta det första steget för att köra kod som kan:

  • Få fullständig kontroll över ett system.
  • Överbelasta ett system med resultatet att systemet kraschar.
  • Kompromettera användar- eller systemdata.
  • Tillämpa graffiti på ett offentligt användargränssnitt.

Information om hur du minskar sårbarheter när du tar emot filer från användare finns i följande resurser:

Mer information om hur du implementerar säkerhetsåtgärder, inklusive exempel från exempelappen, finns i avsnittet Validering.

Lagringsscenarier

Vanliga lagringsalternativ för filer är:

  • Databas

    • För små filuppladdningar är en databas ofta snabbare än alternativen för fysisk lagring (filsystem eller nätverksresurs).
    • En databas är ofta mer praktisk än fysiska lagringsalternativ eftersom hämtning av en databaspost för användardata samtidigt kan tillhandahålla filinnehållet (till exempel en avatarbild).
    • En databas är potentiellt billigare än att använda en datalagringstjänst.
  • Fysisk lagring (filsystem eller nätverksresurs)

    • För stora filuppladdningar:
      • Databasbegränsningar kan begränsa uppladdningens storlek.
      • Fysisk lagring är ofta mindre ekonomiskt än lagring i en databas.
    • Fysisk lagring är potentiellt billigare än att använda en datalagringstjänst.
    • Appens process måste ha läs- och skrivbehörighet till lagringsplatsen. Bevilja aldrig körningsbehörighet.
  • Datalagringstjänst (till exempel Azure Blob Storage)

    • Tjänsterna erbjuder vanligtvis bättre skalbarhet och återhämtning jämfört med lokala lösningar som vanligtvis utsätts för enskilda felpunkter.
    • Tjänster är potentiellt lägre kostnader i scenarier med stor lagringsinfrastruktur.

    Mer information finns i Snabbstart: Använd .NET för att skapa en blob i objektlagring. Avsnittet visar UploadFromFileAsync, men UploadFromStreamAsync kan användas för att spara en FileStream till bloblagring när du arbetar med en Stream.

Scenarier för filuppladdning

Två allmänna metoder för att ladda upp filer är buffring och strömning.

Buffring

Hela filen läss in i en IFormFile, som är en C#-representation av filen som används för att bearbeta eller spara filen.

Vilka resurser (disk, minne) som används av filuppladdningar beror på antalet och storleken på samtidiga filuppladdningar. Om en app försöker buffrar för många uppladdningar kraschar webbplatsen när minnet eller diskutrymmet tar slut. Om storleken eller frekvensen för filuppladdningar uttömmer appresurser använder du strömning.

Anteckning

En enskild buffrad fil som överstiger 64 KB flyttas från minnet till en temporär fil på disken.

Buffring av små filer beskrivs i följande avsnitt i det här avsnittet:

Streaming

Filen tas emot från en begäran i flera delar och bearbetas eller sparas direkt av appen. Direktuppspelning förbättrar inte prestanda nämnvärt. Direktuppspelning minskar kraven på minne eller diskutrymme vid uppladdning av filer.

Strömmande stora filer beskrivs i avsnittet Ladda upp stora filer med strömmande.

Ladda upp små filer med buffrad modellbindning till fysisk lagring

Om du vill ladda upp små filer använder du ett formulär med flera delar eller skapar en POST-begäran med Hjälp av JavaScript.

I följande exempel visas hur ett Razor Pages-formulär används för att ladda upp en enda fil (Pages/BufferedSingleFileUploadPhysical.cshtml i exempelappen):

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

Följande exempel är analogt med föregående exempel förutom att:

  • JavaScripts (Fetch API) används för att skicka formulärets data.
  • Det finns ingen validering.
<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>

Om du vill utföra postformuläret i JavaScript för klienter som inte stöder Fetch API-använder du någon av följande metoder:

  • Använd en Fetch Polyfill (till exempel window.fetch polyfill (github/fetch)).

  • Använd XMLHttpRequest. Till exempel:

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

För att stöda filuppladdningar måste HTML-formulär ange en kodningstyp (enctype) av multipart/form-data.

För ett files indataelement som stöder uppladdning av flera filer anger du attributet multiple på elementet <input>:

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

De enskilda filer som laddas upp till servern kan nås via modellbindning med hjälp av IFormFile. Exempelappen visar flera buffrade filuppladdningar för databas- och fysiska lagringsscenarier.

Varning

Använd inte egenskapen FileName för IFormFile utöver visning och loggning. När du visar eller loggar kodar HTML filnamnet. En cyberattacker kan ange ett skadligt filnamn, inklusive fullständiga sökvägar eller relativa sökvägar. Applikationer bör:

  • Ta bort sökvägen från det filnamn som användaren har angett.
  • Spara det HTML-kodade filnamnet utan sökväg för användargränssnittet eller loggning.
  • Generera ett nytt slumpmässigt filnamn för lagring.

Följande kod tar bort sökvägen från filnamnet:

string untrustedFileName = Path.GetFileName(pathName);

Exemplen som har tillhandahållits hittills tar inte hänsyn till säkerhetsöverväganden. Ytterligare information finns i följande avsnitt samt i exempelappen :

När du laddar upp filer med hjälp av modellbindning och IFormFilekan åtgärdsmetoden acceptera:

Not

Bindningen matchar formulärfiler efter namn. Html-name-värdet i <input type="file" name="formFile"> måste till exempel matcha C#-parametern/egenskapsgränsen (FormFile). Mer information finns i avsnittet Matcha namnattribut till parameternamnet för POST-metoden.

Följande exempel:

  • Loopar genom en eller flera uppladdade filer.
  • Använder Path.GetTempFileName för att returnera en fullständig sökväg för en fil, inklusive filnamnet.
  • Sparar filerna i det lokala filsystemet med hjälp av ett filnamn som genereras av appen.
  • Returnerar det totala antalet och storleken på filer som laddats upp.
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 });
}

Använd Path.GetRandomFileName för att generera ett filnamn utan sökväg. I följande exempel hämtas sökvägen från konfigurationen:

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

Sökvägen som skickas till FileStreammåste innehålla filnamnet. Om filnamnet inte anges utlöses en UnauthorizedAccessException under körning.

Filer som laddas upp med hjälp av tekniken IFormFile buffrade i minnet eller på disken på servern innan de bearbetas. Inuti åtgärdsmetoden är IFormFile-innehållet tillgängligt som en Stream. Förutom det lokala filsystemet kan filer sparas på en nätverksenhet eller till en fillagringstjänst, såsom Azure Blob Storage.

Ett annat exempel som loopar över flera filer för uppladdning och använder säkra filnamn finns i Pages/BufferedMultipleFileUploadPhysical.cshtml.cs i exempelappen.

Varning

Path.GetTempFileName genererar en IOException om fler än 65 535 filer skapas utan att tidigare temporära filer tas bort. Gränsen på 65 535 filer är en gräns per server. Mer information om den här gränsen för Windows OS finns i kommentarerna i följande avsnitt:

Ladda upp små filer med buffrad modellbindning till en databas

Om du vill lagra binära fildata i en databas med Entity Frameworkdefinierar du en Byte matrisegenskap på entiteten:

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

Ange en sidmodellegenskap för klassen som innehåller en IFormFile:

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

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

    ...
}

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

Anteckning

IFormFile kan användas direkt som en åtgärdsmetodparameter eller som en bunden modellegenskap. I föregående exempel används en bunden modellegenskap.

FileUpload används i formuläret Razor 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>

När formuläret skickas till servern kopierar du IFormFile till en dataström och sparar det som en bytematris i databasen. I följande exempel lagrar _dbContext appens databaskontext:

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

Föregående exempel liknar ett scenario som visas i exempelappen:

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

Varning

Var försiktig när du lagrar binära data i relationsdatabaser, eftersom det kan påverka prestanda negativt.

Förlita dig inte på egenskapen FileName i IFormFile utan validering. Egenskapen FileName ska endast användas i visningssyfte och endast efter HTML-kodning.

Exemplen som tillhandahålls tar inte hänsyn till säkerhetsöverväganden. Ytterligare information tillhandahålls i följande avsnitt och i exempelappen :

Ladda upp stora filer med direktuppspelning

I följande exempel visas hur du använder JavaScript för att strömma en fil till en kontrollantåtgärd. Filens antiforgerytoken genereras med ett anpassat filterattribut och skickas till http-klienthuvudena i stället för i begärandetexten. Eftersom åtgärdsmetoden bearbetar uppladdade data direkt inaktiveras formmodellbindningen av ett annat anpassat filter. I åtgärden läss formulärets innehåll med hjälp av en MultipartReader, som läser varje enskild MultipartSection, bearbetar filen eller lagrar innehållet efter behov. När flerdelsavsnitten har lästs utför åtgärden en egen modellbindning.

Det första sidsvaret läser in formuläret och sparar en falskningsskydds-token i en cookie (via attributet GenerateAntiforgeryTokenCookieAttribute). Attributet använder ASP.NET Cores inbyggda antiforgery-stöd för att ange en cookie med en begärandetoken:

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 används för att inaktivera modellbindning:

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

I exempelappen tillämpas GenerateAntiforgeryTokenCookieAttribute och DisableFormValueModelBindingAttribute som filter på sidprogrammodellerna för /StreamedSingleFileUploadDb och /StreamedSingleFileUploadPhysical i Startup.ConfigureServices med hjälp av Razor Pages-konventioner:

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

Eftersom modellbindningen inte läser formuläret binds inte parametrar som är bundna från formuläret (fråga, väg och rubrik fortsätter att fungera). Åtgärdsmetoden fungerar direkt med egenskapen Request. En MultipartReader används för att läsa varje avsnitt. Nyckel-/värdedata lagras i en KeyValueAccumulator. När avsnitten i flera delar har lästs används innehållet i KeyValueAccumulator för att binda formulärdata till en modelltyp.

Den fullständiga StreamingController.UploadDatabase-metoden för direktuppspelning till en databas med 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));
        }
    }
}

Den fullständiga StreamingController.UploadPhysical-metoden för direktuppspelning till en fysisk plats:

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

I exempelappen hanteras verifieringskontroller av FileHelpers.ProcessStreamedFile.

Validering

Exempelappens FileHelpers-klass visar flera kontroller för buffrade IFormFile och strömmade filuppladdningar. Information om hur du bearbetar IFormFile buffrade filuppladdningar i exempelappen finns i metoden ProcessFormFile i filen Utilities/FileHelpers.cs. Information om hur du bearbetar strömmade filer finns i metoden ProcessStreamedFile i samma fil.

Varning

De valideringsmetoder som visas i exempelappen söker inte igenom innehållet i uppladdade filer. I de flesta produktionsscenarier används ett API för virus/skadlig kodskanner i filen innan filen blir tillgänglig för användare eller andra system.

Även om ämnesexemplet innehåller ett fungerande exempel på valideringstekniker ska du inte implementera klassen FileHelpers i en produktionsapp om du inte:

  • Förstå implementeringen fullt ut.
  • Ändra implementeringen efter behov för appens miljö och specifikationer.

Implementera aldrig urskillningslöst säkerhetskod i en app utan att uppfylla dessa krav.

Innehållsverifiering

Använd ett API för virus/skadlig kod från tredje part på uppladdat innehåll.

Genomsökning av filer kräver serverresurser i scenarier med hög volym. Om prestanda för bearbetning av begäranden minskar på grund av filgenomsökning bör du överväga att avlasta genomsökningen till en bakgrundstjänst, eventuellt en tjänst som körs på en annan server än appens server. Vanligtvis lagras uppladdade filer i ett karantänområde tills bakgrundsvirusskannern kontrollerar dem. När en fil klarar granskning flyttas den till sin vanliga fillagringsplats. De här stegen utförs vanligtvis tillsammans med en databaspost som anger genomsökningsstatus för en fil. Genom att använda en sådan metod fortsätter appen och appservern att fokusera på att svara på begäranden.

Validering av filnamnstillägg

Filnamnstillägget för den uppladdade filen bör kontrolleras mot en lista över tillåtna tillägg. Till exempel:

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
}

Validering av filsignatur

En fils signatur bestäms av de första byteen i början av en fil. Dessa byte kan användas för att ange om tillägget matchar innehållet i filen. Exempelappen kontrollerar filsignaturer efter några vanliga filtyper. I följande exempel kontrolleras filsignaturen för en JPEG-bild mot filen:

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

Om du vill hämta ytterligare filsignaturer använder du en databas för filsignaturer (Google-sökresultat) och officiella filspecifikationer. Samråd med officiella filspecifikationer kan säkerställa att de valda signaturerna är giltiga.

Säkerhet för filnamn

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

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

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

Utanför RazorHtmlEncode alltid filnamnsinnehåll från en användares begäran.

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

Validering av storlek

Begränsa storleken på uppladdade filer.

I exempelappen är storleken på filen begränsad till 2 MB (anges i byte). Gränsen anges via Configuration från filen appsettings.json:

{
  "FileSizeLimit": 2097152
}

FileSizeLimit injiceras i PageModel klasser:

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

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

    ...
}

När en filstorlek överskrider gränsen avvisas filen:

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

Matcha namnattributvärdet med parameternamnet för POST-metoden

I icke-Razor formulär som POST:ar formulärdata eller använder JavaScripts FormData direkt måste namnet som anges i formulärets element eller FormData matcha namnet på parametern i kontrollerns åtgärd.

I följande exempel:

  • När du använder ett <input>-element anges attributet name till värdet battlePlans:

    <input type="file" name="battlePlans" multiple>
    
  • När du använder FormData i JavaScript anges namnet till värdet battlePlans:

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

Använd ett matchande namn för parametern för C#-metoden (battlePlans):

  • För en sidhanterarmetod för Razor Pages med namnet Upload:

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • För en åtgärdsmetod för MVC POST-styrenhet:

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

Server- och appkonfiguration

Gräns för längd på flerdelad komponent

MultipartBodyLengthLimit anger gränsen för längden på varje flerdelstext. Formuläravsnitt som överskrider den här gränsen genererar en InvalidDataException när de parsas. Standardvärdet är 134 217 728 (128 MB). Anpassa gränsen med inställningen MultipartBodyLengthLimit i Startup.ConfigureServices:

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

RequestFormLimitsAttribute används för att ange MultipartBodyLengthLimit för en enda sida eller åtgärd.

I en Razor Pages-app, tillämpa filtret med en konvention i 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);

I en Razor Pages-app eller en MVC-app använder du filtret på sidmodellen eller åtgärdsmetoden:

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

Kestrel maximal storlek för begärandetext

För appar som hanteras av Kestrelär standardstorleken för begärandetexten 30 000 000 byte, vilket är cirka 28,6 MB. Anpassa gränsen med hjälp av serveralternativet 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 används för att ange MaxRequestBodySize- för en enda sida eller åtgärd.

I en Razor Pages-app använder du filtret med en konvention i 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);

I en Razor pages-app eller en MVC-app använder du filtret på sidhanterarklassen eller åtgärdsmetoden:

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

Andra Kestrel gränser

Andra Kestrel begränsningar kan gälla för appar som hanteras av Kestrel:

IIS

Standardgränsen för begäran (maxAllowedContentLength) är 30 000 000 byte, vilket är cirka 28,6 MB. Anpassa gränsen i filen web.config. I följande exempel är gränsen inställd på 50 MB (52 428 800 byte):

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

Inställningen maxAllowedContentLength gäller endast för IIS. Mer information finns i Förfrågningsgränser <requestLimits>.

Öka den maximala storleken på begärandetexten för HTTP-begäran genom att ange IISServerOptions.MaxRequestBodySize i Startup.ConfigureServices. I följande exempel är gränsen inställd på 50 MB (52 428 800 byte):

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

Mer information finns i Host ASP.NET Core i Windows med IIS.

Felsöka

Nedan visas några vanliga problem som uppstår när du arbetar med att ladda upp filer och deras möjliga lösningar.

Det gick inte att hitta felet när det distribuerades till en IIS-server

Följande fel anger att den uppladdade filen överskrider serverns konfigurerade innehållslängd:

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

Mer information finns i avsnittet IIS.

Anslutningsfel

Ett anslutningsfel och en återställd serveranslutning indikerar förmodligen att den uppladdade filen överskrider den maximala storleken på Kestrel:s förfrågningskropp. För ytterligare information, se avsnittet Kestrel om maximal begärandetextstorlek. Kestrel klientanslutningsgränser kan också kräva justering.

Null-referensfel med IFormFile

Om kontrollanten accepterar uppladdade filer med IFormFile men värdet är nullkontrollerar du att HTML-formuläret anger ett enctype värde för multipart/form-data. Om det här attributet inte har angetts för elementet <form> sker inte filuppladdningen och eventuella bundna IFormFile argument null. Bekräfta också att uppladdningsnamnet i formulärdata matchar appens namngivning.

Dataströmmen var för stor

Exemplen i det här avsnittet förlitar sig på MemoryStream för att lagra den uppladdade filens innehåll. Storleksgränsen för en MemoryStream är int.MaxValue. Om appens scenario för filuppladdning kräver att filinnehållet är större än 50 MB använder du en alternativ metod som inte förlitar sig på en enda MemoryStream för att lagra en uppladdad fils innehåll.

Ytterligare resurser