Morning,
I am writing simple windows service that will take excel file from given location, generate xml file and move excel to another folder.
I am using:
Excel = Microsoft.Office.Interop.Excel
My code looks like this:
Excel.Application xlApp = null;
Excel.Workbook xlWorkBook = null;
Excel.Worksheet xlWorkSheet = null;
try
{
xlApp = new Excel.Application();
xlWorkBook = xlApp.Workbooks.Open(file);
xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item("Części");
//doing something with xml and excel
xlApp.DisplayAlerts = false;
xlWorkBook.Close();
xlApp.Quit();
} catch (Exception e) {
this.writeErrorLog(e);
} finally {
// Close the Excel process
if (null != xlWorkSheet)
Marshal.ReleaseComObject(xlWorkSheet);
if (null != xlWorkBook)
Marshal.ReleaseComObject(xlWorkBook);
Marshal.ReleaseComObject(xlApp);
GC.Collect();
}
My problem is that when i run it as normal program it works fine and does everything i need. But when i create service and install it, it creates exceptions every time i upload excel file to this folder. Exception says that program could not gain access to my excel file.
System.Runtime.InteropServices.COMException
I have checked this topic but it didn't help me. Can anyone help me solve this problem?
EDIT: Btw. I am using this tutorial to create service.
EDITv2: The way i take file paths:
string sc_path = #"C:\Projekty\AAPLXML\AppFolders\upload";
string tg_path = #"C:\Projekty\AAPLXML\AppFolders\processed";
if (System.IO.Directory.Exists(sc_path) && System.IO.Directory.Exists(tg_path))
{
List<string> files = System.IO.Directory.GetFiles(sc_path).ToList();
if (files.Count == 0) return;
files.ForEach(f => new XmlGenerator(f).Start());
}
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
I am trying to read an excel sheet using C# and store each row into an array. I am able to open the file but the code I am using currently reads to an "2D-Object" array but I would like to read the info into 1D-string arrays.
static void Main(string[] args)
{
// Reference to Excel Application.
Excel.Application xlApp = new Excel.Application();
Excel.Workbook xlWorkbook = xlApp.Workbooks.Open(Path.GetFullPath("excelpractice1.xlsx"));
// Get the first worksheet.
Excel.Worksheet xlWorksheet = (Excel.Worksheet)xlWorkbook.Sheets.get_Item(1);
// Get the range of cells which has data.
Excel.Range xlRange = xlWorksheet.UsedRange;
// Get an object array of all of the cells in the worksheet with their values.
object[,] valueArray = (object[,])xlRange.get_Value(Excel.XlRangeValueDataType.xlRangeValueDefault);
// Close the Workbook.
xlWorkbook.Close(false);
// Relase COM Object by decrementing the reference count.
Marshal.ReleaseComObject(xlWorkbook);
// Close Excel application.
xlApp.Quit();
// Release COM object.
Marshal.FinalReleaseComObject(xlApp);
Console.ReadLine();
}
}
}
`
Sorry, I was trying to comment only. =X
But did it worked for you anyway? LinqToExcel is a great library for manipulate spreadsheets!
Please, let me know if has any doubt yet.
=)
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.
How can I open my Excel worksheet in the current open instance of Microsoft Excel? When I use my code, a new instance of Excel is opened.
private Excel.Application xlApp;
private Excel.Workbook xlWorkBook;
private Excel.Worksheet xlWorkSheet;
xlApp = new Excel.Application();
xlApp.Visible = true;
xlWorkBook = xlApp.Workbooks.Open(textBox1.Text);
xlWorkSheet = (Excel.Worksheet)xlWorkBook.ActiveSheet;
You can do something like the following to get a reference to a running Excel instance (if there is one):
public Excel.Application TryGetExistingExcelApplication()
{
try
{
object o = Marshal.GetActiveObject("Excel.Application");
return (Excel.Application)o;
}
catch (COMException)
{
// Probably there is no existing Excel instance running, return null
return null;
}
}
Once you have a reference to a running instance, you can access it's Workbooks collection.
One caveat: if you try to automate an existing Excel instance, you may get "server busy" exceptions. To avoid this, you can implement IOleMessageFilter error handlers. This article describes how to do it for automating Visual Studio; the technique is identical for automating Excel.