Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Web API: how to access multipart form values when using MultipartMemoryStreamProvider?

I used to use MultipartFormDataStreamProvider to process multipart requests.

Since I want the uploaded file to be stored in memory, instead of a disk file, I've changed my code to use MultipartMemoryStreamProvider. The file loading seems to be working fine but I am no longer able to access other form values which were available through provider.FormData under MultipartFormDataStreamProvider. Could someone show me how to do this?

The raw request captured by Fiddler:

POST http://myserver.com/QCCSvcHost/MIME/RealtimeTrans/ HTTP/1.1 Content-Type: multipart/form-data; boundary="XbCY" Host: na-w-lxu3 Content-Length: 1470 Expect: 100-continue Connection: Keep-Alive  --XbCY Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=PayloadType  X12_270_Request_005010X279A1 --XbCY Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=ProcessingMode  RealTime --XbCY Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=PayloadID  e51d4fae-7dec-11d0-a765-00a0c91e6fa6 --XbCY Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=TimeStamp  2007-08-30T10:20:34Z --XbCY Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=SenderID  HospitalA --XbCY Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=ReceiverID  PayerB --XbCY Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=CORERuleVersion  2.2.0 --XbCY Content-Disposition: form-data; name=Payload; filename=276_5010.edi  ISA*00*~SE*16*0001~GE*1*1~IEA*1*191543498~ --XbCY-- 

My controller code:

string payload = null; NameValueCollection nvc = null; string fname = null; StringBuilder sb = new StringBuilder(); sb.AppendLine(); foreach (StreamContent item in provider.Contents) {     fname = item.Headers.ContentDisposition.FileName;     if (!String.IsNullOrWhiteSpace(fname))     {         payload = item.ReadAsStringAsync().Result;     }     else     {         nvc = item.ReadAsFormDataAsync().Result;     } } 
like image 679
user2434400 Avatar asked Jun 12 '13 18:06

user2434400


2 Answers

Updated 4/28/2015

You could create a custom provider based on MultipartFormDataRemoteStreamProvider.
Example:

public class CustomMultipartFormDataProvider : MultipartFormDataRemoteStreamProvider {     public override RemoteStreamInfo GetRemoteStream(HttpContent parent, HttpContentHeaders headers)     {         return new RemoteStreamInfo(             remoteStream: new MemoryStream(),             location: string.Empty,             fileName: string.Empty);     } } 

Updated

Custom In-memory MultiaprtFormDataStreamProvider:

public class InMemoryMultipartFormDataStreamProvider : MultipartStreamProvider {     private NameValueCollection _formData = new NameValueCollection();     private List<HttpContent> _fileContents = new List<HttpContent>();      // Set of indexes of which HttpContents we designate as form data     private Collection<bool> _isFormData = new Collection<bool>();      /// <summary>     /// Gets a <see cref="NameValueCollection"/> of form data passed as part of the multipart form data.     /// </summary>     public NameValueCollection FormData     {         get { return _formData; }     }      /// <summary>     /// Gets list of <see cref="HttpContent"/>s which contain uploaded files as in-memory representation.     /// </summary>     public List<HttpContent> Files     {         get { return _fileContents; }     }      public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)     {         // For form data, Content-Disposition header is a requirement         ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;         if (contentDisposition != null)         {             // We will post process this as form data             _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName));              return new MemoryStream();         }          // If no Content-Disposition header was present.         throw new InvalidOperationException(string.Format("Did not find required '{0}' header field in MIME multipart body part..", "Content-Disposition"));     }      /// <summary>     /// Read the non-file contents as form data.     /// </summary>     /// <returns></returns>     public override async Task ExecutePostProcessingAsync()     {         // Find instances of non-file HttpContents and read them asynchronously         // to get the string content and then add that as form data         for (int index = 0; index < Contents.Count; index++)         {             if (_isFormData[index])             {                 HttpContent formContent = Contents[index];                 // Extract name from Content-Disposition header. We know from earlier that the header is present.                 ContentDispositionHeaderValue contentDisposition = formContent.Headers.ContentDisposition;                 string formFieldName = UnquoteToken(contentDisposition.Name) ?? String.Empty;                  // Read the contents as string data and add to form data                 string formFieldValue = await formContent.ReadAsStringAsync();                 FormData.Add(formFieldName, formFieldValue);             }             else             {                 _fileContents.Add(Contents[index]);             }         }     }      /// <summary>     /// Remove bounding quotes on a token if present     /// </summary>     /// <param name="token">Token to unquote.</param>     /// <returns>Unquoted token.</returns>     private static string UnquoteToken(string token)     {         if (String.IsNullOrWhiteSpace(token))         {             return token;         }          if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)         {             return token.Substring(1, token.Length - 2);         }          return token;     } } 

Usage:

public async Task Post() {     if (!Request.Content.IsMimeMultipartContent("form-data"))     {         throw new HttpResponseException(HttpStatusCode.BadRequest);     }      var provider = await Request.Content.ReadAsMultipartAsync<InMemoryMultipartFormDataStreamProvider>(new InMemoryMultipartFormDataStreamProvider());      //access form data     NameValueCollection formData = provider.FormData;      //access files     IList<HttpContent> files = provider.Files;      //Example: reading a file's stream like below     HttpContent file1 = files[0];     Stream file1Stream = await file1.ReadAsStreamAsync(); } 
like image 154
Kiran Avatar answered Sep 22 '22 14:09

Kiran


Building on the excellent answer from Kiran, I have pulled together the complete answer from the April 2015 update. It appears that at least one thing has changed in WebAPI, which is what confused me at first. The provider.Files no longer exists, it is .Content. So here is what you, minimally, need to do in order to read posted files without first storing them on disk:

Step 1: create a provider class

Add a file somewhere in your project for this class:

public class InMemoryMultipartFormDataProvider : MultipartFormDataRemoteStreamProvider {    public override RemoteStreamInfo GetRemoteStream(HttpContent parent, HttpContentHeaders headers)    {       return new RemoteStreamInfo(                 remoteStream: new MemoryStream(),                 location: string.Empty,                 fileName: string.Empty);    } } 

I believe this converts each file into a memory stream rather than storing it on disk.

Step 2: add a controller action parse the contents and create the streams

In your controller:

[HttpPost] public async Task<IHttpActionResult> Upload() {    // This endpoint only supports multipart form data    if (!Request.Content.IsMimeMultipartContent("form-data"))    {       return StatusCode(HttpStatusCode.UnsupportedMediaType);    }     // read the content in a memory stream per file uploaded    var provider = await Request.Content.ReadAsMultipartAsync<InMemoryMultipartFormDataProvider>(new InMemoryMultipartFormDataProvider());     // iterate over each file uploaded and do something with the results    foreach (var fileContents in provider.Contents) {       processFileAsMemoryStream(await fileContents.ReadAsStreamAsync());    } } 
like image 22
Greg Veres Avatar answered Sep 20 '22 14:09

Greg Veres