Saving a excel file in frontend retrieved as byte array from api - c#

I'am trying to return a Excel file as a byte array from a ASP.NETcore API with a ajax post, but I have some problems.
I know it is easier to return a IActionResult, but for this case I need it to be in a ajax call.
The backend seems to work somewhat. I create the excel report and return a byte array:
[HttpPost]
[Route("api/excel/")]
public byte[] ExportToExcel([FromBody] List<CarReports> carReports)
{
var orderReports = monthlyReports.OrderBy(x => x.Project.Code).ToList();
var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("Fakturering");
// Here I create the excelsheet
var returnStream = new MemoryStream();
workbook.SaveAs(returnStream);
byte[] arr = returnStream.ToArray();
returnStream.Flush();
returnStream.Close();
return arr;
}
In the frontend I use filesaver.js to create a blob and save as:
export function excelExport(filteredReports, type){
return dispatch => {
dispatch({ type: EXCEL_EXPORT, filteredReports, type });
dispatch(loading());
return client.excelExport(filteredReports, type)
.catch(errors => {
dispatch({type: EXCEL_EXPORT_FAIL})
dispatch(error(errors))
})
.then((response) => {
console.log(response);
const blob = new Blob([response], {
type:
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
fileSaver.saveAs(blob, "test.xlsx");
dispatch(done());
});
}
}
The code seems to work, but when i open the file it looks like this:
It seems like the byte array has been put in the excel sheet.
Does anyone know what i'am doing wrong? Maybe i'am approaching this all wrong

Related

Get processed word file from c# endpoint to nodejs server

I have a nodejs server that sends a GET request with axios to a c# endpoint with json as a parameter. My c# api uses Newtonsoft.Json to deserialize the json, then it reads a word file into memory, and inserts data. The final step I need is for this api to respond by sending the modified document back to the nodejs server. Currently, the c# endpoint is called, and a response is sent back. Upon writing the word document using the archiver library and opening it, a dialogue box appears, saying "Word found unreadable content in export0.docx. Do you want to recover the contents of this document? If you trust the source of this document, click Yes"
async exportToDotnet() {
return await axios.get(`https://localhost:8082/test/${JSON.stringify(this)}`, { responseType: 'arrayBuffer' }).catch(err => {
console.log(`ERR `, err);
}).then((axiosResponse) => {
const data = axiosResponse.data;
console.log(`DATA `, data);
console.log(`DATA LENGTH '${data.length}'`);
return data;
});
}
async function writeZipFile(resultFromExportToDotnet) {
const output = createWriteStream('exported.zip');
output.on("close", () => {
console.log("success????");
});
const archive = archiver('zip');
archive.on('error', (err) => {
console.log('error in archive ', err);
});
archive.append(form, { name: `export0.docx` });
archive.pipe(output);
await archive.finalize();
}
[HttpGet("test/{json}")]
public byte[] ExportDocumentBuffer(string json)
{
Console.WriteLine("Called");
//Converts the json passed in to a FormInstance Object
FormInstance form = JsonConvert.DeserializeObject<FormInstance>(json);
//Read the dotx into memory so we can use it. Would it be better to just use System.IO.File.ReadAllBytes()?
MemoryStream dotxBytes = ReadAllBytesToMemoryStream("test.dotx");
//Read the file bytes into a WordProcessingDocument that we can edit
WordprocessingDocument template = WordprocessingDocument.Open(dotxBytes, true);
template.ChangeDocumentType(WordprocessingDocumentType.Document);
template = ParseFormAndInsertValues(form, template);
byte[] output = dotxBytes.ToArray();
Console.WriteLine($"BYTES '{output.Length}'");
return output;
}
///<summary>Reads all Bytes of the provided file into memory</summary>
///<param name="path">The path to the file</param>
///<returns>A MemoryStream of the file data</returns>
public static MemoryStream ReadAllBytesToMemoryStream(string path)
{
byte[] buffer = System.IO.File.ReadAllBytes(path);
MemoryStream destStream = new MemoryStream(buffer.Length);
destStream.Write(buffer, 0, buffer.Length);
destStream.Seek(0, SeekOrigin.Begin);
return destStream;
}
Things I've tried
Changing the axios responsetype to 'stream', converting the response to a buffer with a function, and writing it to a file
function stream2buffer(stream) {
return new Promise((resolve, reject) => {
const _buf = [];
stream.on("data", (chunk) => _buf.push(chunk));
stream.on("end", () => resolve(Buffer.concat(_buf)));
stream.on("error", (err) => reject(err));
});
}
Changing my c# method to return a HttpResponseMessage
HttpResponseMessage result = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new ByteArrayContent(dotxBytes.ToArray())
};
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "exampleName.docx"
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
Logging the length of byte[] and logging data.length produce 2 different numbers (52107 and 69476, respectively). Is this just a serialization issue? Obviously I'm missing something. Any help would be much appreciated!
Turns out to have been a few things: I used template = WordProcessingDocument.Open(), but never called template.Save() or template.Close() and as such, my changes were never written, and the file was still open. Once I got my byte array output, I used Convert.ToBase64String(output) and returned the string. On the NodeJs side, I changed the responsetype to 'text', and returned Buffer.from(axiosResponse.data, 'base64'); and wrote the file that way.

How to read incoming excel file in base64 and extract their data in C# asp.net

I am making an API using C# that takes excel files and transforms and saves its data to database.
I am transforming the excel sheet to base64 and then convert it using epplus to excel sheet.
Problem:
I want to access the sheet without having to save the file.
My Code so far:
public List<ApplicantEngExamBO> receiveAndSaveApplicantData(string database64, int exam_ID)
{
//converting file to byte[]
byte[] byteArray = Convert.FromBase64String(database64);
using (MemoryStream memStream = new MemoryStream(byteArray,0,byteArray.Length))
{
ExcelPackage package = new ExcelPackage(memStream);
package.Load(memStream);
//Just testing if it got any correct data
//It is not working
byte[] data = package.GetAsByteArray("ID");
var res = new List<ApplicantEngExamBO>();
foreach (var d in data)
{
res.Add(new ApplicantEngExamBO { studID = (int)d });
}
//return package;
return res;
}
}
How can we read the contents of the sheet without saving it, what to do next to get the data ? Other columns include "email".

Returning a PDF from an ASP.NET Core 2 Controller

I am trying to return a PDF file from my ASP.NET Core 2 controller.
I have this code
(mostly borrowed from this SO question):
var net = new System.Net.WebClient();
//a random pdf file link
var fileLocation = "https://syntera.io/documents/T&C.pdf";/
var data = net.DownloadData(fileLocation);
MemoryStream content = null;
try
{
content = new MemoryStream(data);
return new FileStreamResult(content, "Application/octet-stream");
}
finally
{
content?.Dispose();
}
This code above is part of a service class that my controller calls. This is the code from my controller.
public async Task<IActionResult> DownloadFile(string fileName)
{
var result = await _downloader.DownloadFileAsync(fileName);
return result;
}
But I keep getting ObjectDisposedException: Cannot access a closed Stream.
The try and finally block was an attempt to fix it , from another SO question .
The main question is A) Is this the right way to send a PDF file back to the browser and B) if it isn't, how can I change the code to send the pdf to the browser?
Ideally , I don't want to first save the file on the server and then return it to the controller. I'd rather return it while keeping everything in memory.
The finally will always get called (even after the return) so it will always dispose of the content stream before it can be sent to the client, hence the error.
Ideally , I don't want to first save the file on the server and then return it to the controller. I'd rather return it while keeping everything in memory.
Use a FileContentResult class to take the raw byte array data and return it directly.
FileContentResult: Represents an ActionResult that when executed will write a binary file to the response.
async Task<IActionResult> DownloadFileAsync(string fileName){
using(var net = new System.Net.WebClient()) {
byte[] data = await net.DownloadDataTaskAsync(fileName);
return new FileContentResult(data, "application/pdf") {
FileDownloadName = "file_name_here.pdf"
};
}
}
No need for the additional memory stream
You must specify :
Response.AppendHeader("content-disposition", "inline; filename=file.pdf");
return new FileStreamResult(stream, "application/pdf")
For the file to be opened directly in the browser.

Office/Word Add-in Uploading File to MVC Application

I am building an add-in for Word, with the goal of being able to save the open Word document to our MVC web application. I have followed this guide and am sending the slices like this:
function sendSlice(slice, state) {
var data = slice.data;
if (data) {
var fileData = myEncodeBase64(data);
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (request.readyState == 4) {
updateStatus("Sent " + slice.size + " bytes.");
state.counter++;
if (state.counter < state.sliceCount) {
getSlice(state);
}
else {
closeFile(state);
}
}
}
request.open("POST", "http://localhost:44379/api/officeupload/1");
request.setRequestHeader("Slice-Number", slice.index);
request.setRequestHeader("Total-Slices", state.sliceCount);
request.setRequestHeader("FileId", "abc29572-8eca-473d-80de-8b87d64e06a0");
request.setRequestHeader("FileName", "file.docx");
request.send(fileData);
}
}
And then receiving the slices like this:
public void Post()
{
if (Files == null) Files = new Dictionary<Guid, Dictionary<int, byte[]>>();
var slice = int.Parse(Request.Headers.GetValues("Slice-Number").First());
var numSlices = int.Parse(Request.Headers.GetValues("Total-Slices").First());
var filename = Request.Headers.GetValues("FileName").First();
var fileId = Guid.Parse(Request.Headers.GetValues("FileId").First());
var content = Request.Content.ReadAsStringAsync().Result;
if (!Files.ContainsKey(fileId)) Files[fileId] = new Dictionary<int, byte[]>();
Files[fileId][slice] = Convert.FromBase64String(content);
if (Files[fileId].Keys.Count == numSlices)
{
byte[] array = Combine(Files[fileId].OrderBy(x => x.Key).Select(x => x.Value).ToArray());
System.IO.FileStream writeFileStream = new System.IO.FileStream("c:\\temp\\test.docx", System.IO.FileMode.Create, System.IO.FileAccess.Write);
writeFileStream.Write(array, 0, array.Length);
writeFileStream.Close();
Files.Remove(fileId);
}
}
The problem is that the file that is produced by the controller is unreadable in Word. I have tested with a word document with "Test123" as the entire contents of the document, and when the file is saved through word it is 13kb, but when sent to the web app and saved from there the file is 41kb.
My assumption is that the I am missing something either with the encoding or decoding, since I am only sending a single slice so there shouldn't be an issue with recombining them.
There's an Excel snippet in Script Lab that produces the base64 encoded file which you can paste into an online decoder like www.base64decode.org. The APIs are the same as in Word. This can help you isolate the encoding code. After you install Script Lab, open the Samples tab, scroll to the Document section. It's the Get file (using slicing) snippet.

Return excel file response in ASP.NET MVC

I am building a FileResult method which is supposed to return an excel file as a result.
Currently the method looks like this:
public ActionResult GetExcelFile(int id)
{
var entityPermission = AuthenticationManager.GetEntityPermission(PermissionEntity.Events, PermissionLevel.AllRecords);
if (entityPermission == null || !entityPermission.AllowRead)
return Json(new RequestResult(RequestCode.Unauthorized), JsonRequestBehavior.AllowGet);
try
{
using (var serviceManager = new ServiceManager())
{
// Get object model
var firstTableObj = serviceManager.GetDataForExcel(id);
StringBuilder sb = new StringBuilder();
sb.Append("<table border=`" + "1px" + "`b>");
sb.Append("<tr>");
// ... appending other strings and data
sb.Append("</table>");
// Return FileResult
byte[] byteArray = Encoding.UTF8.GetBytes(sb.ToString());
return File(new MemoryStream(byteArray, 0, byteArray.Length), "application/octet-stream", $"{firstTableObj.CurrentDate}.xlsx");
}
}
catch (Exception ex)
{
return Json(new RequestResult(RequestCode.Server_Failure, ex.Message), JsonRequestBehavior.AllowGet);
}
Currently, when I send a request from Angular 2+ frontend, the request is being processed and returned, this is some of the header data:
Content-Disposition:attachment; filename=12.11.2017.xlsx, Content-Length:617, Content-Type:application/octet-stream
However, the file is not being downloaded, the response body is just the html template in string format. What am I missing? I have seen some examples of returning excel files as a FileResult in MVC and they are not much different from mine. Changing the MIME to 'application/vnd.ms-excel' didn't help either. Also, as far as I am aware, there is no need to implement any file download logic in the client, it should work as is, that is a response from the server.
Will appreciate any hints on the subject.
P.S: I know that in general I should not load an entire file into memory, but this is currently for testing purposes (I know an approximate limit to returned file sizes) and will most surely be changed in the future development.
You are not saving an xlsx file. You are saving an html file with xlsx extension.
You probably adjusted a sample found on the internet, but the initial extension was xls.
An html file with xls extension is not an Excel file either, but MS Excel knows to render the html file and displays the file into the spreadsheet. Still, with the recently MS Excel version a warning is raised that is an invalid file format.
If you need to save xlsx files, you need to search for an Excel library like OpenXML from Microsoft, EPPlus open source or EasyXLS commercial with 30-days trial.
You should change
public ActionResult GetExcelFile(int id)
To
public FileResult GetExcelFile(int id)
And of course you can't handle Json response for FileResult. It should be like that;
public FileResult GetExcelFile(int id)
{
var entityPermission = AuthenticationManager.GetEntityPermission(PermissionEntity.Events, PermissionLevel.AllRecords);
if (entityPermission == null || !entityPermission.AllowRead)
//return Json(new RequestResult(RequestCode.Unauthorized), JsonRequestBehavior.AllowGet);
//Do something except returning Json response
try
{
using (var serviceManager = new ServiceManager())
{
// Get object model
var firstTableObj = serviceManager.GetDataForExcel(id);
StringBuilder sb = new StringBuilder();
sb.Append("<table border=`" + "1px" + "`b>");
sb.Append("<tr>");
// ... appending other strings and data
sb.Append("</table>");
// Return FileResult
byte[] byteArray = Encoding.UTF8.GetBytes(sb.ToString());
return File(new MemoryStream(byteArray, 0, byteArray.Length), "application/octet-stream", $"{firstTableObj.CurrentDate}.xlsx");
}
}
catch (Exception ex)
{
//Do something except returning Json response
}
}

Categories