2016-04-05 9 views
25

Wenn ich große Dateien auf meine Web-API in ASP.NET Core hochlade, lädt die Runtime die Datei in den Speicher, bevor meine Funktion zum Verarbeiten und Speichern des Uploads ausgelöst wird . Bei großen Uploads wird dies zu einem Problem, da es langsam ist und mehr Speicher benötigt. Für frühere Versionen von ASP.NET there are some articles zum Deaktivieren der Pufferung von Anforderungen, aber ich bin nicht in der Lage, Informationen zu finden, wie dies mit ASP.NET Core zu tun. Ist es möglich, die Pufferung von Anfragen zu deaktivieren, damit auf meinem Server nicht ständig der Speicher ausgeht?Umgang mit großen Dateiuploads auf ASP.NET Core 1.0

+0

Ich schreibe mein Datei-Upload-Backend, um das Hochladen von Dateien in kleinen Blöcken gemäß der API für [flowjs] (https://github.com/flowjs/flow.js) zu unterstützen. – jltrem

+0

Hallo @jltrem, kannst du bitte den asp.net-Core teilen Controller und Winkelcode, der mit flowjs hochgeladene Dateien behandelt? –

Antwort

18

Verwenden Sie die Microsoft.AspNetCore.WebUtilities.MultipartReader, weil es ...

jeden Stream analysieren kann [mit] minimal Pufferung. Es gibt Ihnen die Header und den Body jedes Abschnitts nacheinander und dann machen Sie, was Sie wollen mit dem Körper dieses Abschnitts (Puffer, verwerfen, auf die Festplatte schreiben, etc.).

Hier ist ein Middleware-Beispiel.

app.Use(async (context, next) => 
{ 
    if (!IsMultipartContentType(context.Request.ContentType)) 
    { 
     await next(); 
     return; 
    } 

    var boundary = GetBoundary(context.Request.ContentType); 
    var reader = new MultipartReader(boundary, context.Request.Body); 
    var section = await reader.ReadNextSectionAsync(); 

    while (section != null) 
    { 
     // process each image 
     const int chunkSize = 1024; 
     var buffer = new byte[chunkSize]; 
     var bytesRead = 0; 
     var fileName = GetFileName(section.ContentDisposition); 

     using (var stream = new FileStream(fileName, FileMode.Append)) 
     { 
      do 
      { 
       bytesRead = await section.Body.ReadAsync(buffer, 0, buffer.Length); 
       stream.Write(buffer, 0, bytesRead); 

      } while (bytesRead > 0); 
     } 

     section = await reader.ReadNextSectionAsync(); 
    } 

    context.Response.WriteAsync("Done."); 
}); 

Hier sind die Helfer.

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

private static string GetBoundary(string contentType) 
{ 
    var elements = contentType.Split(' '); 
    var element = elements.Where(entry => entry.StartsWith("boundary=")).First(); 
    var boundary = element.Substring("boundary=".Length); 
    // Remove quotes 
    if (boundary.Length >= 2 && boundary[0] == '"' && 
     boundary[boundary.Length - 1] == '"') 
    { 
     boundary = boundary.Substring(1, boundary.Length - 2); 
    } 
    return boundary; 
} 

private string GetFileName(string contentDisposition) 
{ 
    return contentDisposition 
     .Split(';') 
     .SingleOrDefault(part => part.Contains("filename")) 
     .Split('=') 
     .Last() 
     .Trim('"'); 
} 

Externe Referenzen

+1

Es scheint, dass dieser Code perfekt auf dnx451 funktioniert, aber es hat einen Speicherverlust auf dnxcore50. Könnte etwas sein, das für RC2 behoben wird. – Martin

+1

GetFileName() aus dem obigen Snippet bewirkt, dass der Server die gesamte Datei puffert. Ich ersetzte es durch den Namen der randomisierten Datei, aber nachdem ich alle Blöcke in die Datei geschrieben habe, sehe ich, dass die Speichernutzung um den Betrag der Dateigröße erhöht wird. Es kann tatsächlich in Ordnung sein, wenn weitere GC es loswerden werden. Aber scheint nicht ok – EvAlex

+0

@EvAlex 'GetFileName' analysiert nur eine Zeichenfolge. Gibt es noch etwas, das vor oder nach dem passiert, was dazu führen könnte, dass der Server die gesamte Datei puffert? –

2

In Ihrem Controller Sie können einfach Request.Form.Files verwenden, um die Dateien zuzugreifen:

[HttpPost("upload")] 
public async Task<IActionResult> UploadAsync(CancellationToken cancellationToken) 
{ 
    if (!Request.HasFormContentType) 
     return BadRequest(); 

    var form = Request.Form; 
    foreach(var formFile in form.Files) 
    { 
     using(var readStream = formFile.OpenReadStream()) 
     { 
      // Do something with the uploaded file 
     } 
    } 


    return Ok(); 
} 
Verwandte Themen