Request.Content.ReadAsMultipartAsync nunca regresa

Tengo una API para un sistema escrito usando ASP.NET Web Api y estoy tratando de ampliarlo para permitir que las imágenes se carguen. He hecho algunas búsquedas en Google y encontré cómo la forma recomendada de aceptar archivos usando MultpartMemoryStreamProvider y algunos métodos asíncronos, pero mi espera en ReadAsMultipartAsync nunca regresa.

Aquí está el código:

[HttpPost] public async Task LowResImage(int id) { if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var provider = new MultipartMemoryStreamProvider(); try { await Request.Content.ReadAsMultipartAsync(provider); foreach (var item in provider.Contents) { if (item.Headers.ContentDisposition.FileName != null) { } } return Request.CreateResponse(HttpStatusCode.OK); } catch (System.Exception e) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e); } } 

Puedo recorrer todo el camino hasta:

 await Request.Content.ReadAsMultipartAsync(provider); 

en ese punto nunca se completará.

¿Cuál es la razón por la que mi espera nunca regresa?

Actualizar

Estoy intentando enviar a esta acción usando curl, el comando es el siguiente:

 C:\cURL>curl -i -F filedata=@C:\LowResExample.jpg http://localhost:8000/Api/Photos/89/LowResImage 

También he intentado usar el siguiente html para enviar a la acción y sucede lo mismo:

 

Me encontré con algo similar en .NET 4.0 (no async / await). Utilizando la stack de subprocesos del depurador pude ver que ReadAsMultipartAsync estaba iniciando la tarea en el mismo subproceso, por lo que sería un punto muerto. Hice algo como esto:

 IEnumerable parts = null; Task.Factory .StartNew(() => parts = Request.Content.ReadAsMultipartAsync().Result.Contents, CancellationToken.None, TaskCreationOptions.LongRunning, // guarantees separate thread TaskScheduler.Default) .Wait(); 

El parámetro TaskCreationOptions.LongRunning fue clave para mí porque sin él, la llamada continuaría iniciando la tarea en el mismo subproceso. Podría intentar usar algo como el siguiente pseudocódigo para ver si le funciona en C # 5.0:

 await TaskEx.Run(async() => await Request.Content.ReadAsMultipartAsync(provider)) 

Encontré el mismo problema con todos los framework 4.5.2 modernos.

Mi método API acepta uno o más archivos cargados mediante la solicitud POST con contenido multiparte. Funcionó bien con archivos pequeños, pero con grandes, mi método simplemente se colgó para siempre porque la función ReadAsMultipartAsync() nunca se completó.

Lo que me ayudó: utilizar un método de controlador async y await a que se ReadAsMultipartAsync() , en lugar de obtener el resultado de la tarea en un método de controlador síncrono.

Entonces, esto no funcionó

 [HttpPost] public IHttpActionResult PostFiles() { return Ok ( Request.Content.ReadAsMultipartAsync().Result .Contents .Select(content => ProcessSingleContent(content)) ); } private string ProcessSingleContent(HttpContent content) { return SomeLogic(content.ReadAsByteArrayAsync().Result); } 

Y esto funcionó:

 [HttpPost] public async Task PostFiles() { return Ok ( await Task.WhenAll ( (await Request.Content.ReadAsMultipartAsync()) .Contents .Select(async content => await ProcessSingleContentAsync(content)) ) ); } private async Task ProcessSingleContentAsync(HttpContent content) { return SomeLogic(await content.ReadAsByteArrayAsync()); } 

donde SomeLogic es solo una función sincrónica que toma contenido binario y produce una cadena (puede ser cualquier tipo de procesamiento).

ACTUALIZACIÓN Y finalmente encontré la explicación en este artículo: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

La causa raíz de este punto muerto se debe a la forma en que se manejan los contextos. De forma predeterminada, cuando se espera una tarea incompleta, se captura el “contexto” actual y se utiliza para reanudar el método cuando finaliza la tarea. Este “contexto” es el SynchronizationContext actual a menos que sea nulo, en cuyo caso es el TaskScheduler actual. Las aplicaciones GUI y ASP.NET tienen un SynchronizationContext que permite que solo se ejecute un fragmento de código a la vez. Cuando se agota, intenta ejecutar el rest del método async dentro del contexto capturado. Pero ese contexto ya tiene un hilo, que está (sincrónicamente) esperando que se complete el método async. Ambos están esperando al otro, causando un punto muerto.

Entonces, básicamente, la guía “Async all the way” tiene una razón detrás, y este es un buen ejemplo.

Con la ayuda de otra respuesta en stackoverflow y una publicación de blog sobre targetFramework , descubrí que actualizar a 4.5 y agregar / actualizar lo siguiente en su web.config corrige este problema:

       

Tengo un proyecto .Net MVC WebAPi en funcionamiento con el siguiente método Post que parece funcionar bien. Es muy similar a lo que ya tienes, así que esto debería ser útil.

  [System.Web.Http.AcceptVerbs("Post")] [System.Web.Http.HttpPost] public Task Post() { // Check if the request contains multipart/form-data. if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } string fileSaveLocation = @"c:\SaveYourFile\Here\XXX"; CustomMultipartFormDataStreamProvider provider = new CustomMultipartFormDataStreamProvider(fileSaveLocation); Task task = Request.Content.ReadAsMultipartAsync(provider).ContinueWith(t => { if (t.IsFaulted || t.IsCanceled) { Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception); } foreach (MultipartFileData file in provider.FileData) { //Do Work Here } return Request.CreateResponse(HttpStatusCode.OK); } ); return task; } 

Yo tuve lo mismo. Mi solución

 public List UploadFiles(HttpFileCollection fileCollection) { var uploadsDirectoryPath = HttpContext.Current.Server.MapPath("~/Uploads"); if (!Directory.Exists(uploadsDirectoryPath)) Directory.CreateDirectory(uploadsDirectoryPath); var filePaths = new List(); for (var index = 0; index < fileCollection.Count; index++) { var path = Path.Combine(uploadsDirectoryPath, Guid.NewGuid().ToString()); fileCollection[index].SaveAs(path); filePaths.Add(path); } return filePaths; } 

e invocando

 if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var filePaths = _formsService.UploadFiles(HttpContext.Current.Request.Files);