WebAPI File Uploading - Without writing files to disk - c#

All the documentation / tutorials / questions about processing a file uploaded using FormData to a ASP.NET WebAPI handler use MultipartFormDataStreamProvider to process the multipart stream to split it into the relevant form fields and files.
var root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
await Request.Content.ReadAsMultipartAsync(provider);
foreach (MultipartFileData file in provider.FileData)
{
// File
}
However, the files are automatically written to a directory during processsing.
It seems a lot of hassle when I could just use HttpContext.Current.Request.Files[0].InputStream to access a given file stream directly in memory.
How can WebAPI just access the file stream directly without the IO overhead of using MultipartFormDataStreamProvider?
Official tutorial: http://www.asp.net/web-api/overview/advanced/sending-html-form-data,-part-2

Solved:
Use the existing simple MultipartMemoryStreamProvider. No custom classes or providers required. This differers from the duplicate question which solved the solution by writing a custom provider.
Then use it in a WebAPI handler as so:
public async Task<IHttpActionResult> UploadFile()
{
if (!Request.Content.IsMimeMultipartContent())
{
return StatusCode(HttpStatusCode.UnsupportedMediaType);
}
var filesReadToProvider = await Request.Content.ReadAsMultipartAsync();
foreach (var stream in filesReadToProvider.Contents)
{
var fileBytes = await stream.ReadAsByteArrayAsync();
}
return StatusCode(HttpStatusCode.OK);
}

Related

how to get path file selected in .netcore web api(IFromFile)

my field is
public IFormFileCollection FileNames { get; set; }
i get multiple file from postman (form-data) and post
in api-controller i get fields of send with postman
my problem here,i need to path of files, how i can get path of FromFile?
updated code :
using (var fileStream = new FileStream(fileName.FileName, FileMode.Create,FileAccess.Write))
{
await fileName.CopyToAsync(fileStream);
fileStream.Dispose();
var request = MyOperationOnFile(fileStream.Name);
...
}
get this exception for second file(first file is ok):
"Cannot access a disposed object.\r\nObject name: 'FileBufferingReadStream'."
if dont use .dispose() get this exception for first file :
Failed to send: Microsoft.AspNetCore.Http.FormFile, with error:The process cannot access the file 'porjectLocation\file' because it is being used by another process
In fact, if you want to upload files, you do not need to know the original path of the files, but only need to create the path you need to upload.
The following is a case of uploading multiple files, please refer to it:
[HttpPost("UploadFile")]
public async Task<IActionResult> UploadFile(IFormFileCollection files)
{
foreach (var fileName in files)
{
var filesPath = Directory.GetCurrentDirectory() + "/Uploadfiles";
if (!System.IO.Directory.Exists(filesPath))//create path
{
Directory.CreateDirectory(filesPath);
}
var path = Path.Combine(filesPath, Path.GetFileName(fileName.FileName));//the path to upload
await fileName.CopyToAsync(new FileStream(path, FileMode.Create));
}
return Ok();
}
Here is the result:
The form file that you received from the request are actually the stream in memory of the files, you will need to save it on the disk or on your database. Check the examples in the documentation:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1

Download files in API ASP.Net Core

I'm trying to create an API that downloads a specific file from the server in ASP.Net Core.
I'm Using the ASP.NET Boilerplate.
I've tried every solution i found, and non of them helped me.
most of the solutions I've tried, implements something like this:
public async Task<IActionResult> Download(string filename)
{
if (filename == null)
return Content("filename not present");
var path = Path.Combine(
Directory.GetCurrentDirectory(),
"wwwroot", filename);
var memory = new MemoryStream();
using (var stream = new FileStream(path, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
return File(memory, GetContentType(path), Path.GetFileName(path));
}
And when using it, I always get an error on the last line Non-invocable member 'File' cannot be used like a method.
If you're seeing this error during compilation, it's probably because the compiler thinks you're trying to invoke the constructor on the System.IO.File class instead of invoking the File() method on the System.Web.Mvc.Controller class.
Try to call the method by explicitly scoping it to the instance with this.File(...).

Upload Csv to solution explorer

I'm trying to build an application in which a user can upload a csv file and convert it into XML. Currently my controller can create .txt file in a temp folder. However when I open the txt file it comes out all corrupted as below:
I have two questions
1. How can I make it so that the file displays properly i.e. as items separated by commas?
2. How can I change my code to make the file upload into my solution explorer
Here is the relevant controller code:
[HttpPost("UploadFiles")]
public async Task<IActionResult> FileUpload(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
var filePaths = new List<string>();
foreach (var formFile in files)
{
if(formFile.Length > 0)
{
var filePath = Path.GetTempPath()+ Guid.NewGuid().ToString()+".txt";
filePaths.Add(filePath);
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
await formFile.CopyToAsync(stream);
}
}
}
return Ok(new { count = files.Count, size, filePaths });
}
Any suggestions would be much appreciated
Thanks in advance
When the file is corrupt I think the conversion doesn't work. Try for now uploading it without any conversion.
For the second question you can do the following
var filePath = Path.Combine(AppContext.BaseDirectory, $"{Guid.NewGuid().ToString()}.csv"); // or whatever extension you are actually having without modifying the original extension
This will store the file either in the "bin" directory path or in the directory where the source-code is located.

How to delete a file after it was streamed in ASP.NET Core

I would like to delete a temporary file after returning it form action. How can i achieve that with ASP.NET Core:
public IActionResult Download(long id)
{
var file = "C:/temp/tempfile.zip";
var fileName = "file.zip;
return this.PhysicalFile(file, "application/zip", fileName);
// I Would like to have File.Delete(file) here !!
}
The file is too big for returning using memory stream.
File() or PhysicalFile() return a FileResult-derived class that just delegates processing to an executor service. PhysicalFileResult's ExecuteResultAsync method calls :
var executor = context.HttpContext.RequestServices
.GetRequiredService<IActionResultExecutor<PhysicalFileResult>>();
return executor.ExecuteAsync(context, this);
All other FileResult-based classes work in a similar way.
The PhysicalFileResultExecutor class essentially writes the file's contents to the Response stream.
A quick and dirty solution would be to create your own PhysicalFileResult-based class that delegates to PhysicalFileResultExecutor but deletes the file once the executor finishes :
public class TempPhysicalFileResult : PhysicalFileResult
{
public TempPhysicalFileResult(string fileName, string contentType)
: base(fileName, contentType) { }
public TempPhysicalFileResult(string fileName, MediaTypeHeaderValue contentType)
: base(fileName, contentType) { }
public override async Task ExecuteResultAsync(ActionContext context)
{
try {
await base.ExecuteResultAsync(context);
}
finally {
File.Delete(FileName);
}
}
}
Instead of calling PhysicalFile() to create the PhysicalFileResult you can create and return a TempPhysicalFileResult, eg :
return new TempPhysicalFileResult(file, "application/zip"){FileDownloadName=fileName};
That's the same thing PhysicalFile() does :
[NonAction]
public virtual PhysicalFileResult PhysicalFile(
string physicalPath,
string contentType,
string fileDownloadName)
=> new PhysicalFileResult(physicalPath, contentType) { FileDownloadName = fileDownloadName };
A more sophisticated solution would be to create a custom executor that took care eg of compression as well as cleaning up files, leaving the action code clean of result formatting code
You can create a FileStream that deletes the underlying file as soon as the stream is closed. With the option DeleteOnClose.
As the stream is closed automatically after download, the file is deleted.
var fileStream = new FileStream(
Path.GetTempFileName(),
FileMode.Create,
FileAccess.ReadWrite,
FileShare.Read,
4096,
FileOptions.DeleteOnClose);
// add something to the file
fileStream.Position = 0;
return File(fileStream, MediaTypeNames.Application.Octet);
It would be hard to do this within the same request as the requests ends directly after the streaming is complete. A better option might be to run a timed background task via a hosted service which periodically removes all files (e.g. with a last write time of >5 minutes ago) from the temp directory.
Since the file is big one solution is to send a token (like a guid) along side the request. Only once the download has been completed you want to delete the file (otherwise you risk losing it). So using that token the client will make a new request using the aforementioned token. So you will know that the download was successful and that you can proceed to delete the file.
I suggest using a Dictionary to map a token to a file.
Deleting on one request seems like a bad practice due to the fact that you can easily lose the file.
Also you can use Henk Mollema answer to be certain that all files are cleared periodically if that is what you are trying to achieve.
You can load content of file. delete file and send content in response. in MVC you can return a file in response. check the snippet below
public FileResult Download()
{
var fileName = $"client-file-name.ext";
var filepath = $"filepath";
byte[] fileBytes = System.IO.File.ReadAllBytes(filepath);
//delete file here
return File(fileBytes, "application/x-msdownload", fileName);
}

Passing CSV file from Angular7 to MVC .Net Core

I'm trying to pass a CSV file to my backend to utilize a library for parsing CSV data.
I've got an Angular7 front end with a C# .Net Core MVC backend utilizing the HttpClient in typescript in order to pass data and objects to and from the backend.
I've tried a number of solutions but with minimal success (the closest being that I can reach the controller in my code, but the file never gets passed in).
I'm trying to avoid doing any kind of serialization or conversion of the CSV until it hits the controller so I'd like to find a solution where I can send the whole of the CSV to the backend.
Any help would be appreciated.
service.ts
// Parse CSV
// The CSV always reaches this part of the code and I can inspect it.
parseCsv$(file: File): Observable<Dto> {
const request$ = this.http.post<Dto>(
location.origin + '/api/csvimport',
file,
{ headers: new HttpHeaders({ 'Content-Type': 'multipart/form-data' }) }
).pipe(
map((response: <Dto>) => response)
);
request$.subscribe();
return request$;
}
CsvImportController.cs
[HttpPost]
[ProducesResponseType(typeof(Dto), 200)]
// The 'file' variable below is always null
public async Task<IActionResult> ParseCsv([FromBody]IFormFile file)
{
// do work here
You should not use FromBody parameter in File Upload.
You should either use, request.Form.Files collection to get the uploaded file,
OR
you can use IList in the controller action as shown below.
Below API gets list of files which are then saved in the temp folder on server.
[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
// full path to file in temp location
var filePath = Path.GetTempFileName();
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await formFile.CopyToAsync(stream);
}
}
}
// process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size, filePath});
}
Hope this helps.
So I was able to figure this out,
I needed to change File to a FileList and then use FormData to append the file. I also needed to drop my content header as letting the backend handle typing the content itself stopped errors from being thrown. In my controller I had to use an IEnumerable<IFormFile> for this to fully work.
I don't fully understand why I had to use a FileList when only
uploading one file, but this was required in order to send along the
file. If I find out more, I will update my answer.
EDIT: Addressing the above, in the MDN documentation, it states that "All <input> element nodes have a files attribute of type FileList on them which allows access to the items in this list" (https://developer.mozilla.org/en-US/docs/Web/API/FileList) So in order to access one file, you actually need to access the first index (FileList[0]) of the FileList array. I may be misunderstanding this, so feel free to comment.
service.ts
parseCsv$(files: FileList): Observable<Dto> {
const formData = new FormData();
Array.from(files).forEach(
(file: File) => formData.append(file.name, file)
);
return this.http.post<Dto>(
location.origin + '/api/csvimport',
formData)
.pipe(
map((response: <Dto>) => response)
);
}
CsvImportController.cs
[HttpPost]
[ProducesResponseType(typeof(Dto), 200)]
public async Task<IActionResult> ParseCsv(IEnumerable<IFormFile> files)
{
// Work here...

Categories

Resources