Come accettare un file POST


254

Sto usando asp.net mvc 4 webapi beta per creare un servizio di riposo. Devo essere in grado di accettare immagini / file POST dalle applicazioni client. È possibile utilizzare la webapi? Di seguito è riportato come azione sto attualmente utilizzando. Qualcuno sa di un esempio come dovrebbe funzionare?

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

3
Funziona solo con MVC e non con il framework WebAPI.
Phil

Dovresti essere in grado di prendere l'oggetto daRequest.Files
Tejs il

7
ApiController non contiene HttpRequestBase che ha la proprietà Files. L'oggetto Request è basato sulla classe HttpRequestMessage.
Phil

Risposte:


168

vedi http://www.asp.net/web-api/overview/formats-and-model-binding/html-forms-and-multipart-mime#multipartmime , anche se penso che l'articolo lo faccia sembrare un po 'più complicato di è davvero.

Fondamentalmente,

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

5
Qual è il vantaggio dell'utilizzo di un'attività per leggere un solo file? Domanda genuina, sto appena iniziando a usare Task. Dalla mia attuale comprensione, questo codice è davvero adatto quando si carica più di un file corretto?
Chris,

48
MultipartFormDataStreamProvider non ha più la proprietà BodyPartFileNames (in WebApi RTM). Vedi asp.net/web-api/overview/working-with-http/…
Shrike,

5
Ragazzi, qualcuno di voi può far luce sul perché non possiamo semplicemente accedere ai file utilizzando HttpContext.Current.Request.Files e invece è necessario utilizzare questo fantasia MultipartFormDataStreamProvider? L'intera domanda: stackoverflow.com/questions/17967544 .
Niaher

7
I file vengono salvati come BodyPart_8b77040b-354b-464c-bc15-b3591f98f30f . Non dovrebbero essere salvati come pic.jpg esattamente come sul client?
lbrahim,

10
MultipartFormDataStreamProvidernon espone più la BodyPartFileNamesproprietà, ho usato FileData.First().LocalFileNameinvece.
Chtiwi Malek,

374

Sono sorpreso che molti di voi sembrano voler salvare i file sul server. La soluzione per mantenere tutto in memoria è la seguente:

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

34
Mantenere i file in memoria può essere utile se non si desidera spendere spazio su disco. Tuttavia, se si consente il caricamento di file di grandi dimensioni, tenerli in memoria significa che il proprio server Web consumerà molta memoria, che non può essere spesa per conservare materiale per altre richieste. Ciò causerà problemi sui server che funzionano a carico elevato.
Willem Meints,

21
@ W.Meints Comprendo le ragioni per voler archiviare i dati, ma non capisco perché qualcuno voglia archiviare i dati caricati sullo spazio del disco del server. Dovresti sempre mantenere l'archiviazione dei file isolata dal web server, anche per progetti più piccoli.
Gleno,

1
Assicurati che la dimensione del tuo file pubblicato sia inferiore a 64k, il comportamento predefinito è ignorare le richieste altrimenti, ero bloccato su questo per un tempo di log.
Gary Davies,

3
Sfortunatamente, MultipartMemoryStreamProvider non aiuta se si desidera leggere anche i dati del modulo. Volevo creare qualcosa di simile a un MultipartFormDataMemoryStreamProvider ma così tante classi e classi di supporto sono interne allo aspnetwebstack :(
martinoss

9
File.WriteAllBytes(filename, buffer);per scriverlo in un file
pomber,

118

Vedi il codice qui sotto, adattato da questo articolo , che dimostra il codice di esempio più semplice che ho trovato. Include upload di file e memoria (più veloci).

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

26
HttpContext.Current è nullo quando WebAPI è ospitato in OWIN che è un contenitore self hosting.
Zach,

1
Risolto il problema in questo modo: var httpRequest = System.Web.HttpContext.Current.Request;
msysmilu,

7
Non utilizzare System.Web in WebAPI a meno che non sia assolutamente necessario.
Kugel,

3
Certamente, System.Web è strettamente associato a IIS. Se stai lavorando all'interno di OWIN piple line o .Net Core, queste API non saranno disponibili durante l'esecuzione su Linux o in hosting autonomo.
Kugel,

2
Bella risposta. Solo un dettaglio: se stai caricando da una pagina HTML, il tag <input type = "file" /> deve avere un attributo "name", altrimenti il ​​file non sarà presente in HttpContext.Current.Request.Files.
GBU

18

Il modo ASP.NET Core è ora qui :

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

16

Ecco una soluzione rapida e sporca che prende il contenuto del file caricato dal corpo HTTP e lo scrive in un file. Ho incluso uno snippet HTML / JS "bare bones" per il caricamento del file.

Metodo API Web:

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

Caricamento file HTML:

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

Attenzione, tuttavia, ciò non funzionerà con i caricamenti di moduli multipart "normali".
Tom,

3
@Tom cosa significa?
Chazt3n,

Significa che non è compatibile con i browser in cui JavaScript è disabilitato / inesistente, ad esempio Netscape 1. *.
Mikael Dúi Bolinder

13

Ho usato la risposta di Mike Wasson prima di aggiornare tutti i NuGet nel mio progetto webapi mvc4. Una volta fatto, ho dovuto riscrivere l'azione di caricamento del file:

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

Apparentemente BodyPartFileNames non è più disponibile in MultipartFormDataStreamProvider.


In WebApi RTM BodyPartFileNames è stato modificato in FileData. Vedi esempio aggiornato su asp.net/web-api/overview/working-with-http/…
Mark van Straten,

Perché non usare solo la raccolta System.Web.HttpContext.Current.Request.Files?
ADOConnection

Sto pensando di usare il tuo metodo con 2 riserve: 1) Questo non scrive due volte: i) in ReadAsMultipartAsynce ii) In File.Move? 2) potresti farlo async File.Move?
seebiscuit,

1) Non ho avuto problemi con due scritture, l'URL viene chiamato due volte? 2) potresti fare Task.Run (() => {File.Move (src, dest);});
Steve Stokes,

10

Verso queste stesse indicazioni, sto pubblicando snipet client e server che inviano file Excel utilizzando WebApi, 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
    }
}

E il controller webapi del server:

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

E il MyMultipartFormDataStreamProvider personalizzato, necessario per personalizzare il nome file:

PS: ho preso questo codice da un altro post 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);
    }
}

Potresti mostrare come ti chiami static method SetFilenel tuo controller?

Questa è una buona risposta L'ampliamento del provider di base in questo modo consente anche di controllare il flusso e offre maggiore flessibilità rispetto alla semplice fornitura di un patharchivio cloud.
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);

    }
}

6
Penso che l'utente abbia bisogno di qualche spiegazione ...!
kamesh,

4

Ecco due modi per accettare un file. Uno che utilizza nel provider di memoria MultipartMemoryStreamProvider e uno che utilizza MultipartFormDataStreamProvider che salva su un disco. Nota, questo è solo per un caricamento di file alla volta. Puoi certo estenderlo per salvare più file. Il secondo approccio può supportare file di grandi dimensioni. Ho testato file di oltre 200 MB e funziona benissimo. L'uso dell'approccio in memoria non richiede il salvataggio su disco, ma genererà un'eccezione di memoria se si supera un determinato limite.

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


1

Questa domanda ha molte buone risposte anche per .Net Core. Stavo usando entrambi i Frameworks, gli esempi di codice forniti funzionano bene. Quindi non lo ripeterò. Nel mio caso la cosa importante era come usare le azioni di caricamento dei file con Swagger in questo modo:

Pulsante di caricamento file in Swagger

Ecco il mio riassunto:

ASP .Net WebAPI 2

  • Per caricare il file usa: MultipartFormDataStreamProvider vedi le risposte qui
  • Come usarlo con Swagger

.NET Core


1

Controller API:

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

0

A complemento della risposta di Matt Frear - Questa sarebbe un'alternativa ASP NET Core per leggere il file direttamente da Stream, senza salvarlo e leggerlo dal disco:

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}");
        }
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.