Cómo aceptar un archivo POST

254

Estoy usando asp.net mvc 4 webapi beta para construir un servicio de descanso. Necesito poder aceptar imágenes / archivos POST de aplicaciones de cliente. ¿Es esto posible usando el webapi? A continuación se muestra cómo acción estoy usando actualmente. ¿Alguien sabe de un ejemplo de cómo debería funcionar esto?

[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
fuente
3
Eso solo funciona con MVC, no con el marco de WebAPI.
Phil
Deberías poder tomar el artículo deRequest.Files
Tejs
77
El ApiController no contiene HttpRequestBase que tiene la propiedad Archivos. Su objeto Request se basa en la clase HttpRequestMessage.
Phil

Respuestas:

168

ver http://www.asp.net/web-api/overview/formats-and-model-binding/html-forms-and-multipart-mime#multipartmime , aunque creo que el artículo hace que parezca un poco más complicado que realmente es.

Básicamente,

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
fuente
55
¿Cuál es el beneficio de usar una tarea para leer solo un archivo? Pregunta genuina, recién estoy empezando a usar Tareas. Desde mi comprensión actual, este código es realmente adecuado para cuando se carga más de un archivo correcto?
Chris
48
MultipartFormDataStreamProvider ya no tiene la propiedad BodyPartFileNames (en WebApi RTM). Ver asp.net/web-api/overview/working-with-http/…
Shrike
55
Chicos, ¿puede alguno de ustedes arrojar algo de luz sobre por qué no podemos simplemente acceder a los archivos usando HttpContext.Current.Request.Files y en su lugar necesitamos usar este elegante MultipartFormDataStreamProvider? La pregunta completa: stackoverflow.com/questions/17967544 .
niaher
77
Los archivos se guardan como BodyPart_8b77040b-354b-464c-bc15-b3591f98f30f . ¿No deberían guardarse como pic.jpg exactamente como estaba en el cliente?
lbrahim
10
MultipartFormDataStreamProviderya no expone la BodyPartFileNamespropiedad, usé en su FileData.First().LocalFileNamelugar.
Chtiwi Malek
374

Me sorprende que muchos de ustedes parezcan querer guardar archivos en el servidor. La solución para mantener todo en la memoria es la siguiente:

[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
fuente
34
Mantener los archivos en la memoria puede ser útil si no desea gastar espacio en disco. Sin embargo, si permite que se carguen archivos grandes, mantenerlos en la memoria significa que su servidor web usará mucha memoria, que no puede gastarse en guardar cosas para otras solicitudes. Esto causará problemas en los servidores que funcionan bajo alta carga.
Willem Meints
21
@ W.Meints Entiendo las razones para querer almacenar datos, pero no entiendo por qué alguien querría almacenar datos cargados en el espacio en disco del servidor. Siempre debe mantener el almacenamiento de archivos aislado del servidor web, incluso para proyectos más pequeños.
Gleno
1
Asegúrese de que el tamaño de su archivo publicado sea inferior a 64k, el comportamiento predeterminado es ignorar las solicitudes, de lo contrario, me quedé atrapado en esto por un tiempo de registro.
Gary Davies
3
Desafortunadamente, el MultipartMemoryStreamProvider no ayuda si también desea leer los datos del formulario. Quería crear algo así como un MultipartFormDataMemoryStreamProvider, pero muchas clases y clases auxiliares son internas en aspnetwebstack :(
martinoss
99
File.WriteAllBytes(filename, buffer);escribirlo en un archivo
pomber
118

Vea el código a continuación, adaptado de este artículo , que demuestra el código de ejemplo más simple que pude encontrar. Incluye cargas de archivos y de memoria (más rápidas).

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
fuente
26
HttpContext.Current es nulo cuando WebAPI está alojado en OWIN, que es un contenedor de alojamiento propio.
Zach
1
Lo arregló así: var httpRequest = System.Web.HttpContext.Current.Request;
msysmilu
77
No use System.Web en WebAPI a menos que sea absolutamente necesario.
Kugel
3
Claro, System.Web está estrechamente acoplado a IIS. Si está trabajando dentro de OWIN piple line o .Net Core, esa API no estará disponible cuando se ejecute en Linux o sea autohospedado.
Kugel
2
Gran respuesta. Solo un detalle: si está cargando desde una página HTML, la etiqueta <input type = "file" /> debe tener un atributo "name", o el archivo no estará presente en HttpContext.Current.Request.Files.
GBU
18

El modo ASP.NET Core ahora está aquí :

[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
fuente
16

Aquí hay una solución rápida y sucia que toma el contenido del archivo cargado del cuerpo HTTP y lo escribe en un archivo. Incluí un fragmento HTML / JS "básico" para la carga del archivo.

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

Carga de archivos 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
fuente
Sin embargo, tenga en cuenta que esto no funcionará con cargas de formularios multiparte 'normales'.
Tom
3
@ Tom, ¿qué significa eso?
Chazt3n
Significa que no es compatible con los navegadores donde JavaScript está deshabilitado / no existe, por ejemplo, Netscape 1. *.
Mikael Dúi Bolinder
13

Usé la respuesta de Mike Wasson antes de actualizar todos los NuGets en mi proyecto webapi mvc4. Una vez que lo hice, tuve que volver a escribir la acción de carga del archivo:

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

Aparentemente, BodyPartFileNames ya no está disponible en MultipartFormDataStreamProvider.

Steve Stokes
fuente
En WebApi RTM, BodyPartFileNames se ha cambiado a FileData. Ver ejemplo actualizado en asp.net/web-api/overview/working-with-http/…
Mark van Straten
¿Por qué no simplemente usar la colección System.Web.HttpContext.Current.Request.Files?
ADOConnection
Estoy pensando en usar su método con 2 reservas: 1) ¿No escribe esto dos veces: i) in ReadAsMultipartAsyncy ii) In File.Move? 2) ¿podrías hacer async File.Move?
seebiscuit
1) No tuve problemas con dos escrituras, ¿se llama a la url dos veces? 2) puede hacer Task.Run (() => {File.Move (src, dest);});
Steve Stokes
10

Hacia estas mismas instrucciones, estoy publicando fragmentos de un cliente y servidor que envían archivos de Excel usando 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
    }
}

Y el controlador del servidor webapi:

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

Y el Custom MyMultipartFormDataStreamProvider, necesitaba personalizar el nombre del archivo:

PS: tomé este código de otra publicación 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
fuente
¿Podría mostrar cómo lo llama static method SetFileen su controlador?
Esta es una buena respuesta. Ampliar el proveedor base de esta manera también le permite controlar la transmisión y le brinda más flexibilidad que proporcionar solo un pathalmacenamiento en la nube.
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);

    }
}
usuario3722373
fuente
66
Creo que el usuario necesita alguna explicación ...!
kamesh
4

Aquí hay dos formas de aceptar un archivo. Uno que usa el proveedor de memoria MultipartMemoryStreamProvider y otro que usa MultipartFormDataStreamProvider que guarda en un disco. Tenga en cuenta que esto es solo para cargar un archivo a la vez. Puede extender esto con certeza para guardar múltiples archivos. El segundo enfoque puede admitir archivos grandes. He probado archivos de más de 200 MB y funciona bien. El uso del enfoque en memoria no requiere que guarde en el disco, pero eliminará la excepción de memoria si excede un cierto límite.

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

Esta pregunta tiene muchas buenas respuestas incluso para .Net Core. Estaba usando ambos Frameworks, los ejemplos de código proporcionados funcionan bien. Entonces no lo repetiré. En mi caso, lo importante era cómo usar las acciones de carga de archivos con Swagger de esta manera:

Botón de carga de archivos en Swagger

Aquí está mi resumen:

ASP .Net WebAPI 2

  • Para cargar el archivo use: MultipartFormDataStreamProvider vea las respuestas aquí
  • Cómo usarlo con Swagger

.NET Core

Mayor
fuente
1

Controlador 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
fuente
0

Complementando la respuesta de Matt Frear: esta sería una alternativa de ASP NET Core para leer el archivo directamente desde Stream, sin guardarlo ni leerlo desde el 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}");
        }
    }
Pedro Coelho
fuente