Excel Instance wont close after Interop Operations - c#

I'm building a new Excel workbook in c# by combining the first sheet of a series of different Excel workbooks; subsequently I export the new Workbook to PDF. I made this work, but there is always one Excel instance running by the end of the method.I had the same issue discussed here with a simpler setup and less Excel objects that I could solve with the GC.Collect command. Now, none of this is working.
public void CombineWorkBooks()
{
Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
xlApp.DisplayAlerts = false;
xlApp.Visible = false;
Workbooks newBooks = null;
Workbook newBook = null;
Sheets newBookWorksheets = null;
Worksheet defaultWorksheet = null;
// Create a new workbook, comes with an empty default worksheet");
newBooks = xlApp.Workbooks;
newBook = newBooks.Add(XlWBATemplate.xlWBATWorksheet);
newBookWorksheets = newBook.Worksheets;
// get the reference for the empty default worksheet
if (newBookWorksheets.Count > 0)
{
defaultWorksheet = newBookWorksheets[1] as Worksheet;
}
// loop through every line in Gridview and get the path' to each Workbook
foreach (GridViewRow row in CertificadosPresion.Rows)
{
string path = row.Cells[0].Text;
string CertName = CertificadosPresion.DataKeys[row.RowIndex].Value.ToString();
Workbook childBook = null;
Sheets childSheets = null;
// Excel of each line in Gridview
childBook = newBooks.Open(path,Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
childSheets = childBook.Worksheets;
if (childSheets != null)
{
// Build a new Worksheet
Worksheet sheetToCopy = null;
// Only first Worksheet of the Workbook belonging to that line
sheetToCopy = childSheets[1] as Worksheet;
if (sheetToCopy != null)
{
// Assign the Certificate Name to the new Worksheet
sheetToCopy.Name = CertName;
// set PageSetup for the new Worksheet to be copied
sheetToCopy.PageSetup.Zoom = false;
sheetToCopy.PageSetup.FitToPagesWide = 1;
sheetToCopy.PageSetup.FitToPagesTall = 1;
sheetToCopy.PageSetup.PaperSize = Microsoft.Office.Interop.Excel.XlPaperSize.xlPaperA4;
// Copy that new Worksheet to the defaultWorksheet
sheetToCopy.Copy(defaultWorksheet, Type.Missing);
}
System.Runtime.InteropServices.Marshal.ReleaseComObject(sheetToCopy);
childBook.Close(false, Type.Missing, Type.Missing);
}
System.Runtime.InteropServices.Marshal.ReleaseComObject(childSheets);
System.Runtime.InteropServices.Marshal.ReleaseComObject(childBook);
}
//Delete the empty default worksheet
if (defaultWorksheet != null) defaultWorksheet.Delete();
//Export to PDF
newBook.ExportAsFixedFormat(Microsoft.Office.Interop.Excel.XlFixedFormatType.xlTypePDF, #"C:\pdf\" + SALESID.Text + "_CertPres.pdf", 0, false, true);
newBook.Close();
newBooks.Close();
xlApp.DisplayAlerts = true;
DownloadFile(SALESID.Text);
System.Runtime.InteropServices.Marshal.ReleaseComObject(defaultWorksheet);
System.Runtime.InteropServices.Marshal.ReleaseComObject(newBookWorksheets);
System.Runtime.InteropServices.Marshal.ReleaseComObject(newBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(newBooks);
xlApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);
GC.Collect();
GC.WaitForPendingFinalizers();
}
protected void DownloadFile(string Salesid)
{
string path = #"c:\\pdf\" + Salesid + "_CertPres.pdf";
byte[] bts = System.IO.File.ReadAllBytes(path);
Response.Clear();
Response.ClearHeaders();
Response.AddHeader("Content-Type", "Application/octet-stream");
Response.AddHeader("Content-Length", bts.Length.ToString());
Response.AddHeader("Content-Disposition", "attachment; filename=" + Salesid + "_CertPres.pdf");
Response.BinaryWrite(bts);
Response.Flush();
Response.End();
}
The problem must have been related to the call of the DownloadFile Method. I eliminated that call, and the Excel process was properly closed. Some of these operations must have kept a reference to one of the COM objects open, so that they could not be closed. By calling "DownloadFile" at the very end after the GarbageCollect the problem is solved. (I'm not quite sure why)

In your method DownloadFile, you call
Response.End()
HttpResponse.End throws an exception (emphasis mine):
To mimic the behavior of the End method in ASP, this method tries to raise a ThreadAbortException exception. If this attempt is successful, the calling thread will be aborted, [...]
This exception aborts your thread. Thus, all your ReleaseComObject, Excel.Quit, GC.Collect stuff is never executed.
The solution: Don't call Response.End. You probably don't need it. If you need it, you might want to consider the alternative mentioned in the documentation instead:
This method is provided only for compatibility with ASP—that is, for compatibility with COM-based Web-programming technology that preceded ASP.NET. If you want to jump ahead to the EndRequest event and send a response to the client, it is usually preferable to call CompleteRequest instead.
[...]
The CompleteRequest method does not raise an exception, and code after the call to the CompleteRequest method might be executed
PS: Using Excel automation from a web application is not officially supported by Microsoft. For future development, you might want to consider using a third-party Excel library instead.

I found that sometimes the only thing that helps is the "sledgehammer method". Killing all running excel instances:
foreach (Process p in Process.GetProcessesByName("EXCEL"))
{
try
{
p.Kill();
p.WaitForExit();
}
catch
{
//Handle exception here
}
}

Looks to me that you have a reference not cleaned up. Probably something like the 'two dot rule' problem - which in my opinion is a silly rule because you can't code anything decent because it's to difficult to keep track of.
You could try Marshal.ReleaseComObject of your COM references but still asking for trouble...
My suggestion would be to try using VSTO to automate Excel. This will clear your references correctly on your behalf.
https://social.msdn.microsoft.com/Forums/vstudio/en-US/a12add6b-99ea-4677-8245-cd667101683e/vsto-and-office-objects-disposing

Related

Converting XLSX file using to a CSV file

I need to convert an XLSX file to another CSV file.
I've done a lot of research on how to do this process, but I did not find anything that suited me.
I found this Github Gist only Convert an Epplus ExcelPackage to a CSV file
That returns an Array of binary. But apparently it does not work any more.
I'm trying to load Array using LoadFromCollection
FileInfo novoArquivoCSV = new FileInfo(fbd.SelectedPath);
var fileInfoCSV = new FileInfo(novoArquivo + "\\" + nameFile.ToString() + ".csv");
using (var csv = new ExcelPackage(fileInfoCSV))
{
csv.Workbook.Worksheets.Add(nameFile.ToString());
var worksheetCSV = csv.Workbook.Worksheets[1];
worksheetCSV.Cells.LoadFromCollection(xlsx.ConvertToCsv());
}
The code you linked to reads an XLSX sheet and returns the CSV data as a byte buffer through a memory stream.
You can write directly to a file instead, if you remove the memory stream and pass the path to the target file in ConvertToCsv :
public static void ConvertToCsv(this ExcelPackage package, string targetFile)
{
var worksheet = package.Workbook.Worksheets[1];
var maxColumnNumber = worksheet.Dimension.End.Column;
var currentRow = new List<string>(maxColumnNumber);
var totalRowCount = worksheet.Dimension.End.Row;
var currentRowNum = 1;
//No need for a memory buffer, writing directly to a file
//var memory = new MemoryStream();
using (var writer = new StreamWriter(targetFile,false, Encoding.UTF8))
{
//the rest of the code remains the same
}
// No buffer returned
//return memory.ToArray();
}
Encoding.UTF8 ensures the file will be written as UTF8 with a Byte Order Mark that allows all programs to understand this is a UTF8 file instead of ASCII. Otherwise, a program could read the file as ASCII and choke on the first non-ASCII character encountered.
Checkout the .SaveAs() method in Excel object.
wbWorkbook.SaveAs("c:\yourdesiredFilename.csv", Microsoft.Office.Interop.Excel.XlFileFormat.xlCSV)
Or following:
public static void SaveAs()
{
Microsoft.Office.Interop.Excel.Application app = new Microsoft.Office.Interop.Excel.ApplicationClass();
Microsoft.Office.Interop.Excel.Workbook wbWorkbook = app.Workbooks.Add(Type.Missing);
Microsoft.Office.Interop.Excel.Sheets wsSheet = wbWorkbook.Worksheets;
Microsoft.Office.Interop.Excel.Worksheet CurSheet = (Microsoft.Office.Interop.Excel.Worksheet)wsSheet[1];
Microsoft.Office.Interop.Excel.Range thisCell = (Microsoft.Office.Interop.Excel.Range)CurSheet.Cells[1, 1];
thisCell.Value2 = "This is a test.";
wbWorkbook.SaveAs(#"c:\one.xls", Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookNormal, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlShared, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
wbWorkbook.SaveAs(#"c:\two.csv", Microsoft.Office.Interop.Excel.XlFileFormat.xlCSVWindows, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlShared, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
wbWorkbook.Close(false, "", true);
}
Is there any simple way to convert .xls file to .csv file? (Excel)
There are several other resources online that can help with this ind of thing. Actually, for something generic like this, you should always Google for a solution, and try to figure it out yourself. That's the best way to learn how to do technical things. If you get stuck, or if you have a very specific question, this site is a great place to post your question(s). It seems to me, you probably started here, and you didn't do any preliminary work yourself.

Excel Interop File Accessed As Read Only Despite Specifically Being Directed Not To

I am having an issue while trying to use Excel Interop with C#.NET.
Im trying to iterate through a large number of workbooks/worksheets and deposit an array of data in a range on each worksheet, as I iterate through the sheets I open, grab my range, set range number formatting, and drop my values into the range, then save and close out, and clean up all my objects.
This all works fine for a while. However, seemingly at random during the process the file will open in read only mode, which causes the SaveAs to halt and bring up a dialog (when this should be running in background for the entire time).
Some factoids:
In my Workbooks.Open() statement I am setting ReadOnly to false, IgnoreRecommendedReadOnly to true, and Notify to false, this should not only prevent the sheet from opening in read only, it should also throw an error if the file isnt able to open in read/write. This does not seem to work out for some reason, as the random read only access still happens.
I am using a sleep command in HoldWhileFileIsOpen(string) to halt the program and wait for the excel file to be completely available both before and after the excel file is used (it halts until the file is not open using FileStream theFile = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None); as a test, this should prevent my program from opening this file until it is 100% unused/available due to the FileShare.None, but I am still getting these read only errors when saving the excel file).
I am using SaveAs to overwrite the original file with the changed version.
I am closing all COM objects and releasing them, the excel application fully exits each iteration and the task manager no longer shows excel as running each time.
I am not opening these spreadsheets or Excel itself anywhere, nor am I touching these files from anywhere besides the code snippet included below.
I want to stress that these are seemingly random, I can iterate through and save the same spreadsheet 50+ times, move on to the next spreadsheet, iterate 30 times, then hit this error. Then the next run I will only be able to iterate through the first spreadsheet 10 times before it happens again. It is not associated with a specific workbook or worksheet.
My code:
if (newRows.Count > 0)
{
//Increment through our array list and build a 2D string array from that list (.Range.Value doesnt like ArrayList objects, so we must use a raw 2D array)...
string[] rowSample = (string[])newRows[0];
string[,] newRowsArray = new string[newRows.Count, rowSample.Length];
int currentRowIndex = 0;
foreach (string[] row in newRows)
{
int columnIndex = 0;
foreach (string columnText in row)
{
newRowsArray[currentRowIndex, columnIndex] = columnText;
columnIndex++;
}
currentRowIndex++;
}
//Hold processing while the current excel file is open, once it closes we can continue...
HoldWhileFileIsOpen(currentExcelFile);
try
{
//Open excel and open current workbook...
Microsoft.Office.Interop.Excel.Application excelApplication = new Microsoft.Office.Interop.Excel.Application();
excelApplication.DisplayAlerts = false;
excelApplication.Visible = false;
Microsoft.Office.Interop.Excel.Workbooks excelWorkbooks = excelApplication.Workbooks;
Microsoft.Office.Interop.Excel.Workbook currentWorkbook = excelWorkbooks.Open(currentExcelFile, Type.Missing, false, Type.Missing, Type.Missing, Type.Missing, true, Type.Missing, Type.Missing, Type.Missing, false, Type.Missing, false, Type.Missing, Type.Missing);
//Open up/Select our current worksheet
Microsoft.Office.Interop.Excel.Sheets workSheets = currentWorkbook.Sheets;
Microsoft.Office.Interop.Excel.Worksheet currentWorkSheet = workSheets[currentWorkSheetName];
currentWorkSheet.Select();
//Create new range, set range number formatting = "#" (text) to retain leading zeroes and other tricky bits in our output, then drop values into range...
Microsoft.Office.Interop.Excel.Range newExcelRows = (Microsoft.Office.Interop.Excel.Range)currentWorkSheet.get_Range(GetColumnAddress(startingColInt) + startingRow, GetColumnAddress(startingColInt + rowSample.Length - 1) + (startingRowIncrementing - 1));
newExcelRows.NumberFormat = "#";
//newExcelRow.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Red);
newExcelRows.Value = newRowsArray;
//Save workbook changes...
currentWorkbook.SaveAs(currentExcelFile, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlNoChange, Microsoft.Office.Interop.Excel.XlSaveConflictResolution.xlLocalSessionChanges, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
//Close things up, all COM objects references above must be released and scrubbed thouroughly...
System.Runtime.InteropServices.Marshal.ReleaseComObject(newExcelRows);
newExcelRows = null;
excelApplication.ActiveWorkbook.Close(Type.Missing, Type.Missing, Type.Missing);
System.Runtime.InteropServices.Marshal.ReleaseComObject(currentWorkSheet);
currentWorkSheet = null;
System.Runtime.InteropServices.Marshal.ReleaseComObject(workSheets);
workSheets = null;
System.Runtime.InteropServices.Marshal.ReleaseComObject(currentWorkbook);
currentWorkbook = null;
//Quit excel...
excelWorkbooks.Close();
System.Runtime.InteropServices.Marshal.ReleaseComObject(excelWorkbooks);
excelWorkbooks = null;
excelApplication.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApplication);
excelApplication = null;
}
catch (Exception ex)
{
if (ex.Message == "Cannot save as that name. Document was opened as read-only.")
{
}
}
//Hold processing while the current excel file is open, once it closes we can continue...
HoldWhileFileIsOpen(currentExcelFile);
LblStatus.AppendText(" - Done modifying and saving worksheet " + currentWorkSheetName + " (" + currentCountry + ")." + Environment.NewLine);
}
else
{
LblStatus.AppendText(" - No data found for current worksheet " + currentWorkSheetName + " (" + currentCountry + ")." + Environment.NewLine);
}
Any assistance would be a great relief, I am not sure what is going on here or why Interop is randomly choosing to screw me during processing of these files.

COMException on closing Excel workbook

I'm using Excel = Microsoft.Office.Interop.Excel to write various data to Excel sheets.
Excel.Workbook wb = null;
Excel.Worksheet ws = null;
Excel.Application excelApp = new Excel.Application();
excelApp.Visible = true;
try {
// Create new workbook
wb = (Excel.Workbook)(excelApp.Workbooks.Add(Type.Missing));
ws = wb.ActiveSheet as Excel.Worksheet;
// write data ...
// Save & Close
excelApp.DisplayAlerts = false; // Don't show file dialog for overwrite
wb.Close(true, targetFilename, Type.Missing);
} finally {
// Close the Excel process
if (null != ws)
Marshal.ReleaseComObject(ws);
if (null != wb)
Marshal.ReleaseComObject(wb);
excelApp.Quit();
Marshal.ReleaseComObject(excelApp);
GC.Collect();
}
This code is exectued by multiple threads at a time, and it's been working almost always. Even the Excel processes disappear in task manager.
However, sometimes a System.Runtime.InteropServices.COMException is thrown at wb.Close(true, targetFilename, Type.Missing). It claims that access on the target filename was denied. Though I've been making sure that the target filenames are unique.
May the exception be due to any bad handling of Excel or maybe that I'm using threads?
Apparently, targetFilename wasn't really unique. There was one single difference in upper/lower case spelling, and it seems like two threads tried to write to the same file at once. The issue was easily solvable by using targetFilename.ToLower().
Anyway, if you discover any further potential issues, please leave a comment.

Hide Excel 2013 while programmatic change a workbook

A really good new feature of Excel 2013 is that it cannot forced to show more than one Excel workbook in one application. This seems the cause of my Problem:
If I open an Excel workbook programmatically using c# and interop Excel 2013 starts with a new application window. I can working with the workbook in code without problems but I want to hide the application.
Using
Excel.Application excelApp = new Excel.Application();
......
excelApp.Workbooks.Open(...);
excelApp.Visible = false;
hides the application window after showing it. Is there a way to stop showing the application as in Excel 2010 or earlier Version?
In my Excel 2013, using excelApp = new Excel.Application doesn't show any window.
May it be some VBA code in opened workbook which displays window?
So I know the question is old but I needed an answer and none of the given ones worked for me. I ended up just setting Visible to false when initializing to avoid the window flashing open before hiding.
Excel.Application excelApp = new Excel.Application() { Visible = false };
Hide Excel application your code has launched, before opening any Workbook :
Excel.Application excel = new Excel.Application();
excel.Visible = false;
[...]
Excel.Workbook workbook;
workbook = excel.Workbooks.Open(...);
You should always put the Visible into try/catch-block
Excel.Application xlsApp = new Excel.Application();
try
{
// Must be surrounded by try catch to work.
// http://naimishpandya.wordpress.com/2010/12/31/hide-power-point-application-window-in-net-office-automation/
xlsApp.Visible = false;
xlsApp.DisplayAlerts = false;
}
catch (Exception e)
{
Console.WriteLine("-------Error hiding the application-------");
Console.WriteLine("Occured error might be: " + e.StackTrace);
}
Excel.Workbook workbook;
workbook = xlsApp.Workbooks.Open(File, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing);

Export data from dataset to excel

I am trying to export data from dataset to excel and save it directly to a given path without giving me the option to open,save or cancel.
Using ExcelLibrary this is a one liner ...
DataSet myDataSet;
... populate data set ...
ExcelLibrary.DataSetHelper.CreateWorkbook("MyExcelFile.xls", myDataSet);
See also Create Excel (.XLS and .XLSX) file from C#
This C# Excel library can also be used to export the dataset. More details about how to export can be found here.
ExcelDocument xls = new ExcelDocument();
xls.easy_WriteXLSFile_FromDataSet("ExcelFile.xls", dataset,
new ExcelAutoFormat(Styles.AUTOFORMAT_EASYXLS1), "Sheet Name");
It's not the greatest solution but here is what I did, it opens a new excel document then copies what is in the dataset, all you need to do is sort out the columns and save it.
Btw totes my first post to answer a question, hope it helps
private void cmdExport_Click(object sender, EventArgs e)
{
System.Diagnostics.Process.Start("excel.exe");
try
{
copyAlltoClipboard();
Microsoft.Office.Interop.Excel.Application xlexcel;
Microsoft.Office.Interop.Excel.Workbook xlWorkBook;
Microsoft.Office.Interop.Excel.Worksheet xlWorkSheet;
object misValue = System.Reflection.Missing.Value;
xlexcel = new Excel.Application();
xlexcel.Visible = true;
xlWorkBook = xlexcel.Workbooks.Add(misValue);
xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
Excel.Range CR = (Excel.Range)xlWorkSheet.Cells[1, 1];
CR.Select();
xlWorkSheet.PasteSpecial(CR, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, true);
}
catch (Exception ex)
{
MessageBox.Show("Error :" + ex.Message);
}
}
private void copyAlltoClipboard()
{
dataGridViewItems.SelectAll();
DataObject dataObj = dataGridViewItems.GetClipboardContent();
if (dataObj != null)
Clipboard.SetDataObject(dataObj);
}
Check this DataSetToExcel
and c# (WinForms-App) export DataSet to Excel
In the first link change the code as follows:
Remove the all code that initially starts and try the following
using (StringWriter sw = new StringWriter("Your Path to save"))
{
using (HtmlTextWriter htw = new HtmlTextWriter(sw))
{
// instantiate a datagrid
DataGrid dg = new DataGrid();
dg.DataSource = ds.Tables[0];
dg.DataBind();
dg.RenderControl(htw);
}
}
Here's another C# library, which lets you export from a DataSet to an Excel 2007 .xlsx file, using the OpenXML libraries.
http://www.mikesknowledgebase.com/pages/CSharp/ExportToExcel.htm
All of the source code is provided, free of charge, along with a demo application, and you can use this in your ASP.Net, WPF and WinForms applications.
Once you've added the class to your application, it just takes one function call to export your data into an Excel file.
CreateExcelFile.CreateExcelDocument(myDataSet, "C:\\Sample.xlsx");
It doesn't get much easier than that.
Good luck !
Hi i found a perfect solution Here
Just replace 'missing.value' with System.Type.Missing in the code. Also remove
oWB.Close(System.Type.Missing, System.Type.Missing, System.Type.Missing);
and
oXL.Quit();
from the code. Otherwise your excel will get closed automatically as soon as it open.

Categories