how to read file data in web api 2? - c#

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
}

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 check file content is missing in File Upload Web API

I have Web API to upload a single file which is sent along the request body. I have the below code to read file binaries from the stream
Task<HttpResponseMessage> task = Request.Content.ReadAsStreamAsync().ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
try
{
using (Stream stream = t.Result)
{
using (MemoryStream ms = new MemoryStream())
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(ms);
byte[] fileBinaries = ms.ToArray();
//logic to process the file
}
}
}
catch (Exception e)
{
//exception handling logic
}
return Request.CreateResponse(HttpStatusCode.Created);
});
return task;
The API works fine when called with file being uploaded; and returns Http status code 201. But if I didn't attached file to the API call still it returns the same code as there is no check on binary data received. I want to add that check to return appropriate error message to user.
I tried to perform this check by evaluating the length of fileBinaries byte array read from Request.Content. But the array has few bytes in it which represents text [object FileList] (don't know how this bytes are filled in the array as i haven't attached any file with the API call). So this won't work for me.
I also tried using the HttpContext.Current.Request.Files.Count but it always returns 0 (Probably due to the file binaries sent in request body) so not suitable for my check.
I can't rely on any headers like File Name as those are not sent in request.
Any help how to perform this?
Try using MultipartMemoryStreamProvider which is ideal for file uploads using webapi
public async Task<IHttpActionResult> UploadFile()
{
var filesReadToProvider = await Request.Content.ReadAsMultipartAsync();
foreach (var stream in filesReadToProvider.Contents)
{
var fileBytes = await stream.ReadAsByteArrayAsync();
}
}
Here fileBytes may not have those 17 bytes.

Read Multipart/Form-Data without touching disk

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();

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