Creating Excel spreadsheets .XLS and .XLSX in C#



If you want to see how to combine NPOI + Excel Table and Chart,
take a look at the post titled NPOI with Excel Table and dynamic Chart.

Recently I had to implement some code to create an Excel spreadsheet/report using C#.

The task was: given an Excel spreadsheet template - a .XLS file (with formulas, pivot tables, macros, etc) I had to fill some data in one of the sheets of the spreadsheet and send this modified spreadsheet back to the user requesting such an operation (Excel report).

The following attests the need for Excel nowadays:

Excel has long been recognized as the de facto standard when it comes to presenting management reports. The unique combination of great calculation engine, excellent charting facilities, pivot tables and the possibility to perform “what if” analysis, make it the “must have” business intelligence tool.
by John Tunnicliffe

I had a great time while studying the possible ways of doing what the task asks for.

It appears to be a simple task at first but as the time passes by you get to know that this is not the case, well, till the moment this blog post was written at least, I think. :-)

Firstly I tried automating Excel through COM automation, but as the application was meant to be used in a web context it is not recommended to use automation. Why? Because COM automation for Excel is not thread safe, that is, EXCEL.EXE was not constructed to be used by concurrent users accessing the same process, in this case EXCEL.EXE; besides, Microsoft Excel must be installed on the server what is not always possible.

For more details on why you shouldn’t use Excel on the server, read this article on Microsoft Help and Support site: Considerations for server-side Automation of Office. The key part is this:

Microsoft does not currently recommend, and does not support, Automation of Microsoft Office applications from any unattended, non-interactive client application or component (including ASP, ASP.NET, DCOM, and NT Services), because Office may exhibit unstable behavior and/or deadlock when Office is run in this environment.

I’ve just experienced the above. EXCEL.EXE insisted in being alive in task manager even after processing the data and being closed in code. Each call to process a spreadsheet opens an EXCEL.EXE process on the server. With such EXCEL.EXE processes don’t being closed as they should you get lots of those processes on memory which could overload the server.

Do not use COM automation if you are developing server-side code.

After struggling with COM automation I finally got to know ExcelPackage which works with Office Open Document Format (OOXML). It can read an .XLSX (Microsoft Excel 2007) template and create another .XLSX file based on such template, giving you lots of possibilities to work with the template copy.

I’ve gotten really happy because I had found a way of doing what the task was asking for but with a minor detail: ExcelPackage works only with .XLSX file format letting the .XLS (Microsoft Excel 2003) format out of the game. Well it turned out to be a big impediment because the client (software buyer) wouldn’t allow us to install the famous Microsoft Office Compatibility Pack for Word, Excel, and PowerPoint 2007 File Formats on user machines so that users could open and save OOXML file formats even using Microsoft Office 2003 suite.

Discovering ExcelPackage was good but it didn’t do the trick that implies the use of an .XLS template.

I got back Googling again and strived to find an open source library that would allow me to do what I wanted. After some time I finally discovered NPOI. Wow, it could read the .XLS template and generate the end result I wanted. Great. I downloaded NPOI 1.2.1 for .NET 2.0 binaries immediately and started playing with it to see what it could really do.

OK, after this short story, I’ll show you how to use both open source projects (ExcelPackage and NPOI).

I’ve created a new ASP.NET MVC project as can be seen in this picture:

 Excel Writer Solution Explorer

In the Content folder I’ve placed the template spreadsheets.
In the Libs folder I’ve placed the DLLs necessary to use both ExcelPackage and NPOI open source projects.

The controller that interests us is the one called ExcelWriterController:

Excel Writer Controller

The methods that handle the creation of the spreadsheet are: ExcelPackageCreate and NPOICreate.

For each controller action (method) there’s a corresponding view that renders the UI to the user. Those views are the ones shown inside the ExcelWriter folder: ExcelPackage.aspx and NPOI.aspx.

This is the Home Page of the Excel Writer MVC Application - take a look at the tabs (ExcelPackage and NPOI) that lead you to the View pages:

Excel Writer Home Page

Each view has a button which when clicked calls the corresponding action method on the ExcelWriterController.

This is the NPOI view page:

Excel Writer NPOI View Page

I’ll play with a simple spreadsheet I filled with the data I got from Excel’s blog post titled Formula to Access a List of Values Interspersed with Zeros or Blanks.

Let’s see the code that goes into the ExcelPackageCreate method:

/// <summary>
/// Creates a new Excel spreadsheet based on a template using the ExcelPackage library.
/// A new file is created on the server based on a template.
/// </summary>
/// <returns>Excel report</returns>
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ExcelPackageCreate()
{
    try
    {
        FileInfo template = new FileInfo(Server.MapPath(@"\Content\ExcelPackageTemplate.xlsx"));

        FileInfo newFile = new FileInfo(Server.MapPath(@"\Content\ExcelPackageNewFile.xlsx"));

        // Using the template to create the newFile...
        using(ExcelPackage excelPackage = new ExcelPackage(newFile, template))
        {
            // Getting the complete workbook...
            ExcelWorkbook myWorkbook = excelPackage.Workbook;

            // Getting the worksheet by its name...
            ExcelWorksheet myWorksheet = myWorkbook.Worksheets["Sheet1"];

            // Setting the value 77 at row 5 column 1...
            myWorksheet.Cell(5, 1).Value = 77.ToString();

            // Saving the change...
            excelPackage.Save();
        }

        TempData["Message"] = "Excel report created successfully!";

        return RedirectToAction("ExcelPackage");
    }
    catch(Exception ex)
    {
        TempData["Message"] = "Oops! Something went wrong.";

        return RedirectToAction("ExcelPackage");
    }
}

Let’s see the code that goes into the NPOICreate method:

/// <summary>
/// Creates a new Excel spreadsheet based on a template using the NPOI library.
/// The template is changed in memory and a copy of it is sent to
/// the user computer through a file stream.
/// </summary>
/// <returns>Excel report</returns>
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult NPOICreate()
{
    try
    {
        // Opening the Excel template...
        FileStream fs =
            new FileStream(Server.MapPath(@"\Content\NPOITemplate.xls"), FileMode.Open, FileAccess.Read);

        // Getting the complete workbook...
        HSSFWorkbook templateWorkbook = new HSSFWorkbook(fs, true);

        // Getting the worksheet by its name...
        HSSFSheet sheet = templateWorkbook.GetSheet("Sheet1");

        // Getting the row... 0 is the first row.
        HSSFRow dataRow = sheet.GetRow(4);

        // Setting the value 77 at row 5 column 1
        dataRow.GetCell(0).SetCellValue(77);

        // Forcing formula recalculation...
        sheet.ForceFormulaRecalculation = true;

        MemoryStream ms = new MemoryStream();

        // Writing the workbook content to the FileStream...
        templateWorkbook.Write(ms);

        TempData["Message"] = "Excel report created successfully!";

        // Sending the server processed data back to the user computer...
        return File(ms.ToArray(), "application/vnd.ms-excel", "NPOINewFile.xls");
    }
    catch(Exception ex)
    {
        TempData["Message"] = "Oops! Something went wrong.";

        return RedirectToAction("NPOI");
    }
}

One drawback of the ExcelPackage library is that it must create a file on the server. There are some modifications to the library that enables you to create the template copy on memory and send it to the user as the NPOI library does. Take a look at the ExcelPackage’s discussion page at CodePlex and specifically this thread: Why create Excel spreadsheets on the server?

The great thing about NPOI of course is that it enables you to work with the template in code and then send a copy of the spreadsheet directly to the user. The template remains intact and the user receives a modified copy of the template which contains the data processed by the application.

With NPOI when you click on the Create Excel report button you get the download dialog window:

Excel Writer NPOI Download dialog

With ExcelPackage you’d have to get the path of the file created on the server, in this case \Content\ExcelPackageNewFile.xlsx and then send that file to the user. This is an extra step and adds an additional burden to the server. I didn’t implement it and so I let this as an exercise to you.

Well, the spreadsheet included in this simple project has only formulas but you can for sure have an Excel template with lots of formulas, pivot tables, macros, etc. This gives you the power of Excel in code in a clean fashion.

Hope this helps shed some light on this topic!

Note
Using the open source libraries presented in this post you won’t need Microsoft Excel installed on the server.

Updated on 6/2/2010

I’m working on an ASP.NET project that uses .NET Framework 1.1. In such a case I needed to use NPOI 1.2.1 for .NET 1.1.

The code you’ll use with NPOI 1.2.1 for .NET 1.1 is the same presented on this post. Just pay attention to include this using directive inside your C# code:

using NPOI.HSSF.UserModel

Visual Studio 2008 C# ASP.NET MVC Web Application
You can get the Microsoft Visual Studio Project at:

http://leniel.googlepages.com/ExcelWriterMvcProject.zip

To try out the code you can use the free Microsoft Visual Web Developer 2008 Express Edition that you can get at: http://www.microsoft.com/express/vwd/Default.aspx

References
ExcelPackage: Office Open XML Format file creation
http://excelpackage.codeplex.com/

ExcelPackage binaries download
http://excelpackage.codeplex.com/Release/ProjectReleases.aspx

NPOI
http://npoi.codeplex.com/

NPOI 1.2.1 for .NET 1.1 
http://npoi.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=33203

NPOI 1.2.1 for .NET 2.0
http://npoi.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=19351

NPOI samples
http://npoi.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=19351#DownloadId=70100

Interesting discussion on stackoverflow
Create Excel (.XLS and .XLSX) file from C#

8 comments:

Zip said...

Hello, nice post.

I have a question about NPOI and templates.
I have a sheet containing a table with different columns, I set up a chart that takes data from this table to draw itself.

When I add in Excel data in the row just under the table, it extends the table, adds the row to the table and updates the chart.

But, if I add rows using NPOI in C#, rows added under the table won't be automatically included in the table, and my chart is not updated the way I would like it to be.

How can I work around this problem?

Leniel Macaferi said...

Hello Zip,

Have you tried

// Forcing formula recalculation...
sheet.ForceFormulaRecalculation = true;

after inserting rows with NPOI?

If this doesn't work, could you please send me your Excel workbook so that I can analyze this?

One thing worth mentioning is that NPOI 1.2.1 (the current release) doesn't support Excel Chart yet. Take a look over the Unsupported Features list at: http://npoi.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=19351

Although you're working with a table linked to a chart, this may have nothing to do with NPOI not supporting Charts yet because you're not working with the chart directly in the code.

I think NPOI is not recognizing the table inside the sheet. If this holds true, we need to look for a way of making it aware of the table.

I haven't worked with tables when using NPOI. I'd be glad to help you if you could send me your test workbook and the code you're working on. You can get my e-mail on my resume (link to my resume is on the blog's menu above).

Leniel Macaferi

Leniel Macaferi said...

Hey Zip,

I've found a way of doing what you want.

I'll write a new post to show you how to that.

Keep an eye here on the blog! :)

Leniel Macaferi

Leniel Macaferi said...

Zip,

You can go to the post NPOI with Excel Table and dynamic Chart to see the workaround.

Hope this helps you,

Leniel Macaferi

Wilbert Vicencio said...

Interesting post... I have a question about NPOI. Can it do cell formatting like Office Interop? Or can ExcelPackage read an .xls file and output to a formatted .xlsx (like adjust column widths, add borders...)?

Leniel Macaferi said...

Wilbert,

Yes, NPOI can do cell formatting. For this purpose you can take a look at the following projects:

- ColorfulMatrix
- ApplyFontInXls

Both projects are part of NPOI samples package - you can download the samples package at http://npoi.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=19351#DownloadId=70100.

You work with HSSFCellStyle object. Take a look at it.

NPOI.HSSF.UserModel.HSSFCell has the CellStyle property that you can set using an HSSFCellStyle object.

In the same way, NPOI.HSSF.UserModel.HSSFRow has the RowStyle property that you can set using an HSSFCellStyle object.

Other option you have is to work with an Excel template workbook. You format this template workbook the way you want and then fill it with data and save a copy of it to the user for example.

ExcelPackage as described in this post doesn't allow you to work with .XLS files (Office 2003 and previous) format.

Leniel Macaferi

Daniel said...

Hi! Thank you for this great tutorial for NPOI.
I like to ask and hope you can help, there is formula cell which I need to copy to another cell. If the source cell formula is =SUM(B11:J11), the next formula for the next row should =SUM(B12:J12)

Leniel Macaferi said...

Hi Daniel,

Thank you for stopping by and for asking!

What you wanna do is a simple task with NPOI.

Let's say you have a template as I describe in the post. In cell K11 you have the formula =SUM(B11:J11).

If your cell values are:

B11=1
C11=2
D11=3
E11=4
F11=5
G11=6
H11=7
I11=8
J11=9

K11=45

In an Excel template define/set your formula inside cell K11 and copy/paste it to K12, K13, K14... and so on. To copy/paste, expand your formula clicking in the minuscule handle in the lower-right corner of cell K11. This handle gives you a way to expand the formula to other rows. Doing this, cell K12=SUM(B12:J12), K13=SUM(B13:J13), K14=SUM(B14:J14), etc.

In your code, do the following:

//Add values into rows 11, 12, 13, 14...

// Force formula recalculation...
sheet.ForceFormulaRecalculation = true;

When you open your workbook created with NPOI, you'll see that your formulas were calculated and that now you have the correct values in column K, that is, you formulas will be applied througut your worksheet.

You can also set the formula in the code doing the following:

// Creating rows 11..14
// 0 is the first row...
for(int i = 10; i < 14; i++)
{
HSSFRow row = sheet.CreateRow(i);

// Setting the value 1 at row 11 column B
row.CreateCell(1).SetCellValue(1);
// Setting the value 2 at row 11 column C
row.CreateCell(2).SetCellValue(2);
// Setting the value 3 at row 11 column D
row.CreateCell(3).SetCellValue(3);
// Setting the value 4 at row 11 column E
row.CreateCell(4).SetCellValue(4);
// Setting the value 5 at row 11 column F
row.CreateCell(5).SetCellValue(5);
// Setting the value 6 at row 11 column G
row.CreateCell(6).SetCellValue(6);
// Setting the value 7 at row 11 column H
row.CreateCell(7).SetCellValue(7);
// Setting the value 8 at row 11 column I
row.CreateCell(8).SetCellValue(8);
// Setting the value 9 at row 11 column J
row.CreateCell(9).SetCellValue(9);

row.CreateCell(10).SetCellFormula(string.Format("SUM(B{0}:J{1})", row.RowNum+1, row.RowNum+1));
}

// Forcing formula recalculation...
sheet.ForceFormulaRecalculation = true;

Hope this helps shed some light into this question.

Leniel Macaferi

Post a Comment