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; } }
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(); }
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()); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With