So akzeptieren Sie einen Datei-POST

254

Ich verwende asp.net mvc 4 webapi beta, um einen Rest-Service zu erstellen. Ich muss in der Lage sein, POST-Bilder / -Dateien von Clientanwendungen zu akzeptieren. Ist das mit dem Webapi möglich? Unten sehen Sie, wie ich die Aktion derzeit verwende. Kennt jemand ein Beispiel, wie das funktionieren soll?

[HttpPost]
public string ProfileImagePost(HttpPostedFile profileImage)
{
    string[] extensions = { ".jpg", ".jpeg", ".gif", ".bmp", ".png" };
    if (!extensions.Any(x => x.Equals(Path.GetExtension(profileImage.FileName.ToLower()), StringComparison.OrdinalIgnoreCase)))
    {
        throw new HttpResponseException("Invalid file type.", HttpStatusCode.BadRequest);
    }

    // Other code goes here

    return "/path/to/image.png";
}
Phil
quelle
3
Das funktioniert nur mit MVC, nicht mit dem WebAPI-Framework.
Phil
Sie sollten in der Lage sein, nur den Artikel vonRequest.Files
Tejs
7
Der ApiController enthält keine HttpRequestBase mit der Eigenschaft Files. Das Request-Objekt basiert auf der HttpRequestMessage-Klasse.
Phil

Antworten:

168

siehe http://www.asp.net/web-api/overview/formats-and-model-binding/html-forms-and-multipart-mime#multipartmime , obwohl ich denke, dass der Artikel es etwas komplizierter erscheinen lässt als ist es wirklich.

Grundsätzlich,

public Task<HttpResponseMessage> PostFile() 
{ 
    HttpRequestMessage request = this.Request; 
    if (!request.Content.IsMimeMultipartContent()) 
    { 
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); 
    } 

    string root = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/uploads"); 
    var provider = new MultipartFormDataStreamProvider(root); 

    var task = request.Content.ReadAsMultipartAsync(provider). 
        ContinueWith<HttpResponseMessage>(o => 
    { 

        string file1 = provider.BodyPartFileNames.First().Value;
        // this is the file name on the server where the file was saved 

        return new HttpResponseMessage() 
        { 
            Content = new StringContent("File uploaded.") 
        }; 
    } 
    ); 
    return task; 
} 
Mike Wasson
quelle
5
Was ist der Vorteil der Verwendung einer Aufgabe zum Lesen nur einer Datei? Echte Frage, ich fange gerade an, Aufgaben zu verwenden. Nach meinem derzeitigen Verständnis ist dieser Code wirklich geeignet, wenn mehr als eine Datei hochgeladen wird.
Chris
48
MultipartFormDataStreamProvider verfügt nicht mehr über die BodyPartFileNames-Eigenschaft (in WebApi RTM). Siehe asp.net/web-api/overview/working-with-http/…
Shrike
5
Leute, kann jemand von euch bitte etwas Licht ins Dunkel bringen, warum wir nicht einfach mit HttpContext.Current.Request.Files auf Dateien zugreifen können und stattdessen diesen schicken MultipartFormDataStreamProvider verwenden müssen? Die vollständige Frage: stackoverflow.com/questions/17967544 .
Niaher
7
Dateien werden als BodyPart_8b77040b-354b-464c-bc15-b3591f98f30f gespeichert . Sollten sie nicht wie pic.jpg genau so gespeichert werden , wie es auf dem Client war?
lbrahim
10
MultipartFormDataStreamProvidermacht keine BodyPartFileNamesEigenschaft mehr verfügbar, ich habe FileData.First().LocalFileNamestattdessen verwendet.
Chtiwi Malek
374

Ich bin überrascht, dass viele von Ihnen anscheinend Dateien auf dem Server speichern möchten. Die Lösung, um alles im Speicher zu behalten, lautet wie folgt:

[HttpPost("api/upload")]
public async Task<IHttpActionResult> Upload()
{
    if (!Request.Content.IsMimeMultipartContent())
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); 

    var provider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.Contents)
    {
        var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
        var buffer = await file.ReadAsByteArrayAsync();
        //Do whatever you want with filename and its binary data.
    }

    return Ok();
}
Gleno
quelle
34
Das Speichern der Dateien kann hilfreich sein, wenn Sie keinen Speicherplatz benötigen. Wenn Sie jedoch zulassen, dass große Dateien hochgeladen werden, bedeutet dies, dass Ihr Webserver viel Speicherplatz verbraucht, der nicht für die Aufbewahrung von Dateien für andere Anforderungen aufgewendet werden kann. Dies führt zu Problemen auf Servern, die unter hoher Last arbeiten.
Willem Meints
21
@ W.Meints Ich verstehe die Gründe für das Speichern von Daten, aber ich verstehe nicht, warum jemand hochgeladene Daten auf dem Serverspeicherplatz speichern möchte. Sie sollten den Dateispeicher immer vom Webserver isoliert halten - auch bei kleineren Projekten.
Gleno
1
Stellen Sie sicher, dass Ihre veröffentlichte Dateigröße weniger als 64 KB beträgt. Das Standardverhalten besteht darin, Anforderungen zu ignorieren. Andernfalls habe ich eine Protokollzeit lang daran festgehalten.
Gary Davies
3
Leider hilft der MultipartMemoryStreamProvider nicht, wenn Sie auch Formulardaten lesen möchten. Wollte so etwas wie einen MultipartFormDataMemoryStreamProvider erstellen, aber so viele Klassen und Hilfsklassen sind intern im aspnetwebstack :(
martinoss
9
File.WriteAllBytes(filename, buffer);um es in eine Datei zu schreiben
pomber
118

Siehe den folgenden Code aus diesem Artikel , der den einfachsten Beispielcode zeigt, den ich finden konnte. Es enthält sowohl Datei- als auch Speicher- Uploads (schneller).

public HttpResponseMessage Post()
{
    var httpRequest = HttpContext.Current.Request;
    if (httpRequest.Files.Count < 1)
    {
        return Request.CreateResponse(HttpStatusCode.BadRequest);
    }

    foreach(string file in httpRequest.Files)
    {
        var postedFile = httpRequest.Files[file];
        var filePath = HttpContext.Current.Server.MapPath("~/" + postedFile.FileName);
        postedFile.SaveAs(filePath);
        // NOTE: To store in memory use postedFile.InputStream
    }

    return Request.CreateResponse(HttpStatusCode.Created);
}
Brent Matzelle
quelle
26
HttpContext.Current ist null, wenn WebAPI in OWIN gehostet wird, einem selbsthostenden Container.
Zach
1
Es wurde wie folgt behoben: var httpRequest = System.Web.HttpContext.Current.Request;
Msysmilu
7
Verwenden Sie System.Web nicht in WebAPI, es sei denn, Sie müssen dies unbedingt tun.
Kugel
3
Sicher, System.Web ist eng mit IIS verbunden. Wenn Sie in OWIN Piple Line oder .Net Core arbeiten, sind diese APIs nicht verfügbar, wenn sie unter Linux oder selbst gehostet ausgeführt werden.
Kugel
2
Gute Antwort. Nur ein Detail: Wenn Sie das Hochladen von einer HTML - Seite sind, die <input type = „file“ /> Tag muss ein Attribut „name“ haben, oder die Datei wird in HttpContext.Current.Request.Files nicht vorhanden sein.
GBU
17

Der ASP.NET Core-Weg ist jetzt hier :

[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
    long size = files.Sum(f => f.Length);

    // full path to file in temp location
    var filePath = Path.GetTempFileName();

    foreach (var formFile in files)
    {
        if (formFile.Length > 0)
        {
            using (var stream = new FileStream(filePath, FileMode.Create))
            {
                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, filePath});
}
Matt Frear
quelle
16

Hier ist eine schnelle und schmutzige Lösung, die hochgeladene Dateiinhalte aus dem HTTP-Body entnimmt und in eine Datei schreibt. Ich habe ein HTML / JS-Snippet "Bare Bones" für den Datei-Upload hinzugefügt.

Web-API-Methode:

[Route("api/myfileupload")]        
[HttpPost]
public string MyFileUpload()
{
    var request = HttpContext.Current.Request;
    var filePath = "C:\\temp\\" + request.Headers["filename"];
    using (var fs = new System.IO.FileStream(filePath, System.IO.FileMode.Create))
    {
        request.InputStream.CopyTo(fs);
    }
    return "uploaded";
}

Hochladen von HTML-Dateien:

<form>
    <input type="file" id="myfile"/>  
    <input type="button" onclick="uploadFile();" value="Upload" />
</form>
<script type="text/javascript">
    function uploadFile() {        
        var xhr = new XMLHttpRequest();                 
        var file = document.getElementById('myfile').files[0];
        xhr.open("POST", "api/myfileupload");
        xhr.setRequestHeader("filename", file.name);
        xhr.send(file);
    }
</script>
James Lawruk
quelle
Beachten Sie jedoch, dass dies bei "normalen" mehrteiligen Formular-Uploads nicht funktioniert.
Tom
3
@ Tom was bedeutet das?
Chazt3n
Dies bedeutet, dass es nicht mit Browsern kompatibel ist, in denen JavaScript deaktiviert / nicht vorhanden ist, z. B. Netscape 1. *.
Mikael Dúi Bolinder
13

Ich habe die Antwort von Mike Wasson verwendet, bevor ich alle NuGets in meinem Webapi MVC4-Projekt aktualisiert habe. Nachdem ich dies getan hatte, musste ich die Aktion zum Hochladen von Dateien neu schreiben:

    public Task<HttpResponseMessage> Upload(int id)
    {
        HttpRequestMessage request = this.Request;
        if (!request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType));
        }

        string root = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/uploads");
        var provider = new MultipartFormDataStreamProvider(root);

        var task = request.Content.ReadAsMultipartAsync(provider).
            ContinueWith<HttpResponseMessage>(o =>
            {
                FileInfo finfo = new FileInfo(provider.FileData.First().LocalFileName);

                string guid = Guid.NewGuid().ToString();

                File.Move(finfo.FullName, Path.Combine(root, guid + "_" + provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", "")));

                return new HttpResponseMessage()
                {
                    Content = new StringContent("File uploaded.")
                };
            }
        );
        return task;
    }

Anscheinend ist BodyPartFileNames im MultipartFormDataStreamProvider nicht mehr verfügbar.

Steve Stokes
quelle
In WebApi RTM wurde der BodyPartFileNames in FileData geändert. Siehe aktualisiertes Beispiel unter asp.net/web-api/overview/working-with-http/…
Mark van Straten
Warum nicht einfach die System.Web.HttpContext.Current.Request.Files-Auflistung verwenden?
ADOConnection
Ich denke darüber nach, Ihre Methode mit 2 Vorbehalten zu verwenden: 1) Schreibt dies nicht zweimal: i) in ReadAsMultipartAsyncund ii) in File.Move? 2) Könnten Sie tun async File.Move?
Seebiscuit
1) Ich hatte keine Probleme mit zwei Schreibvorgängen. Wird die URL zweimal aufgerufen? 2) Sie könnten Task.Run (() => {File.Move (src, dest);}) ausführen;
Steve Stokes
10

In diese Richtung poste ich Client- und Server-Snipets, die Excel-Dateien mit WebApi senden, c # 4:

public static void SetFile(String serviceUrl, byte[] fileArray, String fileName)
{
    try
    {
        using (var client = new HttpClient())
        {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                using (var content = new MultipartFormDataContent())
                {
                    var fileContent = new ByteArrayContent(fileArray);//(System.IO.File.ReadAllBytes(fileName));
                    fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                    {
                        FileName = fileName
                    };
                    content.Add(fileContent);
                    var result = client.PostAsync(serviceUrl, content).Result;
                }
        }
    }
    catch (Exception e)
    {
        //Log the exception
    }
}

Und der Server-Webapi-Controller:

public Task<IEnumerable<string>> Post()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        string fullPath = HttpContext.Current.Server.MapPath("~/uploads");
        MyMultipartFormDataStreamProvider streamProvider = new MyMultipartFormDataStreamProvider(fullPath);
        var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith(t =>
        {
            if (t.IsFaulted || t.IsCanceled)
                    throw new HttpResponseException(HttpStatusCode.InternalServerError);

            var fileInfo = streamProvider.FileData.Select(i =>
            {
                var info = new FileInfo(i.LocalFileName);
                return "File uploaded as " + info.FullName + " (" + info.Length + ")";
            });
            return fileInfo;

        });
        return task;
    }
    else
    {
        throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "Invalid Request!"));
    }
}

Und der benutzerdefinierte MyMultipartFormDataStreamProvider musste den Dateinamen anpassen:

PS: Ich habe diesen Code einem anderen Beitrag entnommen: http://www.codeguru.com/csharp/.net/uploading-files-asynchronously-using-asp.net-web-api .htm

public class MyMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
    public MyMultipartFormDataStreamProvider(string path)
        : base(path)
    {

    }

    public override string GetLocalFileName(System.Net.Http.Headers.HttpContentHeaders headers)
    {
        string fileName;
        if (!string.IsNullOrWhiteSpace(headers.ContentDisposition.FileName))
        {
            fileName = headers.ContentDisposition.FileName;
        }
        else
        {
            fileName = Guid.NewGuid().ToString() + ".data";
        }
        return fileName.Replace("\"", string.Empty);
    }
}
Daniel Melo
quelle
Können Sie zeigen, wie Sie static method SetFilein Ihrem Controller anrufen ?
Das ist eine gute Antwort. Wenn Sie den Basisanbieter auf diese Weise erweitern, können Sie auch den Stream steuern und erhalten mehr Flexibilität als nur die Bereitstellung eines pathCloud-Speichers.
Phil Cooper
6
[HttpPost]
public JsonResult PostImage(HttpPostedFileBase file)
{
    try
    {
        if (file != null && file.ContentLength > 0 && file.ContentLength<=10485760)
        {
            var fileName = Path.GetFileName(file.FileName);                                        

            var path = Path.Combine(Server.MapPath("~/") + "HisloImages" + "\\", fileName);

            file.SaveAs(path);
            #region MyRegion
            ////save imag in Db
            //using (MemoryStream ms = new MemoryStream())
            //{
            //    file.InputStream.CopyTo(ms);
            //    byte[] array = ms.GetBuffer();
            //} 
            #endregion
            return Json(JsonResponseFactory.SuccessResponse("Status:0 ,Message: OK"), JsonRequestBehavior.AllowGet);
        }
        else
        {
            return Json(JsonResponseFactory.ErrorResponse("Status:1 , Message: Upload Again and File Size Should be Less Than 10MB"), JsonRequestBehavior.AllowGet);
        }
    }
    catch (Exception ex)
    {

        return Json(JsonResponseFactory.ErrorResponse(ex.Message), JsonRequestBehavior.AllowGet);

    }
}
user3722373
quelle
6
Ich denke, Benutzer brauchen eine Erklärung ...!
Kamesh
4

Hier sind zwei Möglichkeiten, eine Datei zu akzeptieren. Eine verwendet im Speicheranbieter MultipartMemoryStreamProvider und eine verwendet MultipartFormDataStreamProvider, der auf einer Festplatte speichert. Beachten Sie, dass dies jeweils nur für einen Datei-Upload gilt. Sie können dies mit Sicherheit erweitern, um mehrere Dateien zu speichern. Der zweite Ansatz kann große Dateien unterstützen. Ich habe Dateien über 200 MB getestet und es funktioniert gut. Bei Verwendung des In-Memory-Ansatzes müssen Sie nicht auf der Festplatte speichern, es wird jedoch eine Speicherausnahme ausgelöst, wenn Sie eine bestimmte Grenze überschreiten.

private async Task<Stream> ReadStream()
{
    Stream stream = null;
    var provider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.Contents)
    {
        var buffer = await file.ReadAsByteArrayAsync();
        stream = new MemoryStream(buffer);
    }

    return stream;
}

private async Task<Stream> ReadLargeStream()
{
    Stream stream = null;
    string root = Path.GetTempPath();
    var provider = new MultipartFormDataStreamProvider(root);
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.FileData)
    {
        var path = file.LocalFileName;
        byte[] content = File.ReadAllBytes(path);
        File.Delete(path);
        stream = new MemoryStream(content);
    }

    return stream;
}
Filix Mogilevsky
quelle
1

Ich hatte ein ähnliches Problem mit der Vorschau-Web-API. Dieser Teil wurde noch nicht auf die neue MVC 4-Web-API portiert, aber möglicherweise hilft dies:

REST-Datei-Upload mit HttpRequestMessage oder Stream?

Bitte lassen Sie es mich wissen, können Sie sich morgen hinsetzen und versuchen, es erneut umzusetzen.

Remy
quelle
1

Diese Frage hat auch für .Net Core viele gute Antworten. Ich habe beide Frameworks verwendet. Die bereitgestellten Codebeispiele funktionieren einwandfrei. Also werde ich es nicht wiederholen. In meinem Fall war es wichtig, wie man Datei-Upload-Aktionen mit Swagger wie folgt verwendet :

Schaltfläche zum Hochladen von Dateien in Swagger

Hier ist meine Zusammenfassung:

ASP .Net WebAPI 2

.NET Core

Haupt
quelle
1

API-Controller:

[HttpPost]
public HttpResponseMessage Post()
{
    var httpRequest = System.Web.HttpContext.Current.Request;

    if (System.Web.HttpContext.Current.Request.Files.Count < 1)
    {
        //TODO
    }
    else
    {

    try
    { 
        foreach (string file in httpRequest.Files)
        { 
            var postedFile = httpRequest.Files[file];
            BinaryReader binReader = new BinaryReader(postedFile.InputStream);
            byte[] byteArray = binReader.ReadBytes(postedFile.ContentLength);

        }

    }
    catch (System.Exception e)
    {
        //TODO
    }

    return Request.CreateResponse(HttpStatusCode.Created);
}
Tiago Medici
quelle
0

Ergänzung zu Matt Frears Antwort - Dies wäre eine ASP NET Core-Alternative zum Lesen der Datei direkt aus dem Stream, ohne sie zu speichern und von der Festplatte zu lesen:

public ActionResult OnPostUpload(List<IFormFile> files)
    {
        try
        {
            var file = files.FirstOrDefault();
            var inputstream = file.OpenReadStream();

            XSSFWorkbook workbook = new XSSFWorkbook(stream);

            var FIRST_ROW_NUMBER = {{firstRowWithValue}};

            ISheet sheet = workbook.GetSheetAt(0);
            // Example: var firstCellRow = (int)sheet.GetRow(0).GetCell(0).NumericCellValue;

            for (int rowIdx = 2; rowIdx <= sheet.LastRowNum; rowIdx++)
               {
                  IRow currentRow = sheet.GetRow(rowIdx);

                  if (currentRow == null || currentRow.Cells == null || currentRow.Cells.Count() < FIRST_ROW_NUMBER) break;

                  var df = new DataFormatter();                

                  for (int cellNumber = {{firstCellWithValue}}; cellNumber < {{lastCellWithValue}}; cellNumber++)
                      {
                         //business logic & saving data to DB                        
                      }               
                }
        }
        catch(Exception ex)
        {
            throw new FileFormatException($"Error on file processing - {ex.Message}");
        }
    }
Pedro Coelho
quelle