We are developing web api from where excel will be downloaded with data. While searching on the net we found libraries like npoi, epplus, closedxml.
Do we really need to use these libraries to work with excel or go with the standard approach?
We are using asp.net core for web api development.
Edit: Basically our front end is in angular 5 from where we
are exposing the web api. In web api we have written logic to get data and after getting data we need to place in certain format/template provided(Cell, Column wise, sheet wise etc.). There are quite a number of rows which we need to export in excel.
Also our database and apis are azure based.
Any help on this appreciated !
I have used epplus, and i think it works well for this scenario. Let me give you an example.
Exporting Data
private ExcelPackage CreateDoc(string title, string subject, string keyword)
{
var p = new ExcelPackage();
p.Workbook.Properties.Title = title;
p.Workbook.Properties.Author = "Application Name";
p.Workbook.Properties.Subject = subject;
p.Workbook.Properties.Keywords = keyword;
return p;
}
public ExcelPackage getApplicantsStatistics()
{
ExcelPackage p = CreateDoc("Applicant Statistics", "Applicant statistics", "All Applicants");
var worksheet = p.Workbook.Worksheets.Add("Applicant Statistics");
//Add Report Header
worksheet.Cells[1, 1].Value = "Applicant Statistics";
worksheet.Cells[1, 1, 1, 3].Merge = true;
//Get the data you want to send to the excel file
var appProg = _unitOfWork.ApplicantsProgram
.AllIncluding(pr => pr.Program1)
.GroupBy(ap => ap.Program1.Name)
.Select(ap => new { programName = ap.Key, TotalNum = ap.Count() })
.ToList();
//First add the headers
worksheet.Cells[2, 1].Value = "SR No";
worksheet.Cells[2, 2].Value = "Program";
worksheet.Cells[2, 3].Value = "No. of Applicants";
//Add values
var numberformat = "#,##0";
var dataCellStyleName = "TableNumber";
var numStyle = p.Workbook.Styles.CreateNamedStyle(dataCellStyleName);
numStyle.Style.Numberformat.Format = numberformat;
for (int i = 0; i < appProg.Count; i++)
{
worksheet.Cells[i + 3, 1].Value = i + 1;
worksheet.Cells[i + 3, 2].Value = appProg[i].programName;
worksheet.Cells[i + 3, 3].Value = appProg[i].TotalNum;
}
// Add to table / Add summary row
var rowEnd = appProg.Count + 2;
var tbl = worksheet.Tables.Add(new ExcelAddressBase(fromRow: 2, fromCol: 1, toRow: rowEnd, toColumn: 3), "Applicants");
tbl.ShowHeader = true;
tbl.TableStyle = TableStyles.Dark9;
tbl.ShowTotal = true;
tbl.Columns[2].DataCellStyleName = dataCellStyleName;
tbl.Columns[2].TotalsRowFunction = RowFunctions.Sum;
worksheet.Cells[rowEnd, 3].Style.Numberformat.Format = numberformat;
// AutoFitColumns
worksheet.Cells[2, 1, rowEnd, 3].AutoFitColumns();
return p;
}
The returned ExcelPackage object can be sent as a download to the File with MVC
byte[] reportBytes;
using (var package = _excelRep.getApplicantsStatistics())
{
reportBytes = package.GetAsByteArray();
}
return File(reportBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
There are several good libraries for doing so, my favorite ones are
EPPlus and OpenXML by Microsoft
https://github.com/JanKallman/EPPlus
https://learn.microsoft.com/en-us/office/open-xml/open-xml-sdk
There is not much difference what your db and frontend are as everything is organized by the backend.
Related
I know there are many topics on the issue, but my requirements are more specific.. I'm using EF to select records into my project, and then export them to Excel. I've used This snippet code.
Let me explain what I'm trying to do. Given the following table(It's for simplification, as you will see in the code the table is a bit larger):
Name | Content
A "Content1"
A "Content2"
A "Content3"
B "other content"
......
When I export to excel, I don't want A to appear 3 times next to each content, I'd like to have only one "A" (which I was able to do) and merge the 3 cells (align them to the center too if possible) into one(without touching the Content column) .
This is my code:
public IActionResult Index()
{
var knownCampaigns = _repository.getDataForExport();
//return View(result);
string sWebRootFolder = _hostingEnvironment.WebRootPath;
string sFileName = #"demo.xlsx";
string URL = string.Format("{0}://{1}/{2}", Request.Scheme, Request.Host, sFileName);
FileInfo file = new FileInfo(Path.Combine(sWebRootFolder, sFileName));
if (file.Exists)
{
file.Delete();
file = new FileInfo(Path.Combine(sWebRootFolder, sFileName));
}
using (ExcelPackage package = new ExcelPackage(file))
{
// add a new worksheet to the empty workbook
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("CampaignMatches");
//First add the headers
worksheet.Cells[1, 2].Value = "Customer Name";
worksheet.Cells[1, 3].Value = "Guid";
worksheet.Cells[1, 4].Value = "Campaign Title";
worksheet.Cells[1, 5].Value = "Referrer Title";
worksheet.Cells[1, 6].Value = "Activity Date";
worksheet.Cells[1, 7].Value = "Is clicked?";
int counter = 2;
string oldGuid = "";
foreach (var campaign in knownCampaigns)
{
if (oldGuid == campaign.Guid || worksheet.Cells["C" + (counter - 1)].Value.ToString() == campaign.Guid)
{
oldGuid = campaign.Guid;
worksheet.Cells["A" + counter].Value = "";
worksheet.Cells["B" + counter].Value = "";
}
else
{
oldGuid = "";
worksheet.Cells["A" + counter].Value = campaign.customerName;
worksheet.Cells["B" + counter].Value = campaign.Guid;
}
worksheet.Cells["C" + counter].Value = campaign.campaignTitle;
worksheet.Cells["D" + counter].Value = campaign.reffererTitle;
worksheet.Cells["E" + counter].Value = campaign.activityDate;
worksheet.Cells["F" + counter].Value = campaign.is_clicked;
counter++;
}
package.Save(); //Save the workbook.
}
var result = PhysicalFile(Path.Combine(sWebRootFolder, sFileName), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
Response.Headers["Content-Disposition"] = new ContentDispositionHeaderValue("attachment")
{
FileName = file.Name
}.ToString();
return result;
}
Right now, my Customer Name and Guid column only appears once as intended, but I don't know how to merge the cells together into one cell.
Image of current output:
[![enter image description here][2]][2]
Image of wanted output:
[![enter image description here][3]][3]
It looks like it's not that obvious. Looks like there is an Elements property on worksheet that you can add type of MergedCell. https://learn.microsoft.com/en-us/office/open-xml/how-to-merge-two-adjacent-cells-in-a-spreadsheet
If someone will face the same issue, I've managed to solve this using Merge attribute , I had to extract the positions of the start column index, row index and end row index & end column index.
var Wsc_Guid = worksheet.Cells[startRowIndex, guidIndex, startRowIndex + numOfSameRecords, guidIndex];//Select the correct cells for Guids
Wsc_Guid.Merge = true;
Without going into too much detail, this is my first project. I am very new at coding and this may be a very simple oversight on my part. I am an engineer and I need to add some "custom properties" to over 1000+ CAD files. I am using excel to assign set properties to each file.
I am using EPPlus.
I have a using OfficeOpenXml; statement.
During debugging, I get an error at line 73: worksheet.Cells[row, 6].Value = file;
System.NullReferenceException: 'Object reference not set to an instance of an object.worksheet was null."
I can see that the value for "var worksheet" at line 64 is null but I can't figure out why...
public static void InsertPropName2()
{
string ExFile = #"J:\VB Test\Excel\SW.Prop.xlsx";
var FileInfo = new FileInfo(ExFile);
List<string> DrwPropName = new List<string>()
{
"MPN",
"Description",
"OEM Location",
"Material",
"Surface Finish",
"Hardness",
"Color",
};
List<string> PartPropName = new List<string>()
{
"MPN",
"Description",
"Drawn",
"Q.A. Approved",
"Checked",
"Engineering Approved",
"Revision",
"Drawn By:",
"Drawn Date",
"Q.A. Approval",
"Q.A. App. Date",
"Checked By",
"Checked Date",
"Engineering Approval",
"Engineering App. Date",
};
List<int> PartPropType = new List<int>()
{
30,
30,
30,
30,
30,
30,
30,
30,
64,
30,
64,
30,
64,
30,
64,
};
using (ExcelPackage excel = new ExcelPackage(FileInfo))
{
string directory = #"J:\Move (Test)\a";
string[] Files = Directory.GetFiles(directory);
foreach (string file in Files)
{
string WSName = file.Substring(file.Length - 31);
//line 64
var worksheet = excel.Workbook.Worksheets[WSName];
string FileType = file.Substring(file.Length - 6);
switch (FileType)
{
case "SLDDRW":
int row = 1;
int a = 0;
//line 73
worksheet.Cells[row, 6].Value = file;
foreach (string prop in DrwPropName)
{
worksheet.Cells[row, 7].Value = DrwPropName[a];
worksheet.Cells[row, 8].Value = 30;
a++;
}
break;
case "SLDPRT":
int row2 = 1;
int b = 0;
worksheet.Cells[row2, 6].Value = file;
foreach (string prop in PartPropName)
{
worksheet.Cells[row2, 7].Value = PartPropName[b];
worksheet.Cells[row2, 8].Value = PartPropType[b];
b++;
row2++;
}
break;
case "SLDASM":
int row3 = 1;
int c = 0;
worksheet.Cells[row3, 6].Value = file;
foreach (string prop in PartPropName)
{
worksheet.Cells[row3, 7].Value = PartPropName[c];
worksheet.Cells[row3, 8].Value = PartPropType[c];
c++;
row3++;
}
break;
default:
break;
}
}
excel.Save();
}
}
Any help is greatly appreciated. Again sorry if this is a very simple noob error!
edit:
I have another class that I run before I run this that creates an excel file with the worksheets in question above so I know the worksheet exists. I have also visual checked to see if that the excel file has the worksheets in question. the code in the other class executes and I have no errors with that part of my project.
The WSName is not declared anywhere else in the class so It is not the same as other NullReferenceException threads.
Part of the code that's in the other class:
using (ExcelPackage excel = new ExcelPackage(FileInfo))
{
string WSName = file.Substring(file.Length - 31);
var worksheet = excel.Workbook.Worksheets.Add(WSName);
Thanks for trying to solve the issue. I figured out what the problem was.
WSName was set from code Directory.GetFiles(directory); so I can keep track of the properties to each CAD file.
When the first class member used the value of WSName to create the worksheet. The value for WSName had "\". The actual name of the worksheet omitted the "\" even though I used the "#" symbol in front of the string. It must me a limitation of excel. When the second class member tried to write to the worksheet even though WSName was coded the same way as the first member it could not find it due to the missing "\".
First of all if this is the wrong place to ask this question, let me know.
Now I'll get right into it as clear as I can.
I generate and Excel file using EPPlus, from a C# application.
The file generates ok, but while using filters on it (not the ones from EPPlus, but from Excel itself) it dose not filter everything. It filters a few rows.
I can provide code snippets, or the excel generated file. I have searched the web a lot before coming here, but I have not found this problem encountered by someone else.
Thank you in advance for your input.
enter code here
ExcelPackage ExcelPkg = new ExcelPackage();
ExcelWorksheet wsSheet1 = ExcelPkg.Workbook.Worksheets.Add("Sheet1");
#region Create table and headers
using (ExcelRange Rng = wsSheet1.Cells[TableRange])
{
//Create Table
ExcelTable table = wsSheet1.Tables.Add(Rng, "OSTable");
for (int i = 0; i < headers.Count; i++)
{
table.Columns[i].Name = headers[i];
}
table.ShowHeader = true;
table.ShowFilter = false;
}
#endregion
#region Insert data into the Excel Table Cells
foreach (ArticleRow ar in mainArticleList)
{
for (int i = 0; i < ar.Qty.Count; i++)
{
currentRow++;
string thisRowRange = "A" + currentRow.ToString() + ":" + "Q" + currentRow.ToString();
#region BOM_Position Col: A
using (ExcelRange Rng = wsSheet1.Cells["A" + currentRow.ToString()])
{
Rng.Value = Convert.ToInt32(ar.BOM_Position);
Rng.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
Rng.Style.Font.Size = FontSize;
}
#endregion
#region Input Col: B
using (ExcelRange Rng = wsSheet1.Cells["B" + currentRow.ToString()])
{
Rng.Value = ar.Input;
Rng.Style.Font.Size = FontSize;
}
#endregion
#region QTY Col: J
using (ExcelRange Rng = wsSheet1.Cells["J" + currentRow.ToString()])
{
Rng.Value = ar.Qty[i];
Rng.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
Rng.Style.Font.Size = FontSize;
}
#endregion
#endregion
#region Price Col: L
using (ExcelRange Rng = wsSheet1.Cells["L" + currentRow.ToString()])
{
Rng.Value = ar.Price[i];
Rng.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
Rng.Style.Font.Size = FontSize;
}
#endregion
#endregion
#region ArticleUrl Col: Q
using (ExcelRange Rng = wsSheet1.Cells["Q" + currentRow.ToString()])
{
Rng.Formula = "=HYPERLINK(\"" + ar.Link + "\", \"" + ar.Store + "\")";
Rng.StyleName = StyleName;
Rng.Style.Font.Size = FontSize;
}
#endregion
#region Formatting Row Color by Store
using (ExcelRange Rng = wsSheet1.Cells[thisRowRange])
{
Rng.Style.Border.Top.Style = ExcelBorderStyle.None;
Rng.Style.Border.Bottom.Style = ExcelBorderStyle.None;
Rng.Style.Border.Left.Style = ExcelBorderStyle.Thin;
Rng.Style.Border.Left.Color.SetColor(Color.LightCyan);
Rng.Style.Border.Right.Style = ExcelBorderStyle.Thin;
Rng.Style.Border.Right.Color.SetColor(Color.LightCyan);
Rng.Style.Fill.PatternType = ExcelFillStyle.Solid;
if (ar.Store == "Farnell" | ar.Store == "FarnellBETA")
{ Rng.Style.Fill.BackgroundColor.SetColor(ColorFarnellRow); }
else if (ar.Store == "Mouser")
{ Rng.Style.Fill.BackgroundColor.SetColor(ColorMouserRow); }
}
#endregion
#region Formatting Stock Cell Color by Content
using (ExcelRange Rng = wsSheet1.Cells["I" + currentRow.ToString()])
{
if (ar.Stock == "0" | ar.Stock == "-" | ar.Stock == "n/a" | ar.Stock == "--")
{
Rng.Style.Fill.PatternType = ExcelFillStyle.Solid;
Rng.Style.Fill.BackgroundColor.SetColor(Color.Red);
}
}
#endregion
}
}
#endregion
I have deleted some repetitive parts of the code, as to not clutter you.
Why dont you try TableStyle: OfficeOpenXml.Table.TableStyles.Medium6 , this is part of EpPlus. and you can apply this with header.The tab style gives good UI looks(BackgroundColor) as well as auto filter.
Anyway you are writing so many additional code for Style, then you are filtering as well(which is not working for you).
I have found out the source of my problem , and I've decided to post an answer maybe it will help someone in the future.
I had given a wrong Table Range, making my table smaller than all the data I was writing, and the rest of the data was written outside the table. That's why I could not filter. The filters were working but only on a part of the table.
I want to create a C# Console app (or a service - not sure how to develop a service yet) that:
1) Knows when a new email is received in Inbox>LOTALogs folder. This email is sent by a mobile application and includes an attachment and some issues that the customer experienced.
2) Takes the new email content, which is comma-separated, parses and appends the content into an Excel worksheet that already has the columns set up.
I managed to create:
1) The parser:
public static string[] emailContentsArray()
{
string content = "Username = Customer1,User ID = 362592,Unit ID = 805618,Date = Mar 12, 2017,Device = Android LGE LG-H990,OS version = 7.0 (API 24),App version = 1.0.0.56,Description = some description,Message = some message";
string[] contentArray = content.Split(',');
// Case where date format includes another comma
if (contentArray.Length > 10)
{
// Parsing headers
contentArray[0] = contentArray[0].Substring(11);
contentArray[1] = contentArray[1].Substring(10);
contentArray[2] = contentArray[2].Substring(10);
contentArray[3] = contentArray[3].Substring(7) + ", " + contentArray[4].Substring(1);
contentArray[4] = contentArray[5].Substring(9);
contentArray[5] = contentArray[6].Substring(13);
contentArray[6] = contentArray[7].Substring(14);
contentArray[7] = contentArray[8].Substring(14);
contentArray[8] = contentArray[9].Substring(10);
contentArray[9] = null;
for (int i = 0; i < contentArray.Length; i++)
{
Console.Write(contentArray[i] + ",");
}
}
//else
//{
//}
return contentArray;
}
2) Accessed the folder and counted the number of items:
public static string[] emailContent()
{
string[] content = null;
Microsoft.Office.Interop.Outlook.Application app = null;
Microsoft.Office.Interop.Outlook.NameSpace ns = null;
Microsoft.Office.Interop.Outlook.MAPIFolder inboxFolder = null;
Microsoft.Office.Interop.Outlook.MAPIFolder logFolder = null;
app = new Microsoft.Office.Interop.Outlook.Application();
ns = app.GetNamespace("MAPI");
inboxFolder = ns.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox);
logFolder = app.ActiveExplorer().CurrentFolder = inboxFolder.Folders["LOTALogs"];
int itemCount = logFolder.Items.Count;
Console.WriteLine("\n\nFolder Name: {0}, Num Items: {1}\n", logFolder.Name, itemCount);
return content;
}
3) Opened and printed the contents of the spreadsheet:
Excel.Application xlApp = new Excel.Application();
string path = "C:\\SomeUser\\BugReports";
Excel.Workbook xlWorkbook = xlApp.Workbooks.Open(#path);
Excel.Worksheet xlWorksheet = xlWorkbook.Sheets[1];
Excel.Range xlRange = xlWorksheet.UsedRange;
for (int i = 1; i <= xlRange.Row + xlRange.Rows.Count - 1; i++)
{
for (int j = 1; j <= xlRange.Column + xlRange.Columns.Count - 1; j++)
{
if (j == 1)
Console.Write("\r\n");
if (xlRange.Cells[i, j] != null && xlRange.Cells[i, j].Value2 != null)
Console.Write(xlRange.Cells[i, j].Value2.ToString() + "\t");
}
}
xlWorkbook.Save();
xlWorkbook.Close();
xlApp.Quit();
Console.ReadLine();
I am a little lost now :)
I still need to:
1) Create an event listener (I think that's what it's called) so I can tell the email body parser to go fetch the email contents.
2) Extract the email body from the email.
Got this using
Console.WriteLine(logFolder.Items[1].Body);
3) Take the email content and append it to the spreadsheet.
4) Should I create this as a Windows Service?
PS - I am not a developer, just fiddling around with code and trying to be as efficient as possible. I don't want to fill this spreadsheet out manually when there's a technological solution in sight. Please comment if you have any suggestions on being more efficient with the code and model it differently.
Looks solid to me. I'd refrain from the service, but it greatly depends on your users. Unless your customer really wants to be "blind" from the whole process, it adds a lot of unwarranted complications.
As for appending to the spreadsheet...
int lastRow = xlWorksheet.UsedRange.Rows;
Excel.Range xlRange = xlWorksheet.Cells[lastRow + 1, 1];
xlRange.Value = stuffFromInbox;
If you're only adding the one item to the spreadsheet, this will work fine. For mass read/write operations with the spreadsheet (like your "Opened and printed the contents of the spreadsheet"), it would be much more efficient to read the Value or Value2 of the entire Range into a object[,]. Then iterate through the local array.
I have a cell with a formula, that I need to invoke Calculate() method on, to get the result.
Somehow I cannet invoke the Calculate method on any cells in my sheet, what am I missing?
I am using EPPlus version 4.0.5.0.
My code is as follows:
ws2.Cells[failedRow, failedColumn + 1].Formula = "SUM(B20:B" + (failedRow - 1) + ")";
And I need Calculate on that same cell. Any ideas?
Revising my answer based on #Stewart mentioning the Formula Calculations they added.
I tried again and it DOES seem to work. So, Jesper, in you case, cell.Calculate() should do it for you. I tried this:
public void Formula_Calc_Test()
{
var datatable = new DataTable("tblData");
datatable.Columns.AddRange(new[] {new DataColumn("Col1", typeof (int)), new DataColumn("Col2", typeof (int))});
for (var i = 0; i < 10; i++)
{
var row = datatable.NewRow();
row[0] = i;
row[1] = i * 10;
datatable.Rows.Add(row);
}
using (var pck = new ExcelPackage())
{
var workbook = pck.Workbook;
var worksheet = workbook.Worksheets.Add("Sheet1");
var cells = worksheet.Cells;
cells.LoadFromDataTable(datatable, true);
var failedRow = 11;
cells["C1"].Formula = "SUM(B2:B" + (failedRow - 1) + ")";
cells["C1"].Calculate();
Console.WriteLine(cells["C1"].Value);
}
}
And did get 360 in the output.
XlsIO is a .NET library that reads and writes Excel 2003/2007/2010/2013/2016 files. Using XlsIO, you can calculated the formula of the cell very easily. The whole suite of controls is available for free (commercial applications also) through the community license program if you qualify. The community license is the full product with no limitations or watermarks.
Step 1: Create a console application
Step 2: Add reference to Syncfusion.XlsIO.Base and Syncfusion.Compression.Base; You can add these reference to your project using NuGet also.
Step 3: Copy & paste the following code snippet.
The following code snippet illustrates how to calculate the formula value in the cell using XlsIO
using (ExcelEngine excelEngine = new ExcelEngine())
{
IApplication application = excelEngine.Excel;
application.DefaultVersion = ExcelVersion.Excel2013;
IWorkbook workbook = application.Workbooks.Create(1);
IWorksheet sheet = workbook.Worksheets[0];
//Setting values to the cells
sheet.Range["A1"].Number = 10;
sheet.Range["B1"].Number = 10;
//Setting formula in the cell
sheet.Range["C1"].Formula = "=SUM(A1,B1)";
// Enabling CalcEngine for computing the formula
sheet.EnableSheetCalculations();
string computedValue = sheet.Range["C1"].CalculatedValue;
workbook.SaveAs("Formula.xlsx");
// Disabling the CalcEngine
sheet.DisableSheetCalculations();
}
For further information about XlsIO, please refer our help documentation
Note: I work for Syncfusion