NPOI export excel directly to frontend without saving it on server - asp.net-core

Is it possible to write an excel file (with NPOI) directly to browser without saving it first on the server.
I tried following in my controller without success:
public async Task<IActionResult> ExportExcel(){
var fs = new MemoryStream();
IWorkbook workbook;
workbook = new XSSFWorkbook();
ISheet excelSheet = workbook.CreateSheet("Test);
IRow row = excelSheet.CreateRow(0);
row.CreateCell(0).SetCellValue("ID");
row.CreateCell(1).SetCellValue("Name");
row = excelSheet.CreateRow(1);
row.CreateCell(0).SetCellValue(1);
row.CreateCell(1).SetCellValue("User 1");
workbook.Write(fs);
byte[] bytes = new byte[fs.Length];
fs.Read(bytes, 0, (int)fs.Length);
return File(bytes, "application/vnd.ms-excel", sampleType.Abbreviation+".xlsx");
}
When executing above method I always get following error:
ObjectDisposedException: Cannot access a closed Stream.
...
System.IO.MemoryStream.get_Length()
byte[] bytes = new byte[fs.Length];
...
Or is their another great nuget package to handle (read and write) excel files without storing files on server?
PS: I am using dotnet core 2.1 en nuget package: https://www.nuget.org/packages/NPOI/

Write directly to the Response.Body.
Because Excel is treated as an attachment, we also need to set the Response.Headers
To make life easy, we can create an extension method firstly:
public static class IWorkBookExtensions {
public static void WriteExcelToResponse(this IWorkbook book, HttpContext httpContext, string templateName)
{
var response = httpContext.Response;
response.ContentType = "application/vnd.ms-excel";
if (!string.IsNullOrEmpty(templateName))
{
var contentDisposition = new Microsoft.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
contentDisposition.SetHttpFileName(templateName);
response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
}
book.Write(response.Body);
}
}
and now we can export the excel file directly :
public async Task ExportExcel(){
IWorkbook workbook;
workbook = new XSSFWorkbook();
ISheet excelSheet = workbook.CreateSheet("Test");
IRow row = excelSheet.CreateRow(0);
row.CreateCell(0).SetCellValue("ID");
row.CreateCell(1).SetCellValue("Name");
row = excelSheet.CreateRow(1);
row.CreateCell(0).SetCellValue(1);
row.CreateCell(1).SetCellValue("User 1");
workbook.WriteExcelToResponse(HttpContext,"test.xlsx");
}

Related

ASP WebAPI 2 _pdf HTTPResponseMessage_couldnt open

Used the below code to read a pdf file and return as response from WebAPI 2.
When I used a text file here and also changed the response FileName="new.txt", then it works fine. Running the WEBAPI in swagger, could download file in the response and the file opens too.
But if its a pdf file, the downloaded file couldnt be opened. Also tried zip and xl files....File is corrupted and couldnt be opened.
[HttpGet]
[Route("GetPDF")]
public IHttpActionResult GetABCPDF()
{
var bytes = System.IO.File.ReadAllBytes(bookPath_Pdf);
var dataStream = new MemoryStream(bytes);
HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(dataStream)
};
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "new.pdf"
};
httpResponseMessage.Content.Headers.ContentLength = dataStream.Length;
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
ResponseMessageResult responseMessageResult = ResponseMessage(httpResponseMessage);
return responseMessageResult;
}

EPPlus - Saving to Memory Stream results in empty file, Saving to File works ok

I have what appears to be a strange problem with C# that I've not been able to figure out, hoping for some help here.
I've written the below demo that creates what is essentially the same file, but one Saves the ExcelPackage to a file using ExcelPackage.SaveAs(FileInfo(filePath)) (which works just fine and as expected), while the other saves to a MemoryStream (which results in a completely empty file).
Interestingly I do have other applications that utilise the same MemoryStream pattern and the file saves just fine, but it seems "temperamental" and can't figure out when it works and when it does not.
Anyone knows why this happens and how I can get it to work via a MemoryStream?
class Program
{
static void Main(string[] args)
{
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
TestXLToFile($#"Export\Test-{DateTime.Now.ToString("dd-MM-yy.H.mm.ss")}.xlsx");
var stream = TestXLToMemStream();
stream.CopyTo(new FileStream($#"Export\TestMs-{DateTime.Now.ToString("dd-MM-yy.H.mm.ss")}.xlsx", FileMode.Create));
}
public static void TestXLToFile(string FilePath)
{
using (ExcelPackage xl = new ExcelPackage())
{
var ws = xl.Workbook.Worksheets.Add("TEST");
ws.Cells[1, 1].Value = "abc123";
xl.SaveAs(new FileInfo(FilePath));
}
}
public static MemoryStream TestXLToMemStream()
{
ExcelPackage xl = new ExcelPackage();
var ws = xl.Workbook.Worksheets.Add("TEST");
ws.Cells[1, 1].Value = "abc123";
MemoryStream ms = new MemoryStream();
xl.SaveAs(ms);
return ms;
}
}
The problem is because you are not calling the Flush and Close methods for the FileStream. You should make use of the Using statement when using Streams as follows:
using(var stream = TestXLToMemStream())
using(var fileStream = new FileStream($#"Export\TestMS-{DateTime.Now.ToString("dd-MM-yy.H.mm.ss")}.xlsx", FileMode.Create, FileAccess.Write))
{
stream.WriteTo(fileStream);
}

how to read excel file in memory (without saving it in disk) and return its content dotnet core

Im working on a webApi using dotnet core that takes the excel file from IFormFile and reads its content.Iam following the article
https://levelup.gitconnected.com/reading-an-excel-file-using-an-asp-net-core-mvc-application-2693545577db which is doing the same thing except that the file here is present on the server and mine will be provided by user.
here is the code:
public IActionResult Test(IFormFile file)
{
List<UserModel> users = new List<UserModel>();
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
using (var stream = System.IO.File.Open(file.FileName, FileMode.Open, FileAccess.Read))
{
using (var reader = ExcelReaderFactory.CreateReader(stream))
{
while (reader.Read()) //Each row of the file
{
users.Add(new UserModel
{
Name = reader.GetValue(0).ToString(),
Email = reader.GetValue(1).ToString(),
Phone = reader.GetValue(2).ToString()
});
}
}
}
return Ok(users);
}
}
When system.IO tries to open the file, it could not find the path as the path is not present. How it is possible to either get the file path (that would vary based on user selection of file)? are there any other ways to make it possible.
PS: I dont want to upload the file on the server first, then read it.
You're using the file.FileName property, which refers to the file name the browser send. It's good to know, but not a real file on the server yet. You have to use the CopyTo(Stream) Method to access the data:
public IActionResult Test(IFormFile file)
{
List<UserModel> users = new List<UserModel>();
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
using (var stream = new MemoryStream())
{
file.CopyTo(stream);
stream.Position = 0;
using (var reader = ExcelReaderFactory.CreateReader(stream))
{
while (reader.Read()) //Each row of the file
{
users.Add(new UserModel{Name = reader.GetValue(0).ToString(), Email = reader.GetValue(1).ToString(), Phone = reader.GetValue(2).ToString()});
}
}
}
return Ok(users);
}
Reference

Get list of named ranges using googlesheets api v4 in c#

I need to be able to get a list of all named ranges in a spreadsheet, but can't figure out how to do this.
I've found the following code but its not c# - how do I do the same thing in c#?
Code that does what I need but not in c#
function getNamedRanges2(spreadsheetId) {
var ss = SpreadsheetApp.openById(spreadsheetId);
var sheetIdToName = {};
ss.getSheets().forEach(function(e) {
sheetIdToName[e.getSheetId()] = e.getSheetName();
});
var result = {};
Sheets.Spreadsheets.get(spreadsheetId, {fields: "namedRanges"})
.namedRanges.forEach(function(e) {
var sheetName = sheetIdToName[e.range.sheetId.toString()];
var a1notation = ss.getSheetByName(sheetName).getRange(
e.range.startRowIndex + 1,
e.range.startColumnIndex + 1,
e.range.endRowIndex - e.range.startRowIndex,
e.range.endColumnIndex - e.range.startColumnIndex
).getA1Notation();
result[e.name] = sheetName + "!" + a1notation;
});
return result;
}
function main() {
var spreadsheetId = "### spreadsheet ID ###";
var result = getNamedRanges2(spreadsheetId);
Logger.log(JSON.stringify(result));
}
Based on the Quickstart for Sheets API and the C# sample code for Method: spreadsheets.get2, you can combine the two code snippets as following, in order to retrieve named ranges:
using Google.Apis.Auth.OAuth2;
using Google.Apis.Sheets.v4;
using Google.Apis.Sheets.v4.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Newtonsoft.Json;
using Data = Google.Apis.Sheets.v4.Data;
namespace SheetsQuickstart
{
class Program
{
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/sheets.googleapis.com-dotnet-quickstart.json
static string[] Scopes = { SheetsService.Scope.SpreadsheetsReadonly };
static string ApplicationName = "Google Sheets API .NET Quickstart";
static void Main(string[] args)
{
UserCredential credential;
using (var stream =
new FileStream("credentials.json", FileMode.Open, FileAccess.Read))
{
// The file token.json stores the user's access and refresh tokens, and is created
// automatically when the authorization flow completes for the first time.
string credPath = "token.json";
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}
// Create Google Sheets API service.
var service = new SheetsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
// The spreadsheet to request.
string spreadsheetId = "YOUR SPREADSHEET ID";
// TODO: Update placeholder value.
SpreadsheetsResource.GetRequest request = service.Spreadsheets.Get(spreadsheetId);
request.Fields = "namedRanges";
// To execute asynchronously in an async method, replace `request.Execute()` as shown:
Data.Spreadsheet response = request.Execute();
// TODO: Change code below to process the `response` object:
Console.WriteLine(JsonConvert.SerializeObject(response));
}
}
}
The response will contain only the named ranges when request.Fields = "namedRanges"; is specified.

EPPPlus cannot open RDLC rendered to EXCELOPENXML

Does anyone have any idea , or even a solution, to open an xlsx file using EPPlus created from an rdlc report.
At the moment, when I try to do this I get an null exception when I try to access the worksheets.
// filename is the xlsx file created by exported rdlc to excel
FileInfo newFile = new FileInfo(filename);
ExcelPackage pck = new ExcelPackage(newFile);
// I get error here
pck.Workbook.Worksheets.Add("New Sheet");
If you intention is to add onto the file you are using the wrong constructor. The one you are using now is for creating a new package off of a template. The on your want is this (without the extra Boolean) which will open the file directly:
ExcelPackage pck = new ExcelPackage(newFile);
Response to Comments
#A.Ellwood I just tried it with the file you posted and it works fine for me. Here is the code I used:
[TestMethod]
public void RDLC_Test()
{
var datatable = new DataTable("tblData");
datatable.Columns.AddRange(new[]
{
new DataColumn("Col1", typeof (int)), new DataColumn("Col2", typeof (int)),
new DataColumn("Col3", typeof (object))
});
for (var i = 0; i < 10; i++)
{
var row = datatable.NewRow();
row[0] = i;
row[1] = i*10;
row[2] = Path.GetRandomFileName();
datatable.Rows.Add(row);
}
var fi = new FileInfo(#"c:\temp\Report1.xlsx");
using (var pck = new ExcelPackage(fi))
{
var workbook = pck.Workbook;
var worksheet = workbook.Worksheets.Add("Sheet1");
worksheet.Cells.LoadFromDataTable(datatable, true);
pck.Save();
}
}