how to create and download excel document using asp.net - c#

How to create and download excel document using asp.net ?
The purpose is to use xml, linq or whatever to send an excel document to a customer via a browser.
Edit : Use case
The customer load a gridview ( made with ajax framework ) in a browser, the gridview is directly linked to an sql database.
I put a button 'export to excel' to let customer save this gridview data on his computer ansd i would like to launch a clean download of an excel.
The solutions proposed here are not clean, like send an html document and change the header to excel document etc, i'm searching a simple solution on codeplex right now, i will let you know.

Starter kit
First i have downloaded the Open XML Format SDK 2.0.
It comes with 3 useful tools in :
C:\Program Files\Open XML Format SDK\V2.0\tools
DocumentReflector.exe wich auto
generate the c# to build a
spreadsheet from the code.
OpenXmlClassesExplorer.exe display
Ecma specification and the class
documentation (using an MSDN style
format).
OpenXmlDiff.exe graphically compare
two Open XML files and search for
errors.
I suggest anyone who begin to rename .xlsx to .zip, so you can see the XML files who drive our spreadsheet ( for the example our sheets are in "xl\worksheets" ).
The code
Disclaimer : I have stolen all the code from an MSDN technical article ;D
The following code use an *.xlsx template i made manually to be able to modify it.
Namespaces references
using System.IO;
using System.Xml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml;
// Database object
DataClassesDataContext db = new DataClassesDataContext();
// Make a copy of the template file.
File.Copy(#"C:\inetpub\wwwroot\project.Web\Clients\Handlers\oxml-tpl\livreurs.xlsx", #"C:\inetpub\wwwroot\project.Web\Clients\Handlers\oxml-tpl\generated.xlsx", true);
// Open the copied template workbook.
using (SpreadsheetDocument myWorkbook = SpreadsheetDocument.Open(#"C:\inetpub\wwwroot\project.Web\Clients\Handlers\oxml-tpl\generated.xlsx", true))
{
// Access the main Workbook part, which contains all references.
WorkbookPart workbookPart = myWorkbook.WorkbookPart;
// Get the first worksheet.
WorksheetPart worksheetPart = workbookPart.WorksheetParts.ElementAt(2);
// The SheetData object will contain all the data.
SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();
// Begining Row pointer
int index = 2;
// Database results
var query = from t in db.Clients select t;
// For each item in the database, add a Row to SheetData.
foreach (var item in query)
{
// Cell related variable
string Nom = item.Nom;
// New Row
Row row = new Row();
row.RowIndex = (UInt32)index;
// New Cell
Cell cell = new Cell();
cell.DataType = CellValues.InlineString;
// Column A1, 2, 3 ... and so on
cell.CellReference = "A"+index;
// Create Text object
Text t = new Text();
t.Text = Nom;
// Append Text to InlineString object
InlineString inlineString = new InlineString();
inlineString.AppendChild(t);
// Append InlineString to Cell
cell.AppendChild(inlineString);
// Append Cell to Row
row.AppendChild(cell);
// Append Row to SheetData
sheetData.AppendChild(row);
// increase row pointer
index++;
}
// save
worksheetPart.Worksheet.Save();
}
I havent finished yet, my second job is to auto download the spreadsheet after modification.
Finally, i redirect the user to my generated spredsheet (from my aspx)
context.Response.Redirect("Oxml-tpl/generated.xlsx");

just set Response.ContentType = "application/vnd.ms-excel" and your page will rendered as an excel sheet on the clients browser
Sample code here

There are quite a few ways of handling this, depending on how extensive the Excel functionality is. Binoj's answer works if the Excel is just a spreadsheet and has no direct Excel functionality built in. The client can add functionality, concats, etc. These are "dumb" excel docs until the client does soemthing.
To create a more full featured Excel doc, you havve two basic choices that I can think of offhand.
Use either the office components (re: bad) to create an excel document, or a third party component, like SoftArtisan's ExcelWriter. Great component, but there is a cost.
Use a control on the page that allows export to Excel. Most vendors of ASSP.NET controls have this functionality on their grids.
Option #1 allows you pretty much all functionality of Excel. Option #2 is a bit more limited, at least in the controls I have tried.

Good article on how top export to excel from Erika Ehrli
http://blogs.msdn.com/erikaehrli/archive/2009/01/30/how-to-export-data-to-excel-from-an-asp-net-application-avoid-the-file-format-differ-prompt.aspx

Related

EPPlus InsertRow corrupts excel file

I am currently comparing different files with each other and put the output in an Excel Workbook with multiple worksheets (one per file comparison) using EPPlus 4.5.3.3.
The worksheets are simple ranges - no tables.
Sometimes it is required to move the content of one Excel Sheet to a certain row in another Excel Sheet.
For this I am using the InsertRow function of EPPlus. The function itself does the trick and inserts the content at the desired row.
However, when opening the Workbook I receive these errors:
The content of that XML file is the following:
<?xml version="1.0" encoding="UTF-8" standalone="true"?>
-<recoveryLog xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<logFileName>error044200_02.xml</logFileName>
<summary>Errors were detected in file 'C:\Users\...\test.xlsx'</summary>
-<repairedParts>
<repairedPart>Repaired Part: /xl/worksheets/sheet5.xml part with XML error. Load error. Line 1, column 0.</repairedPart>
</repairedParts>
When I am adding the content at the last row without using InsertRow everything is working fine.
Also, if I save the ExcelPackage immediately after using InsertRow the error is also present, so it definitely has something to do with this function (also if I am only using one Worksheet). After using InsertRow it seems like the whole formatting of the respective Excel Sheet is screwed up. The inserted content appears to be right though.
The excel is initially created from an empty template excel that contains nothing but the header names (also had the same issue with a template without anything inside):
The following code can be used to reproduce (this is just an example - in my case every sheet has more than 50 rows):
FileInfo filePath= new FileInfo(fileName);
excel = new ExcelPackage(filePath);
ExcelWorkbook wb = excel.Workbook;
foreach (ExcelWorksheet ws in wb.Worksheets)
{
int i = 5;
ws.InsertRow(i, 1);
//same effect with ws.InsertRows(i, 1, i-1)
ws.Cells[i, 1].Value = "test";
ws.Cells[i, 2].Value = "test";
//excel.Save();
}
excel.Save();
Although my answer is not based on EPPlus, have you looked at this post ?
Inserting new rows and moving exsisting ones with OpenXML SDK 2.0
I am guessing that you are getting errors because the newly added rows from the ws.InsertRow function are not updating the required row indexes. This is mentioned in the post.
Also there is a recommendation in the comments "To anyone who is getting an error when opening the file try this line before you save your document'... xlsFile.WorkbookPart.DeletePart(xlsFile.WorkbookPart.CalculationChainPart);

How to access data in sheet in my Visual Studio Office Excel workbook?

I need to create a "Document level customization" with C# code (not and Excel Add-in!)
I created a Visual Studio Office Excel 2010 Workbook project type. This creates a workbook with 3 sheets in my project. I added some "configuration" information to one of those sheets.
I need to access this configuration information programmatically (Sheet1 contains a button - pressing on that button should
load configuration data
open a WinForm
present configuration data on that form,
but somehow I can't find how to do that...
If I try to initialize Sheet1 class, compiler expects two parameters - Microsoft.Office.Tools.Excel.Factory and IServiceProvider, but I am calling this from a button that is placed on Sheet2 - so it's after Excel Workbook is already opened... shouldn't Sheet1 be initialized automatically?
So, how can I access Sheet1 from my VSTO project's c# code?
EDIT
Please see project sample screencast here
I have a button on Sheet2, that should
load some data from Sheet1
initialize WinForm
add it as a DataSource for a ComboBox on that WinForm
I can not find a way how to read data from that Sheet1...
It seems, that there are not a lot developers (at least at stackoverflow) that work with Excel workbooks in Visual Studio / VSTO), but still this is is how I got this basic stuff working - in case if this is helpful to someone else
Since my code was in the Worksheet's *.cs file it turned out I can access project's xlsx file this way:
var excel = (Excel.Application)this.Application;
var xlbook = (Excel.Workbook)excel.ActiveWorkbook;
var worksheets = xlbook.Worksheets;
var sheet = (Excel.Worksheet)worksheets["Sheet3"];
int row = 2;//1st row for column titles
while (!string.IsNullOrEmpty(((Excel.Range)sheet.Cells[row, 2]).Value))
{
var weight = ((Excel.Range)sheet.Cells[row, 3]).Value;
row++;
}
Some additional things about processing data from Excel sheet in c# code, I found out (maybe that's helpful for someone):
the .NET type for Excel Cell is Excel.Range (at least I didn't
find any other option)
when reading cell that is empty in Excel file, it's value on .NET side is null, not ""
values that seem to be strings on Excel side - can turn to be different types when loaded on c# side. I don't know if it's the best way, but I solved it like this:
var weight = (((Excel.Range)sheet.Cells[row, 3]).Value);
if (weight is double)
{
product.Weight = ((double)((Excel.Range)sheet.Cells[row, 3]).Value).ToString();
}
else if (weight is string)
{
product.Weight = ((Excel.Range)sheet.Cells[row, 3]).Value;
}

Delete rows from Excel

Following are the approaches I tried:
A) I tried to delete rows from an excel sheet using Microsoft.Office.Interop.Excel.
I'm doing this in a script task within a SSIS package.
I added the library to the GAC, since it was raising an error : Could not load Library.
Now it's raises this error saying : Retrieving the COM class factory for component with CLSID {00024500-0000-0000-C000-000000000046} failed due to the following error: 80040154.
Googling this tells me I need MS Office installed for it to work, which I don't want coz the server I deploy this solution on is definitely not going to have MS Office installed on it. I'm no expert, but I would like to know why such operations are not possible, by simply adding reference to a dll? Why is it mandatory to install MS Office.
B) I also tried Oledb jet provider, but this one doesn't allow deleting of rows.
The only operations it supports is Insert, Update and Select.
Things I have come across on the web:
A) A SO Questions' answer suggests to use Npoi, but I can't totally rely on that, because what's free library today can become paid in future.
B) Also I have come across EPP Plus library. I have used it and understand that it's based on a GNU public license, but I'm apprehensive on using it because it may become a paid tool in future.
C) I have also come across people using Open XML SDK by Microsoft. Before I get my hands dirty in this, I would love if someone up front tells me whether I should be using this. Not that I'm lazy to try it out myself but what what would be helpful to me before I start is, does this SDK need any external programs installed on the machine. Coz it requires me to install an msi to be able to us it.
Is there a work around to do this using Microsoft COM components? I'm not asking a subjective question here. I want to know technical obstacles, if any when I use the above three researched tools.
Thanks in advance
The point is with Interop that you indeed must have office installed. So bluntly said, you cannot use Interop. If you only need to support xlsx files, you can do it in xml.
See this and this link for more details about unpacking xlsx files, editing and repacking. The only thing you need than is something to unzip it and your own xml handling code.
If the requirement is to also support xls files you have a bit of a problem. I tried this in the past without any additional installations but did not succeed, so I decided to only support xlsx. I either needed some .msi files or office installed on the server.
You're saying that you are using a script task in SSIS; then why not import the excel file you want to delete the values from it (preferably into a database or keep it cached into a datatable) and then generate a new xls file with just the data you want to keep.
OR don't use the script task at all and use, inside a data flow, a configured excel source combined with a script component (which is basically the same thing as a script task just that you can use this one only in a data flow) and do all your work there. If you have a dynamic connection to the excel file, you can always use variables (parameters if you're on DataTools) to configure such a connection.
Good luck!
If you want to use Microsoft.Office.Interop.Excel then, yes, you do need Excel on the server. Therefore, so long as you only want to deal with xlsx based workbooks / 2007+ then I would suggest that OpenXML is the way to go. It's a bit of a learning curve and you get to realise how much work Excel does for you in the background but is not too bad once you get used to it.
A very quick sample knocked up in LINQPad:
void Main()
{
string fileName = #"c:\temp\delete-row-openxml.xlsx";
using (SpreadsheetDocument doc = SpreadsheetDocument.Open(fileName, true))
{
// Get the necessary bits of the doc
WorkbookPart workbookPart = doc.WorkbookPart;
SharedStringTablePart sstpart = workbookPart.GetPartsOfType<SharedStringTablePart>().First();
SharedStringTable sst = sstpart.SharedStringTable;
// Get the first worksheet
WorksheetPart worksheetPart = workbookPart.WorksheetParts.First();
Worksheet sheet = worksheetPart.Worksheet;
var rows = sheet.Descendants<Row>();
foreach (Row row in rows.Where(r => ShouldDeleteRow(r, sst)))
{
row.Remove();
}
}
}
private bool ShouldDeleteRow(Row row, SharedStringTable sst)
{
// Whatever logic to apply to decide whether to remove a row or not
string txt = GetCellText(row.Elements<Cell>().FirstOrDefault(), sst);
return (txt == "Row 3");
}
// Basic way to get the text of a cell - need to use the SharedStringTable
private string GetCellText(Cell cell, SharedStringTable sst)
{
if (cell == null)
return "";
if ((cell.DataType != null) && (cell.DataType == CellValues.SharedString))
{
int ssid = int.Parse(cell.CellValue.Text);
string str = sst.ChildElements[ssid].InnerText;
return str;
}
else if (cell.CellValue != null)
{
return cell.CellValue.Text;
}
return "";
}
Note that this will clear the row not shuffle up all the other rows. To do that you'd need to provide some logic to adjust row indexes of the remaining rows.
To answer a little more of the OP question - the OpenXML msi is all that is needed apart from the standard .Net framework. The sample needs a reference to WindowsBase.dll for the packaging API and using statements for DocumentFormat.OpenXml.Packaging and DocumentFormat.OpenXml.Spreadsheet. The OpenXML API package can be referenced in VS via Nuget too so you don't even need to install the msi if you don't want. But it makes sense to do so IMHO.
One other item that you will find VERY useful is the OpenXML tools msi. This lets you open a Word or Excel doc and see the XML layout inside - most helpful.
This is how I managed to remove rows in excel and move up the data
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using (SpreadsheetDocument document = SpreadsheetDocument.Open(pathToFile, true))
{
WorkbookPart wbPart = document.WorkbookPart;
var worksheet = wbPart.WorksheetParts.First().Worksheet;
var rows = worksheet.GetFirstChild<SheetData>().Elements<Row>();
// Skip headers
foreach (var row in rows.Skip(1))
{
if (/* some condition on which rows to delete*/)
{
row.Remove();
}
}
// Fix all row indexes
string cr;
for (int i = 2; i < rows.Count(); i++)
{
var newCurrentRowIndex = rows.ElementAt(i - 1).RowIndex.Value + 1;
var currentRow = rows.ElementAt(i);
currentRow.RowIndex.Value = updatedRowIndex;
IEnumerable<Cell> cells = currentRow.Elements<Cell>().ToList();
if (cells != null)
{
foreach (Cell cell in cells)
{
cr = cell.CellReference.Value;
cr = Regex.Replace(cell.CellReference.Value, #"[\d-]", "");
cell.CellReference.Value = $"{cr}{updatedRowIndex}";
}
}
}
worksheet.Save();
}

Microsoft Open SDK 2.0 to generate excel file using c#

Please refer link for the code i am using to generate excel file from data table. I am able to generate excel file successfully.
But the problem/Challenge/Question is as follows.
I Want to generate column as per the datatype so if Column value contains date the it cell format should be date(dd/mm/yyy) if number then numeric. ans so on...
I have tried to generate excel file as per data format you can see specific methods to generate cell value. But the problem is when user download the file it will gives the warning message that "Excel found unreadable content 'filename'. Do you want to recover the content of this workbook?". I don,t want that warning message should come.
If I am writing everything as text without format then file will open without any warning message and after downloading file, if user tries to format respective column in date or number format then also it will not allow user to format/slice & dice data in excel file.
Reference :- http://www.codeproject.com/Articles/263106/Export-Tabular-Data-in-CSV-and-Excel-Formats-Throu
Please let me know the solution if anybody has.
I am using DocumentFormat.OpenXml.dll
private Cell CreateTextCell(string header, UInt32 index, string text)
{
var cell = new Cell {DataType = CellValues.InlineString, CellReference = header + index};
var istring = new InlineString();
var t = new Text {Text = text};
istring.Append(t);
cell.Append(istring);
return cell;
}
private Cell CreateDateCell(string header, UInt32 index, DateTime sDate)
{
Cell cell = new Cell();
cell.DataType = CellValues.Date;
cell.CellReference = header + index;
cell.StyleIndex = UInt32Value.FromUInt32(14);
cell.CellValue = new CellValue { Text = sDate.ToOADate().ToString() };
return cell;
}
private Cell CreateNumberCell(string header, UInt32 index, string text)
{
Cell cell = new Cell();
cell.DataType = CellValues.Number;
cell.CellReference = header + index;
cell.CellValue = new CellValue(text);
return cell;
}
I have moved to EPPlus .net library to generate excel file and it is very easy to use.
Thanks.
I may not have the complete solution but you can try the below steps to find the root cause:
One of the reasons for unreadable content error (There are many reasons for this but considering that you are just writing from the datatable only).is if there is a mismatch between the StyleIndex/Datatype/Cellvalue format
To identify the root cause you can try:
Create a sample excel and directly write a cell (only a cell) with number, it that goes on fine, try the next datatype one by one.
Next try to write them one after the other.
Do this and sort down the types which are causing problems.
Next try to vary the StyleIndex/cellFormat with that type (you can check for examples online) till the format is fine without error. Once you are good with all types, you can try writing to the entire excel.
You can also use this method if you want to try something new using OpenXML (since you do not have well documented examples for everything Online)
Another way to identify issues is to use Openxml productivity tool and do a validation of the file.

How can I read an Excel 2010 file in my C# code using a DLL?

UPDATE1:
I am using Excel 2010 and I've searched the web and found thousands upon thousands of ways to do this via win form, console, etc. But I can't find a way to do this via DLL. and none of the sample on-line is complete all in bit and pieces.
UPDATE END
I have looked and goggled but did not get the specific what i am looking for, as show below the excel sample sheet.
i'm looking a way to read and store the each cell data in a variable
i have started something like this:
Workbook workbook = open(#"C:\tmp\MyWorkbook.xls");
IWorksheet worksheet = workbook.Worksheets[0];
IRange a1 = worksheet.Cells["A1"];
object rawValue = a1.Value;
string formattedText = a1.Text;
Console.WriteLine("rawValue={0} formattedText={1}", rawValue, formattedText);
Your code can work with a couple changes.
One thing to remember is that Excel worksheets are 1-based, not 0-based (and use Worksheet instead of IWorksheet):
Worksheet worksheet = workbook.Worksheets[1];
And to get a range, it is easiest to call get_Range() on the worksheet object (and use Range instead of IRange):
Range a1 = worksheet.get_Range("A1");
With those two lines of code changed, your example will work fine.
UPDATE
Here is a "complete" example:
Right-click your project in the solution explorer and click "Add
Reference".
Click on the COM tab and sort the list by Component Name. Find "Microsoft Excel 14.0 Object Library" in the list and select it. Click OK.
In the code file where you want this to run, add a using Microsoft.Office.Interop.Excel;
Use this code, which I've modified as little as possible from your example:
var excel = new Microsoft.Office.Interop.Excel.Application();
Workbook workbook = excel.Workbooks.Open(#"C:\tmp\MyWorkbook.xls");
Worksheet worksheet = workbook.Worksheets[1];
Range a1 = worksheet.get_Range("A1");
object rawValue = a1.Value;
string formattedText = a1.Text;
Console.WriteLine("rawValue={0} formattedText={1}", rawValue, formattedText);
Excel.Sheets sheets = workbook.Worksheets;
Excel.Worksheet worksheet = (Excel.Worksheet)sheets.get_Item(1);
System.Array myvalues;
Excel.Range range = worksheet.get_Range("A1", "E1".ToString());
myvalues = (System.Array)range.Cells.Value;
If you don't want to be in a war with com components and registering dlls,
the best way to read excel is Excel Reader for .NET
I have been using it for so long time , and I can say it just works.
and excelReader.IsFirstRowAsColumnNames property makes everything easy.
You can play your data within a dataset.

Categories