How upload csv file and parse it? - c#

I need upload csv file and parse it.
I can see file in http body request, but when I pass it in the csvreader I can see base64 string like headers:
On client side I use angularjs:
'uploadBulkUsersFile': {
method: 'POST', url: CONFIG.apiServiceBaseUri + "api/users/bulkUsers",
headers: {
"Content-Type": undefined
},
transformRequest: angular.identity,
withCredentials: true
},
and call:
var _uploadBulkUsersFile = function (bulkUsersFile) {
var fd = new FormData();
fd.append("file", bulkUsersFile);
return usersResource.uploadBulkUsersFile({}, fd, function (result) {
return result;
}).$promise;
};
and on server side I use webapi2:
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
foreach (var file in provider.Contents)
{
var buffer = await file.ReadAsStreamAsync();
TextReader textReader = new StreamReader(buffer);
var csv = new CsvReader(textReader);
var records = csv.GetRecords<BulkUploadUser>().ToList();
return Created("DefaultApi", records);
}
http request payload
------WebKitFormBoundarySKPlgJRINOMnpxVP
Content-Disposition: form-data; name="file"
data:application/vnd.ms-excel;base64,RW1haWwsRmlyc3ROYW1lLExhc3ROYW1lDQpwcm9zdG8uZHVkYUBnbWFpbC5jb20yLERlbmlzLER1ZGFpZXYNCnByb3N0by5kdWRhQGdtYWlsLmNvbSxEZW5pcyxEdWRhaWV2DQpwcm9zdG8uZHVkYUBnbWFpbC5jb20yLERlbmlzLER1ZGFpZXYNCg==
------WebKitFormBoundarySKPlgJRINOMnpxVP--
UPDATE
#Ubercode suggest me convert base 64 to string, I made it, but is looks very disgusting:
var buffer = await file.ReadAsStreamAsync();
TextReader textReader = new StreamReader(buffer);
var text = textReader.ReadToEnd();
var indexOfWord = "base64,";
var base64EncodedBytes = System.Convert.FromBase64String(text.Substring(text.IndexOf(indexOfWord) + indexOfWord.Length));
var encoded = System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
TextReader textReader2 = new StringReader(encoded);
var csv = new CsvReader(textReader2);
var records = csv.GetRecords<BulkUploadUser>().ToList();

You need to decode the base64 encoded stuff into your file:
How do I encode and decode a base 64 string
You can tidy your code up a little bit thus:
string text;
using(TextReader textReader = new StreamReader(buffer))
text = textReader.ReadToEnd();
CsvReader csv;
using(var ms
= new MemoryStream(Convert.FromBase64String(text.Substring(text.IndexOf(',') + 1)))
using (var textReader2 = new StreamReader(ms))
csv = new CsvReader(textReader2);
var records = csv.GetRecords<BulkUploadUser>().ToList();

if (!Request.Content.IsMimeMultipartContent())
{
return StatusCode(HttpStatusCode.UnsupportedMediaType);
}
HttpPostedFile uploadedFile = HttpContext.Current.Request.Files[0];
var csv = new CsvReader(uploadedFile.InputStream);

The uploaded data is indeed csv, not excel:
Email,FirstName,LastName
prosto.duda#gmail.com2,Denis,Dudaiev
prosto.duda#gmail.com,Denis,Dudaiev
prosto.duda#gmail.com2,Denis,Dudaiev
It would be best to prevent the data being converted to base64 in the first place (it's bigger and needs decoding).
For some reason, the data is send as 'excel' and in base64:
data:application/vnd.ms-excel;base64,
So you probably want to check the following:
headers: {
"Content-Type": undefined
},
transformRequest: angular.identity,
I don't know AngularJS, but you might want to change Content-Type to text/csv and check transformRequest.
Or maybe it gets converted to base64 here:
var fd = new FormData();
fd.append("file", bulkUsersFile);
So it would be best to check where the data gets converted and fix it to send plain text instead. This will prevent the encoding/decoding step, and the data will be smaller too.

Just had the same problem, thanks for the advices in this post.
First of all, there are two possibilities to upload a file: Http-POST-Content or form-data (is post too, but not equally the same).
I'm using the simpler method, the Http-POST-Content. There are no problems with base64 encoding, etc, the solution stays simple, but I think it can be pretty uneficcent with huge files.
I used the follwing code in the angular Project:
Angular Code
Import Dialog (html):
<input hidden #imageInput type="file" accept=".csv" (change)="processFile(imageInput)">
<button mat-button class="image-upload-button full-width" (click)="imageInput.click()">
<mat-icon>add</mat-icon>
</button>
Dialog Code (.ts)
const file: File = imageInput.files[0];
this.dialogRef.close(file);
ts-Code executed after dialog close
const url = 'myapi/Import';
return this.http.post<ImportResult>(url, file).toPromise();
C# Code
try
{
var result = new ImportResult();
s = System.Web.HttpContext.Current.Request.GetBufferedInputStream();
using (var sr = new System.IO.StreamReader(s))
{
s = null;
var reader = new CsvReader(sr, new CsvConfiguration
{
Delimiter = ";"
});
var records = reader.GetRecords<RowEntry>();
...
}
return result;
}
finally
{
s?.Dispose();
}
Hope this will help, it's a simple solution working fine.

Related

How to save the response in the Postman same as the name I mentioned it?

This piece of code will put all the data(string) as an XML format in the Postman. But when I try to save the response in the Postman so, it will save as response.xml, which I don't want. I want that Postman will save my XML content in the form of filename.xml.
So, how should I proceed with? I have to google it and found that something like attachment, content-type, and all must be used. But, didn't solve my case. Please help with your suggestion.
For simplicity, I have reduced the code. Here, graphics is string datatype.
public ActionResult GetXML()
{
string filename = "Demo";
return Content(graphics, "application/xml");
}
You need to set Content-Disposition header so the that browser prompts for saving response.
You can do that by using following approach.
public IActionResult Getxml()
{
var xmlData =
"<records><record><Name>Camacho, Sydnee Q.</Name><Id>1</Id><Age>19</Age><City>Podolsk</City></record><record><Name>Bowman, Lester V.</Name><Id>2</Id><Age>21</Age><City>Padang</City></record></records>";
//sampleFile.xml can be replaced by any filename of your choice.
var fileName = "sampleFile.xml";
Response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}");
return Content(xmlData, "application/xml", Encoding.UTF8);
}
With this change when this URL is browsed in the browser it will prompt save dialog with sampleFile.xml populated as filename.
Also when you save the response from postman it will show sampleFile.xml populated as file name in the dialog.
I hope this will help you solve your issue.
I would suggest to use the FileContentResult for a file download.
public ActionResult GetXML()
{
var fileName = "Demo.xml";
var xml = "...";
return new FileContentResult(Encoding.UTF8.GetBytes(xml), "application/xml; charset=utf-8")
{
FileDownloadName = fileName,
};
}
You need to set the response headers so that the download client automatically treats the content as a "file", the key is the ContentDisposition header, this is how we pass the filename back.
public HttpResponseMessage GetXMLFile()
{
string filename = "Demo";
var byteArray = System.Text.Encoding.UTF8.GetBytes(graphics);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(new MemoryStream(byteArray), byteArray.Length)
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") //attachment will force download
{
FileName = filename
};
return result;
}

Downloading a .doc extension file saved in stream memory - returned from web service api (.asmx file) to angular service

I have a web service (.asmx file extension), which creates a Document (Aspose.Words) which I save to MemoryStream.
I would like the user to download the file on click - without having to save the actual file anywhere but to the users computer when he downloads.
I am struggling with this for quite a while...
even if i manage to download the file - the content is not correct.
This is my angular code:
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
Generate() {
this.http.get(
`${this.environment.Url}Generate`, {
headers: headers,
responseType: 'arraybuffer'
}
).subscribe((res: any) => {
this.downLoadFile(res, 'application/msword');
}
);
}
downLoadFile(data: any, type: string) {
let blob = new Blob([data], { type: type});
let url = window.URL.createObjectURL(blob);
let pwa = window.open(url);
if (!pwa || pwa.closed || typeof pwa.closed === 'undefined') {
alert( 'Please disable your Pop-up blocker and try again.');
}
}
And this is what i did in the web service:
public async Task<Stream> Generate() {
Document doc = _Generate(profileToCV);
MemoryStream dstStream = new MemoryStream();
doc.Save(dstStream, SaveFormat.Docx);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(dstStream);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "foo";
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/msword");
response.Content.Headers.ContentLength = dstStream.GetBuffer().Length;
return await response.Content.ReadAsStreamAsync();
}
I tried alot of different variations.
like changing the responseType returned to 'blob' or 'blob' as 'json',
or returning the HttpResponseMessage.
nothing worked as expected.
I can't seem to extract the content saved in what is returned to client properly in file.
so - Help! ... Thanks in advance!
Just create a link element and fire click event,
this.blob = new Blob([data], {type: 'application/pdf'}); // your file format
var downloadURL = window.URL.createObjectURL(data);
var link = document.createElement('a');
link.href = downloadURL;
link.download = "help.pdf";
link.click();

Tweetsharp SendTweetWithMedia from url

I am trying to use Tweetsharp's SendTweetWithMedia with a image which I don't have stored locally, I only have a url. All the examples I have found of SendTweetWithMedia use a file on the local system.
var thumb = "http://somesite.net/imageurl";
var service = new TwitterService(key, secret);
service.AuthenticateWith(token, tokenSecret);
var req = WebRequest.Create(thumb);
using (var stream = req.GetResponse().GetResponseStream())
{
response = service.SendTweetWithMedia(new SendTweetWithMediaOptions
{
Status = tweet.Trim(),
Images = new Dictionary<string, Stream> { { fullname, stream } }
});
}
I get the following error from SendTweetWithMedia:
'System.NotSupportedException': This stream does not support seek operations.
I could download the file from the url and save locally, but I'd rather use the url. Is this possible?
In the end, I just created a temporary file:
byte[] data;
using (var client = new WebClient())
{
data = client.DownloadData(thumb);
}
File.WriteAllBytes($"{Path.GetTempPath()}\\xyz.jpg", data);
best answer I could come up with. Still a few more lines than I'd like though.

Failed to load PDF document in web api

I am getting error while downloading and opening pdf files as
Failed to load PDF document
. But when i tried to download txt file it gets download success fully. I need to this ajax request to be as POST method, So after searching in internet i found this code.
$.ajax({
type: "POST",
url: url,
cache: false,
contentType: false,
processData: false,
success: function (data) {
var blob = new Blob([data]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = textName;
link.click();
}, error: function (data) { alert("error"); }});
I use web api for downloading the file
public HttpResponseMessage Download(string fileName)
{
string filePath = Path.Combine(PATH, fileName.Replace('/', '\\'));
byte[] pdf = System.IO.File.ReadAllBytes(filePath);
HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new ByteArrayContent(pdf);
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "MyPdf.pdf";
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return result;
}
Please help me
Referencing Download pdf file using jquery ajax
jQuery has some issues loading binary data using AJAX requests, as it
does not yet implement some HTML5 XHR v2 capabilities
It provides two options for you try. I won't repeat them here as they belong to that answer. Check the link.
On the server side, I've had success downloading PDF files using this method.
[HttpPost]
public HttpResponseMessage Download(string fileName)
{
string filePath = Path.Combine(PATH, fileName.Replace('/', '\\'));
byte[] pdf = System.IO.File.ReadAllBytes(filePath);
//content length for header
var contentLength = pdf.Length;
var statuscode = HttpStatusCode.OK;
var result = Request.CreateResponse(statuscode);
result.Content = new StreamContent(new MemoryStream(buffer));
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
result.Content.Headers.ContentLength = contentLength;
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "MyPdf.pdf";
return result;
}
Not that much different to your original.
You should also check to make sure that the original file was not corrupt on the server if you are reading from disk/database or if you are dynamically generating the file.
The problem is probably your Content-Disposition header. You need inline, but you have attachment.
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("inline");
What you have now tells the browser to specifically send the file directly, and never attempt to display it in-browser. Chrome's internal PDF viewer currently chokes when you do this.
More: Content-Disposition:What are the differences between "inline" and "attachment"?

Using CSVHelper to output stream to browser but unable to save file

I am trying to achieve exactly what the poster in this question is trying to do except the browser (Chrome) doesn't attempt to download the .csv file or present the user with a download box. I am using a HTTP GET request and the code is virtually identical in the question above. I'm using Fiddler to monitor the traffic and I can see the .csv file stream in the response, but the browser seems to be ignore it and I can't work out why...
Here is my current implemenation (base on #MattThrower's question):
I make an AJAX call to the MVC controller:
$("#exportToCsvLink").click(function () {
$.ajax({
type: "GET",
url: window.project.common.routes.urls.ExportChartDataToCsv,
data: { parameterId: parameter.parameterId }
});
});
The MVC controller processes the CSV export and returns a FileStreamResult
public FileStreamResult ExportChartDataToCsv(int parameterId)
{
List<TestViewModel> data = _CommonService.GetData(parameterId);
var result = WriteCsvToMemory(data);
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
public byte[] WriteCsvToMemory(IEnumerable<TestViewModel> data)
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(data);
streamWriter.Flush();
return memoryStream.ToArray();
}
}

Categories