Read Multipart/Form-Data without touching disk - c#

Alright, so I want to receive a Multipart/Form-Data POST request in my WebAPI, and do things with the files and form fields contained within. That's easy, like so:
var provider = new MultipartFormDataStreamProvider(Path.GetTempPath());
await Request.Content.ReadAsMultipartAsync(provider);
var formValue = provider.FormData["form_value_here"];
var files = provider.FileData.Select(d => d.LocalFileName);
It's easy to get any form field or particular file I need. However, I would like to have similar capability, but instead of saving files to disk when ReadAsMultipartAsync is called, I'd like to have them in a Stream
The reasoning for this, is that I would like to hash the values of the file(s) being posted, and reject the request if necessary, before saving the files to disk. Is there built in provider or class that I've missed that has a convenient API?

You can use MultipartMemoryStreamProvider to get the form data as a stream. You'll want something like this:
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
var fileNames = new List<string>();
foreach (var file in provider.Contents)
{
fileNames.Add(file.Headers.ContentDisposition.FileName.Trim('\"'));
var buffer = await file.ReadAsByteArrayAsync();
//hash values, reject request if needed
}
return Ok();

Related

Posting MultiPartFileData file to a function

I have data coming from a source to my Api and I need to post it to another POST ActionResult.
I have no problem in receiving data in first function which is basically this;
var provider = new MultipartFormDataStreamProvider(root);
try
{
//irrelevant confirmations and other code pieces are left out
await Request.Content.ReadAsMultipartAsync(provider);
foreach (MultipartFileData fileData in provider.FileData)
{
var appPath = System.AppDomain.CurrentDomain.BaseDirectory.ToString();
var basePath = Path.GetFullPath(Path.Combine(appPath, #"..\"));
var headerActivityPath = basePath + "\\Documents\\" + tenantString + "\\Activity\\" + activityId;
File.Copy(fileData.LocalFileName, Path.Combine(activityPath, fileName));
}
}
I need to make an POST after this one finishes receiving the file. I want to make it without saving it to disk first, so I don't know where I should do the POST request with HttpClient.
The ActionResult with will be receiving the data has a parameter with HttpPostedFileBase but I don't know what to send it.
Every method I used before uses a file on the disk, is it possible to do this without saving the file to disk first?
HttpPostedFileBase has a property on it called InputStream this will give you access to the content of the attachment which was uploaded to your endpoint. You can use this as the argument to a new StreamContent in the HttpClient.
foreach (MultipartFileData fileData in provider.FileData)
{
await client.PostAsync("http://someurl.com", new StreamContent(fileData.InputStream));
}

Web API: Download Multiple Files Separately

I have a Web Api controller method that gets passed document IDs and it should return the document files individually for those requested Ids. I have tried the accepted answer from the following link to achieve this functionality, but it's not working. I don't know where I did go wrong.
What's the best way to serve up multiple binary files from a single WebApi method?
My Web Api Method,
public async Task<HttpResponseMessage> DownloadMultiDocumentAsync(
IClaimedUser user, string documentId)
{
List<long> docIds = documentId.Split(',').Select(long.Parse).ToList();
List<Document> documentList = coreDataContext.Documents.Where(d => docIds.Contains(d.DocumentId) && d.IsActive).ToList();
var content = new MultipartContent();
CloudBlockBlob blob = null;
var container = GetBlobClient(tenantInfo);
var directory = container.GetDirectoryReference(
string.Format(DirectoryNameConfigValue, tenantInfo.TenantId.ToString(), documentList[0].ProjectId));
for (int docId = 0; docId < documentList.Count; docId++)
{
blob = directory.GetBlockBlobReference(DocumentNameConfigValue + documentList[docId].DocumentId);
if (!blob.Exists()) continue;
MemoryStream memStream = new MemoryStream();
await blob.DownloadToStreamAsync(memStream);
memStream.Seek(0, SeekOrigin.Begin);
var streamContent = new StreamContent(memStream);
content.Add(streamContent);
}
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
httpResponseMessage.Content = content;
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
httpResponseMessage.StatusCode = HttpStatusCode.OK;
return httpResponseMessage;
}
I tried with 2 or more document Ids but only one file was downloaded and that also is not in the correct format (without extension).
Zipping is the only option that will have consistent result on all browsers. MIME/multipart content is for email messages (https://en.wikipedia.org/wiki/MIME#Multipart_messages) and it was never intended to be received and parsed on the client side of a HTTP transaction. Some browsers do implement it, some others don't.
Alternatively, you can change your API to take in a single docId and iterate over your API from your client for each docId.
I think only way is that you zip your all the files and then download one zip file. I guess you can use dotnetzip package because it is easy to use.
One way is that, you can first save your files on disk and then stream the zip to download. Another way is, you can zip them in memory and then download the file in stream
public ActionResult Download()
{
using (ZipFile zip = new ZipFile())
{
zip.AddDirectory(Server.MapPath("~/Directories/hello"));
MemoryStream output = new MemoryStream();
zip.Save(output);
return File(output, "application/zip", "sample.zip");
}
}

how to read file data in web api 2?

I use the MultipartFormDataStreamProvider to read uploaded files as the snippet below illustrates. However this isn't secure as it saves the files straight to temp. First, I want to inspect the raw bytes and perform some validation checks. Please show me how to access the raw bytes.
if (Request.Content.IsMimeMultipartContent())
{
MultipartFormDataStreamProvider streamProvider = new MultipartFormDataStreamProvider("C:\temp");
return this.Request.Content
.ReadAsMultipartAsync<MultipartFormDataStreamProvider>(streamProvider)
.ContinueWith((tsk) =>
{
MultipartFormDataStreamProvider provider = tsk.Result;
});
}
var provider = await Request.Content.ReadAsMultipartAsync(new MultipartMemoryStreamProvider());
foreach (var c in provider.Contents)
{
var stream = await c.ReadAsStreamAsync();
// do something with the stream
}

Save/Get ObservableCollection to/from Roaming

I'm making a Universal windows store app and I have an ObservableCollection.
What I want is to save the data from the ObservableCollection somewhere so that the data can sync between desktop and phone that have the same universal app, and then I need to be able to get this data so that I can update the ObservableCollection.
I haven't done anything like this before, so I'm not sure how to proceed... Do I need to save this to an XML file, and if so, how will I sync it between the different devices. The only storage methods I know how to use are:
ApplicationData.Current.LocalSettings.Values["key"] = "something";
//Which stores "something" in local storage with key "key".
ApplicationData.Current.RoamingSettings.Values["key"] = "something";
//Which stores "something" in user's Microsoft account storage with key "key".
Last one I think looks like to what I actually want, but it wouldn't be practical to save all my ObservableCollection items and properties like this.
The easiest way is to write it in a file in the app data roaming folder; app settings aren't very convenient to store collections. You can use XML, JSON or anything else. These days the trend is to use JSON, so here's an example.
Serialization:
var folder = ApplicationData.Current.RoamingFolder;
var file = await folder.CreateFileAsync("collection.json", CreationCollisionOption.ReplaceExisting);
using (var stream = await file.OpenStreamForWriteAsync())
using (var writer = new StreamWriter(stream, Encoding.UTF8))
{
string json = JsonConvert.SerializeObject(collection);
await writer.WriteAsync(json);
}
Deserialization:
var folder = ApplicationData.Current.RoamingFolder;
var file = await folder.GetFileAsync("collection.json");
using (var stream = await file.OpenStreamForReadAsync())
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
string json = await reader.ReadToEndAsync();
var collection = JsonConvert.DeserializeObject<ObservableCollection<YourClass>>(json);
}
(using JSON.NET)

How to get the stream for a Multipart file in webapi upload?

I need to upload a file using Stream (Azure Blobstorage), and just cannot find out how to get the stream from the object itself. See code below.
I'm new to the WebAPI and have used some examples. I'm getting the files and filedata, but it's not correct type for my methods to upload it. Therefore, I need to get or convert it into a normal Stream, which seems a bit hard at the moment :)
I know I need to use ReadAsStreamAsync().Result in some way, but it crashes in the foreach loop since I'm getting two provider.Contents (first one seems right, second one does not).
[System.Web.Http.HttpPost]
public async Task<HttpResponseMessage> Upload()
{
if (!Request.Content.IsMimeMultipartContent())
{
this.Request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
}
var provider = GetMultipartProvider();
var result = await Request.Content.ReadAsMultipartAsync(provider);
// On upload, files are given a generic name like "BodyPart_26d6abe1-3ae1-416a-9429-b35f15e6e5d5"
// so this is how you can get the original file name
var originalFileName = GetDeserializedFileName(result.FileData.First());
// uploadedFileInfo object will give you some additional stuff like file length,
// creation time, directory name, a few filesystem methods etc..
var uploadedFileInfo = new FileInfo(result.FileData.First().LocalFileName);
// Remove this line as well as GetFormData method if you're not
// sending any form data with your upload request
var fileUploadObj = GetFormData<UploadDataModel>(result);
Stream filestream = null;
using (Stream stream = new MemoryStream())
{
foreach (HttpContent content in provider.Contents)
{
BinaryFormatter bFormatter = new BinaryFormatter();
bFormatter.Serialize(stream, content.ReadAsStreamAsync().Result);
stream.Position = 0;
filestream = stream;
}
}
var storage = new StorageServices();
storage.UploadBlob(filestream, originalFileName);**strong text**
private MultipartFormDataStreamProvider GetMultipartProvider()
{
var uploadFolder = "~/App_Data/Tmp/FileUploads"; // you could put this to web.config
var root = HttpContext.Current.Server.MapPath(uploadFolder);
Directory.CreateDirectory(root);
return new MultipartFormDataStreamProvider(root);
}
This is identical to a dilemma I had a few months ago (capturing the upload stream before the MultipartStreamProvider took over and auto-magically saved the stream to a file). The recommendation was to inherit that class and override the methods ... but that didn't work in my case. :( (I wanted the functionality of both the MultipartFileStreamProvider and MultipartFormDataStreamProvider rolled into one MultipartStreamProvider, without the autosave part).
This might help; here's one written by one of the Web API developers, and this from the same developer.
Hi just wanted to post my answer so if anybody encounters the same issue they can find a solution here itself.
here
MultipartMemoryStreamProvider stream = await this.Request.Content.ReadAsMultipartAsync();
foreach (var st in stream.Contents)
{
var fileBytes = await st.ReadAsByteArrayAsync();
string base64 = Convert.ToBase64String(fileBytes);
var contentHeader = st.Headers;
string filename = contentHeader.ContentDisposition.FileName.Replace("\"", "");
string filetype = contentHeader.ContentType.MediaType;
}
I used MultipartMemoryStreamProvider and got all the details like filename and filetype from the header of content.
Hope this helps someone.

Categories