Comment accepter un fichier POST

254

J'utilise asp.net mvc 4 webapi beta pour créer un service de repos. Je dois pouvoir accepter les images / fichiers POSTés des applications clientes. Est-ce possible à l'aide de la webapi? Voici comment j'utilise actuellement l'action. Quelqu'un connaît-il un exemple de la façon dont cela devrait fonctionner?

[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
la source
3
Cela ne fonctionne qu'avec MVC et non avec le framework WebAPI.
Phil
Vous devriez être en mesure de récupérer l'objet deRequest.Files
Tejs
7
ApiController ne contient pas le HttpRequestBase qui a la propriété Files. Son objet Request est basé sur la classe HttpRequestMessage.
Phil

Réponses:

168

voir http://www.asp.net/web-api/overview/formats-and-model-binding/html-forms-and-multipart-mime#multipartmime , bien que je pense que l'article donne l'impression que c'est un peu plus compliqué que ça l'est vraiment.

Fondamentalement,

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
la source
5
Quel est l'avantage d'utiliser une tâche pour lire un seul fichier? Véritable question, je commence tout juste à utiliser les tâches. D'après ma compréhension actuelle, ce code est vraiment adapté pour télécharger plusieurs fichiers correctement?
Chris
48
MultipartFormDataStreamProvider n'a plus de propriété BodyPartFileNames (dans WebApi RTM). Voir asp.net/web-api/overview/working-with-http/…
Shrike
5
Les gars, pouvez-vous s'il vous plaît nous expliquer pourquoi nous ne pouvons pas simplement accéder aux fichiers à l'aide de HttpContext.Current.Request.Files et avons plutôt besoin d'utiliser ce MultipartFormDataStreamProvider? La question complète: stackoverflow.com/questions/17967544 .
niaher
7
Les fichiers sont enregistrés sous BodyPart_8b77040b-354b-464c-bc15-b3591f98f30f . Ne doivent-ils pas être enregistrés comme pic.jpg exactement comme ils l'étaient sur le client?
lbrahim
10
MultipartFormDataStreamProvidern'expose plus la BodyPartFileNamespropriété, j'ai utilisé à la FileData.First().LocalFileNameplace.
Chtiwi Malek
374

Je suis surpris que beaucoup d'entre vous semblent vouloir enregistrer des fichiers sur le serveur. La solution pour tout garder en mémoire est la suivante:

[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
la source
34
Garder les fichiers en mémoire peut être utile si vous ne voulez pas dépenser d'espace disque. Cependant, si vous autorisez le téléchargement de fichiers volumineux, le fait de les conserver en mémoire signifie que votre serveur Web utilisera beaucoup de mémoire, ce qui ne peut pas être dépensé pour conserver des éléments pour d'autres demandes. Cela entraînera des problèmes sur les serveurs qui fonctionnent sous une charge élevée.
Willem Meints
21
@ W.Meints Je comprends les raisons de vouloir stocker des données, mais je ne comprends pas pourquoi quelqu'un voudrait stocker des données téléchargées sur l'espace disque du serveur. Vous devez toujours garder le stockage de fichiers isolé du serveur Web - même pour les petits projets.
Gleno
1
Assurez-vous que la taille de votre fichier publié est inférieure à 64 Ko, le comportement par défaut est d'ignorer les demandes sinon, j'étais coincé là-dessus pendant une heure de journal.
Gary Davies
3
Malheureusement, MultipartMemoryStreamProvider n'aide pas si vous souhaitez également lire les données du formulaire. Je voulais créer quelque chose comme un MultipartFormDataMemoryStreamProvider mais tant de classes et de classes d'assistance sont internes dans l'aspnetwebstack :(
martinoss
9
File.WriteAllBytes(filename, buffer);l'écrire dans un fichier
pomber
118

Consultez le code ci-dessous, adapté de cet article , qui illustre l'exemple de code le plus simple que j'ai pu trouver. Il comprend à la fois des téléchargements de fichiers et de mémoire (plus rapides).

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
la source
26
HttpContext.Current est null lorsque WebAPI est hébergé dans OWIN qui est un conteneur auto-hébergé.
Zach
1
Correction comme ceci: var httpRequest = System.Web.HttpContext.Current.Request;
msysmilu
7
N'utilisez pas System.Web dans WebAPI, sauf si vous en avez absolument besoin.
Kugel
3
Bien sûr, System.Web est étroitement lié à IIS. Si vous travaillez sur la ligne de base OWIN ou .Net Core, ces API ne seront pas disponibles lors de l'exécution sous Linux ou auto-hébergées.
Kugel
2
Très bonne réponse. Juste un détail: si vous téléchargez à partir d'une page HTML, la balise <input type = "file" /> doit avoir un attribut "name", sinon le fichier ne sera pas présent dans HttpContext.Current.Request.Files.
GBU
18

La méthode ASP.NET Core est maintenant :

[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
la source
16

Voici une solution rapide et sale qui prend le contenu du fichier téléchargé du corps HTTP et l'écrit dans un fichier. J'ai inclus un extrait "bare bones" HTML / JS pour le téléchargement du fichier.

Méthode 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";
}

Téléchargement de fichiers 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>
James Lawruk
la source
Attention cependant, cela ne fonctionnera pas avec les téléchargements de formulaires en plusieurs parties «normaux».
Tom
3
@Tom qu'est-ce que cela signifie?
Chazt3n
Cela signifie qu'il n'est pas compatible avec les navigateurs où JavaScript est désactivé / inexistant, par exemple Netscape 1. *.
Mikael Dúi Bolinder
13

J'ai utilisé la réponse de Mike Wasson avant de mettre à jour tous les NuGets dans mon projet webapi mvc4. Une fois que je l'ai fait, j'ai dû réécrire l'action de téléchargement de fichier:

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

Apparemment, BodyPartFileNames n'est plus disponible dans MultipartFormDataStreamProvider.

Steve Stokes
la source
Dans WebApi RTM, BodyPartFileNames a été remplacé par FileData. Voir l'exemple mis à jour sur asp.net/web-api/overview/working-with-http/…
Mark van Straten
Pourquoi ne pas simplement utiliser la collection System.Web.HttpContext.Current.Request.Files?
ADOConnection
Je pense utiliser votre méthode avec 2 réserves: 1) N'est-ce pas écrit deux fois: i) dans ReadAsMultipartAsyncet ii) dans File.Move? 2) Pourriez-vous faire async File.Move?
seebiscuit
1) Je n'ai pas eu de problèmes avec deux écritures, l'URL est-elle appelée deux fois? 2) vous pourriez faire Task.Run (() => {File.Move (src, dest);});
Steve Stokes
10

Dans cette même direction, je poste un snipets client et serveur qui envoient des fichiers Excel à l'aide de 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
    }
}

Et le contrôleur webapi du serveur:

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

Et le MyMultipartFormDataStreamProvider personnalisé, nécessaire pour personnaliser le nom de fichier:

PS: j'ai pris ce code d'un autre article 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
la source
Pourriez-vous montrer comment vous appelez-vous static method SetFiledans votre contrôleur?
C'est une bonne réponse. L'extension du fournisseur de base comme celui-ci vous permet également de contrôler le flux et vous donne plus de flexibilité que de fournir uniquement un pathstockage 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);

    }
}
user3722373
la source
6
Je pense que l'utilisateur a besoin d'explications ...!
kamesh
4

Voici deux façons d'accepter un fichier. L'une utilisant le fournisseur de mémoire MultipartMemoryStreamProvider et l'autre utilisant MultipartFormDataStreamProvider qui enregistre sur un disque. Remarque, ce n'est que pour un téléchargement de fichier à la fois. Vous pouvez certainement l'étendre pour enregistrer plusieurs fichiers. La deuxième approche peut prendre en charge des fichiers volumineux. J'ai testé des fichiers de plus de 200 Mo et cela fonctionne très bien. L'utilisation de l'approche en mémoire ne vous oblige pas à enregistrer sur le disque, mais supprimera une exception de mémoire si vous dépassez une certaine 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;
}
Filix Mogilevsky
la source
1

J'ai eu un problème similaire pour l'API Web de prévisualisation. Je n'ai pas encore porté cette partie vers la nouvelle API Web MVC 4, mais cela peut peut-être aider:

Téléchargement de fichier REST avec HttpRequestMessage ou Stream?

Veuillez me le faire savoir, pouvez vous asseoir demain et essayer de le mettre en œuvre à nouveau.

Rémy
la source
1

Cette question a beaucoup de bonnes réponses même pour .Net Core. J'utilisais les deux Frameworks, les exemples de code fournis fonctionnent bien. Je ne vais donc pas le répéter. Dans mon cas, l'important était de savoir comment utiliser les actions de téléchargement de fichiers avec Swagger comme ceci:

Bouton de téléchargement de fichier dans Swagger

Voici mon récapitulatif:

ASP .Net WebAPI 2

  • Pour télécharger un fichier, utilisez: MultipartFormDataStreamProvider voir les réponses ici
  • Comment l' utiliser avec Swagger

.NET Core

Majeur
la source
1

Contrôleur 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);
}
Tiago Medici
la source
0

Compléter la réponse de Matt Frear - Ce serait une alternative ASP NET Core pour lire le fichier directement à partir de Stream, sans l'enregistrer et le lire à partir du disque:

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