Passing CSV file from Angular7 to MVC .Net Core - c#

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...

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

File upload in MVC Razor file empty? [duplicate]

I'm encountering a problem sending files stored in a database back to the user in ASP.NET MVC. What I want is a view listing two links, one to view the file and let the mimetype sent to the browser determine how it should be handled, and the other to force a download.
If I choose to view a file called SomeRandomFile.bak and the browser doesn't have an associated program to open files of this type, then I have no problem with it defaulting to the download behavior. However, if I choose to view a file called SomeRandomFile.pdf or SomeRandomFile.jpg I want the file to simply open. But I also want to keep a download link off to the side so that I can force a download prompt regardless of the file type. Does this make sense?
I have tried FileStreamResult and it works for most files, its constructor doesn't accept a filename by default, so unknown files are assigned a file name based on the URL (which does not know the extension to give based on content type). If I force the file name by specifying it, I lose the ability for the browser to open the file directly and I get a download prompt. Has anyone else encountered this?
These are the examples of what I've tried so far.
//Gives me a download prompt.
return File(document.Data, document.ContentType, document.Name);
//Opens if it is a known extension type, downloads otherwise (download has bogus name and missing extension)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType);
//Gives me a download prompt (lose the ability to open by default if known type)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType) {FileDownloadName = document.Name};
Any suggestions?
UPDATE:
This questions seems to strike a chord with a lot of people, so I thought I'd post an update. The warning on the accepted answer below that was added by Oskar regarding international characters is completely valid, and I've hit it a few times due to using the ContentDisposition class. I've since updated my implementation to fix this. While the code below is from my most recent incarnation of this problem in an ASP.NET Core (Full Framework) app, it should work with minimal changes in an older MVC application as well since I'm using the System.Net.Http.Headers.ContentDispositionHeaderValue class.
using System.Net.Http.Headers;
public IActionResult Download()
{
Document document = ... //Obtain document from database context
//"attachment" means always prompt the user to download
//"inline" means let the browser try and handle it
var cd = new ContentDispositionHeaderValue("attachment")
{
FileNameStar = document.FileName
};
Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
return File(document.Data, document.ContentType);
}
// an entity class for the document in my database
public class Document
{
public string FileName { get; set; }
public string ContentType { get; set; }
public byte[] Data { get; set; }
//Other properties left out for brevity
}
public ActionResult Download()
{
var document = ...
var cd = new System.Net.Mime.ContentDisposition
{
// for example foo.bak
FileName = document.FileName,
// always prompt the user for downloading, set to true if you want
// the browser to try to show the file inline
Inline = false,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(document.Data, document.ContentType);
}
NOTE: This example code above fails to properly account for international characters in the filename. See RFC6266 for the relevant standardization. I believe recent versions of ASP.Net MVC's File() method and the ContentDispositionHeaderValue class properly accounts for this. - Oskar 2016-02-25
I had trouble with the accepted answer due to no type hinting on the "document" variable: var document = ... So I'm posting what worked for me as an alternative in case anybody else is having trouble.
public ActionResult DownloadFile()
{
string filename = "File.pdf";
string filepath = AppDomain.CurrentDomain.BaseDirectory + "/Path/To/File/" + filename;
byte[] filedata = System.IO.File.ReadAllBytes(filepath);
string contentType = MimeMapping.GetMimeMapping(filepath);
var cd = new System.Net.Mime.ContentDisposition
{
FileName = filename,
Inline = true,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(filedata, contentType);
}
To view file (txt for example):
return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);
To download file (txt for example):
return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");
note: to download file we should pass fileDownloadName argument
Darin Dimitrov's answer is correct. Just an addition:
Response.AppendHeader("Content-Disposition", cd.ToString()); may cause the browser to fail rendering the file if your response already contains a "Content-Disposition" header. In that case, you may want to use:
Response.Headers.Add("Content-Disposition", cd.ToString());
I believe this answer is cleaner, (based on
https://stackoverflow.com/a/3007668/550975)
public ActionResult GetAttachment(long id)
{
FileAttachment attachment;
using (var db = new TheContext())
{
attachment = db.FileAttachments.FirstOrDefault(x => x.Id == id);
}
return File(attachment.FileData, "application/force-download", Path.GetFileName(attachment.FileName));
}
Below code worked for me for getting a pdf file from an API service and response it out to the browser - hope it helps;
public async Task<FileResult> PrintPdfStatements(string fileName)
{
var fileContent = await GetFileStreamAsync(fileName);
var fileContentBytes = ((MemoryStream)fileContent).ToArray();
return File(fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf);
}
FileVirtualPath --> Research\Global Office Review.pdf
public virtual ActionResult GetFile()
{
return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}
Action method needs to return FileResult with either a stream, byte[], or virtual path of the file. You will also need to know the content-type of the file being downloaded. Here is a sample (quick/dirty) utility method. Sample video link
How to download files using asp.net core
[Route("api/[controller]")]
public class DownloadController : Controller
{
[HttpGet]
public async Task<IActionResult> Download()
{
var path = #"C:\Vetrivel\winforms.png";
var memory = new MemoryStream();
using (var stream = new FileStream(path, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
var ext = Path.GetExtension(path).ToLowerInvariant();
return File(memory, GetMimeTypes()[ext], Path.GetFileName(path));
}
private Dictionary<string, string> GetMimeTypes()
{
return new Dictionary<string, string>
{
{".txt", "text/plain"},
{".pdf", "application/pdf"},
{".doc", "application/vnd.ms-word"},
{".docx", "application/vnd.ms-word"},
{".png", "image/png"},
{".jpg", "image/jpeg"},
...
};
}
}
If, like me, you've come to this topic via Razor components as you're learning Blazor, then you'll find you need to think a little more outside of the box to solve this problem. It's a bit of a minefield if (also like me) Blazor is your first forray into the MVC-type world, as the documentation isn't as helpful for such 'menial' tasks.
So, at the time of writing, you cannot achieve this using vanilla Blazor/Razor without embedding an MVC controller to handle the file download part an example of which is as below:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
[Route("api/[controller]")]
[ApiController]
public class FileHandlingController : ControllerBase
{
[HttpGet]
public FileContentResult Download(int attachmentId)
{
TaskAttachment taskFile = null;
if (attachmentId > 0)
{
// taskFile = <your code to get the file>
// which assumes it's an object with relevant properties as required below
if (taskFile != null)
{
var cd = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileNameStar = taskFile.Filename
};
Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
}
}
return new FileContentResult(taskFile?.FileData, taskFile?.FileContentType);
}
}
Next, make sure your application startup (Startup.cs) is configured to correctly use MVC and has the following line present (add it if not):
services.AddMvc();
.. and then finally modify your component to link to the controller, for example (iterative based example using a custom class):
<tbody>
#foreach (var attachment in yourAttachments)
{
<tr>
<td>#attachment.Filename </td>
<td>#attachment.CreatedUser</td>
<td>#attachment.Created?.ToString("dd MMM yyyy")</td>
<td><ul><li class="oi oi-circle-x delete-attachment"></li></ul></td>
</tr>
}
</tbody>
Hopefully this helps anyone who struggled (like me!) to get an appropriate answer to this seemingly simple question in the realms of Blazor…!

ASP.NET MVC - error while trying to read and write to text file

currently I am trying to create two controller methods reading from file and writing to same file:
public ActionResult UploadText(string name)
{
var path = Path.Combine(Server.MapPath("~/text/"), name);
var fileContents = System.IO.File.ReadAllText(path);
ViewData["text"] = fileContents;
ViewData["textName"] = name;
return View();
}
[HttpPost]
public ActionResult TextPost(string textName)
{
string text = Request["text-content"];
var path = Path.Combine(Server.MapPath("~/text/"), textName);
System.IO.File.WriteAllText(path, text);
return RedirectToAction("Index");
}
Reading file content and writing to it works, but it cannot be read second time, File can't be accessed because it is being used by another process error appears.
What am I doing wrong?
System.IO.File.ReadAllText and System.IO.File.WriteAllText both close the file after they and finished with the file per the documentation. The issue stems from the async nature of the web and well if you have more than one request while the file is open you will get the error you are seeing. Here are some MSDN examples to get you started.
Here are a couple more links for your pleasure
simultaneous read-write a file in C#
How to both read and write a file in C#

WebAPI File Uploading - Without writing files to disk

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

Get file path from inputstream?

I am trying to get the last modified date from a file, but need its path? Could someone please show me how i can get the file path?
[HttpGet]
public string uploadfile(string token, string filenameP, DateTime modDate, HttpPostedFileBase file)
{
MemoryStream target = new MemoryStream();
file.InputStream.CopyTo(target);
byte[] data = target.ToArray();
//ModDate = File.GetLastWriteTimeUtc("Path");
}
You are creating a new file on the server when you upload. The last modified date will be "now" (the time the file is created). There is no way to snoop the user's machine to get this information (which is not part of the file itself). Can't be done with an HTTP form upload.
Now, some file types may contain metadata in the file which may have pertinent information. If you know the file type and it does contain such metadata then you can open the file and have a look.
You just don't. Most (if not all) browsers do not provide this information for security reasons in internet sceanrios.
You can read date by javascript (HTML5) and send it as hidden input field of form.
Something like
<script>
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// files is a FileList of File objects. List some properties.
var output = [];
for (var i = 0, f; f = files[i]; i++) {
output.push(f.lastModifiedDate ? f.lastModifiedDate.toLocaleDateString() );
}
document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
http://www.html5rocks.com/en/tutorials/file/dndfiles/

Categories

Resources