Exporting Excel file to the browser - ASP.Net - c#

Im trying to export an excel document created from a data table to the browser/client. This works fine in an aspx code behind file, but when I have moved the method to a .cs file using MVC the method does not generate the excel file in the browser downloads bar, the response is returned as text and encoded characters in the request response, below is the code in the controller that isn't generating the file (the code saves the file to the hard disc firsts, which works correctly just doesn't send to the browser)
protected void ExportToExcel_Click(object sender, EventArgs e)
{
var fromdate = Convert.ToDateTime(DateFrom);
var todate = Convert.ToDateTime(DateTo);
var type = ddlTransactionType.SelectedValue;
var transstatus = P2UFramework.Payment.BrainTree.GetTransactionStatus(type);
var transdetails = P2UFramework.Payment.BrainTree.Transactions_ByDate(transstatus, fromdate, todate.AddDays(1));
//var dt = P2UFramework.Utility.Conversion.ListToDataTable.ToDataTable(transdetails);
var dt = new DataTable();
dt.Columns.Add("Date");
dt.Columns.Add("TransactionId");
dt.Columns.Add("PatientId");
dt.Columns.Add("PatientDetails");
dt.Columns.Add("Email");
dt.Columns.Add("RxOrderNo");
dt.Columns.Add("PODOrderNo");
dt.Columns.Add("OTCOrderNo");
dt.Columns.Add("BrainTreeTransactionId");
dt.Columns.Add("Amount");
dt.Columns.Add("TransactionStatus");
foreach (var item in transdetails)
{
dt.Rows.Add(item.TransactionDate, item.P2UTransactionId, item.CustomerId, item.CustomerName, item.Email, item.PrescriptionOrderID,
item.PodOrderID, item.OtcOrderID, item.BrainTreeTransactionId, item.Amount, item.Status);
}
// Export to excel
string outputFile = KwibooCommon.DataExtract.Write(dt, Server.MapPath("~/_Assets/"), KwibooCommon.DataExtract.Format.Excel);
try
{
HttpContext.Current.Response.ClearContent();
HttpContext.Current.Response.ClearHeaders();
HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=" + outputFile.Split('\\').Last());
HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
HttpContext.Current.Response.WriteFile(outputFile);
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.Close();
}
catch (ThreadAbortException ex)
{
throw ex;
}
finally
{
try
{
File.Delete(outputFile);
}
catch (Exception) { }
}
}

Rob,
You're going to want to put some layers between the endpoint and ultimately reading the excel file for multiple reasons.
First, excel files by nature are extremely volatile, and not meant to be concrete data sources like SQL or even Access.
Second, you're combining a series of concerns into a single method that belong in multiple classes. Take it from experience that this is going to be a nightmare for you to maintain in the future. Especially if you're a VBA guru, that excel file is macro enabled, and you're going to have to maintain it moving forward.
Let's assume you have to live off of the excel file. Read it into access at the very least. SQL server express is free and can handle .csv imports easily out of the box. The server connection wizard in VS can handle .mdb files if need be. Import the data, have a plan and an automated solution to maintain it. Ideally, have a service that reads the csv from the excel file on a regular basis into SQL.
I've been in the situation of having to develop solutions with nothing but office. It's not fun, but there are ways to make it better.
Have a controller class that provides an endpoint and nothing else. Have a service layer that handles business logic, and have a repository layer that does nothing but read from your data source. I'd recommend it not be an .mdb file but if it is, VS can handle it.
I hope I've given you a brief overview of how to handle the situation. Please ask if you have any specific questions. I've been there, I'm happy to help.

Related

Error opening excel generated with EPPlus in medium trust environment

I have searched high and low and find similar questions all with similar answers and none seem to solve my problem. My ultimate goal is to allow the user to save a datatable to an excel document which opens (not saves) in excel and they can choose to save it if they wish. This is to be done with the click of a button on a webpage written in c#.
There's tons of examples on how to do this, in many different ways, many of which seem quite straightforward - I just can't seem to find a way that completely works in the environment I am in (medium trust production environment and this cannot be altered).
Essentially, when a button is clicked on the webpage, my code creates an excel file using EPPlus:
ExcelPackage p = new ExcelPackage();
p.Workbook.Worksheets.Add(worksheetname);
ExcelWorksheet workSheet = p.Workbook.Worksheets[1];
workSheet.Cells[1,1].LoadFromDataTable(dt, true);
var range = workSheet.Cells[1,1,dt.Rows.Count,dt.Columns.Count];
var table = workSheet.Tables.Add(range, "results");
table.ShowTotal = false;
table.TableStyle = TableStyles.Medium4;
workSheet.Cells.AutoFitColumns();
workSheet.Cells.Style.HorizontalAlignment = ExcelHorizontalAlignment.Left;
workSheet.View.ShowGridLines = false;
Response.Clear();
Response.ClearHeaders();
Response.BinaryWrite(p.GetAsByteArray());
//Also tried this instead of above line with same error: p.SaveAs(Response.OutputStream);
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("content-disposition", "attachment; filename=" + filename + ".xlsx");
Response.End();
This code seems straightforward, and works locally from my machine. I click the button, then receive a prompt to Save or Open. I choose Open, and the file opens in Excel, which is exactly what my end users want. I then copy the website out to our dev web server(full trust) and it still works great. Then I copy to our staging environment which is medium trust, not full trust and get the following error: System.Security.SecurityException Exception Message: Request for the permission of type 'System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.
I am not trying to save a file, just simply display for the end user to be able to open it in Excel. They can then choose to Save-As from Excel app if they wish.
Doing this with Excel interop is straightforward and I got it working locally as well, but without Excel on the server, I don't believe this is a viable option. Hence my decision to try EPPlus, which seemed like the answer until it wouldn't work in our staging environment. The only difference I can think of is that our staging environment is medium trust and the development environment is full trust. I need a solution that works in medium trust, but without the errors of my other option I tried below using html.
I used to do this as html and open with the following code, but since it isn't a true excel file, when the user selects open, they always got the error: "The file format and extension don't match. The file could be corrupted or unsafe. Unless you trust its source, don't open it. Do you want to open it anyway?". They could just hit yes and most of the time the file would open and behave just fine, other times it will simply say "Unable to read file" and they would have to re-click the button - they have requested this error go away. Here is how I used to do it (and again, this worked most the time, and works just fine in our medium trust environment, but has that pesky error). This is why I decided to go the route of creating a 'true' excel file-to avoid this error.
System.IO.StringWriter tw = new System.IO.StringWriter();
System.Web.UI.HtmlTextWriter hw = new System.Web.UI.HtmlTextWriter(tw);
DataGrid dgGrid = new DataGrid();
dgGrid.DataSource = dt;
dgGrid.DataBind();
dgGrid.RenderControl(hw);
Response.Clear();
Response.ClearContent();
Response.ClearHeaders();
Response.Buffer = true;
Response.Expires = 0;
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
//Also tried these two ways to see if the "file format and extension don't match" error go away-it didn't
//also tried sending in filename with .xls and .xlsx extension with same results
//Response.ContentType = "application/vnd.ms-excel";
//Response.ContentType = "application/octet-stream";
Response.AppendHeader("Content-Disposition", "attachment; filename=" + filename);
this.EnableViewState = false;
Response.Write(tw.ToString());
Response.Flush();
Response.Clear();
Response.End();
Any suggestions would be great! Also, if there is another open source tool which may do the job, that would be good too. I cannot use any and all open source options, but if someone has a suggestion on another tool that would work in medium trust, I can see if it's in my company's approved list.
The issue is that EPPlus is using features from System.IO even when using the constructors that take a memory stream. That particular library needs full trust, so it will not run in a medium trust environment. Have a look at the following two files:
http://epplus.codeplex.com/SourceControl/latest#EPPlus/Packaging/ZipPackage.cs
string name = Path.GetFileName(p.Key);
string extension = Path.GetExtension(p.Key);
http://epplus.codeplex.com/SourceControl/latest#EPPlus/Packaging/ZipPackagePart.cs
internal void WriteZip(ZipOutputStream os)
{
...code...
if (_rels.Count > 0)
{
string f = Uri.OriginalString;
var name = Path.GetFileName(f);
_rels.WriteZip(os, (string.Format("{0}_rels/{1}.rels", f.Substring(0, f.Length - name.Length), name)));
}
...code...
}
Both of those sections use the Path class from System.IO to perform some basic tasks.
ZipPackage.cs solution:
var parts = p.Key.Split('/');
var name = parts[parts.Length - 1];
parts = name.Split('.');
var extension = "." + parts[parts.Length - 1];
ZipPackagePart.cs solution:
internal void WriteZip(ZipOutputStream os)
{
...code...
if (_rels.Count > 0)
{
string f = Uri.OriginalString;
var parts = f.Split('/');
var name = parts[parts.Length - 1];
_rels.WriteZip(os, (string.Format("{0}_rels/{1}.rels", f.Substring(0, f.Length - name.Length), name)));
}
...code...
}
There may be other places where this issue presents itself, but this should give you an idea of what you need to look for and change after pulling the source code in to your solution. Best of luck!

c# downloading a zip archieve of files created by web application

using Ionic.Zip
...
using (ZipFile zip = new ZipFile())
{
zip.AlternateEncodingUsage = ZipOption.AsNecessary;
zip.AddDirectoryByName("Files");
foreach (GridViewRow row in GridView1.Rows)
{
if ((row.FindControl("chkSelect") as CheckBox).Checked)
{
string filePath = (row.FindControl("lblFilePath") as Label).Text;
zip.AddFile(filePath, "Files");
}
}
Response.Clear();
Response.BufferOutput = false;
string zipName = String.Format("Zip_{0}.zip", DateTime.Now.ToString("yyyy-MMM-dd-HHmmss"));
Response.ContentType = "application/zip";
Response.AddHeader("content-disposition", "attachment; filename=" + zipName);
zip.Save(Response.OutputStream);
Response.End();
}
Hello! This portion of code does the downloading of a zipped directory. Let's say I have a gridview of CONTENTS of text files I want to download. Is there a way of making the program download such archieve without knowing or writing the paths to files?
The code should work this way:
1. get item from gridview
2. create a text file from the content
3. add it to the zip directory
(repeat foreach item in gridview)
n. download a zipped file
According to the documentation, you can add an entry from a Stream. So consider where you currently do this:
zip.AddFile(filePath, "Files");
Instead of adding a "file" given a path, you'd add a "file" given a stream of data.
So you can create a stream from a string:
new MemoryStream(Encoding.UTF8.GetBytes(someString)) // or whatever encoding you use
and add it to the Zip:
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(someString)))
{
zip.AddEntry(someFileName, stream);
// other code
zip.Save(Response.OutputStream);
}
One thing to note here is that your resource management and disposal (with the using blocks) might get a little tricky. This is because, according to the documentation:
The application should provide an open, readable stream; in this case it will be read during the call to Save() or one of its overloads.
What this means is that if you dispose of any of the streams before calling .Save(), it will fail when you call it. You might want to look through the documentation some more to see if there's a way to force the Zip to read the streams earlier in the process. Otherwise you're basically going to have to manage a bunch of open streams until it's time to "save" the Zip.
Edit: It looks like the documentation was right there...
In cases where a large number of streams will be added to the ZipFile, the application may wish to avoid maintaining all of the streams open simultaneously. To handle this situation, the application should use the AddEntry(String, OpenDelegate, CloseDelegate) overload.
This will be a little more complex and will require you to open/close/dispose your streams manually in your delegates. So it's up to you as you build your logic whether this is preferable to nesting your using blocks. It'll likely depend on how many streams you plan to use.

Read Excel file with OleDB c#, when it is used by other process

I am trying to read an excel file every 2 seconds, This file is getting updated by other RTD application.
I am able to read this file by Oledb connection, but problem comes when i am trying to read it every 2 seconds. Out of 10 attempts it is able to read 4-5 times only and at other attempts ,it throws exception.
Connection String
Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\nids\shes.xlsm;Extended Properties="Excel 12.0 Macro;HDR=Yes;IMEX=1"
Code
//opening connection to excel file
using (OleDbConnection connection = new OleDbConnection(constr))//constr = connection string
{
try
{
connection.Open();
isconopen = true;
}
catch
{
dispatcherTimer2.Start();
connection.Close();
isconopen = false;
}
// If connection is ok , then query sheet
if (isconopen == true)
{
strcon = "SELECT * FROM [" + dsheet + "]";
using (OleDbDataAdapter adapter = new OleDbDataAdapter(strcon, connection))
{
try
{
adapter.Fill(result);
isread = true;
adapter.Dispose();
connection.Close();
}
catch
{
isread = false;
dispatcherTimer2.Start();
adapter.Dispose();
connection.Close();
}
}
}
//if able to retrieve data then call some other function
if (isread == true)
{
converToCSV(0);// for further processing
}
Please help me , i am trying this from last 1 month. Please please please please help me out
Sadly OleDB driver by default will open file exclusively then you can't open it when it's in use by someone else, even just for reading.
Two considerations:
Other application may finalize its work with the file within milliseconds so it's good to try again
Driver will always open file locked for writing (so you can't open it twice via OleDB) but it's shared for reading (so you can copy it).
That said I suggest you should first try to open it after a short pause, if it's still in use (and you can't wait more) then you can make a copy and open that.
Let me assume you have your code in a HandlExcelFile() function:
void HandleExcelFile(string path)
{
try
{
// Function will perform actual work
HandleExcelFileCore(path);
}
catch (Exception) // Be more specific
{
Thread.Sleep(100); // Arbitrary
try
{
HandleExcelFileCore(path);
}
catch (Exception)
{
string tempPath = Path.GetTempFileName();
File.Copy(path, tempPath);
try
{
HandleExcelFileCore(tempPath);
}
finally
{
File.Delete(tempPath);
}
}
}
}
Code is little bit ugly so just consider it a starting point to write your own function.
Considerations:
Retry isn't such bad thing and it's a common way to solve this kind of problems. It's, for example, what Windows shell does (and it's even more normal with networks).
If application didn't close the file then you may copy (and read) old data. If you always need most up-to-date data then you have only one choice: wait. If you can assume that unsaved data belongs to the previous time frame (T - 1, as in digital electronic when signal edge is on clock edge) then just do it and live happy.
Untested solution:
I didn't try this so you have to do it by yourself. Actuallly (I was initially wrong) you can open a read-only connection (through extended properties). It's not documented if this apply to connection only or both file handle and connection. Anyway let's try to change your connection string to:
Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\nids\shes.xlsm;Extended Properties="Excel 12.0 Macro;HDR=Yes;IMEX=1;ReadOnly=true"
Just added a ReadOnly=true at the end of Extended Properties.
Other solutions:
The only alternative solution that comes to my mind is to...manually read Excel file (so you can open it just for reading). That said even in this case the other application may have not written new data (so you'll read old one).
Don't use a timer at all. Change your design to use a FileSystemWatcher, you'll read the file only when notified it has been changed.
When applicable...just do not use a shared file as IPC mechanism! Well, you may not be able to change 2nd application so this may not be your case.
Do not use OleDB to read Microsoft Excel files, there are many 3rd part free libraries that don't open file with exclusive lock, for example this one.
Do not read data directly from files, if Microsoft Excel instance is always running you can use COM interop to get notifications. Check this general article about COM add-ins and this to see how you can attach your C# application with Office Interop to an existing Microsoft Excel instance.
Not sure , why do you want to read the excel the way you are doing.
You can try LinqToExcel for excel reading , its a nice little library for reading excel files also if you need to create excel then try to EPPLUS library. These library i personally found really effective when working with Excels
I had similar problem. Below fixes worked for me
1) Don't hold your connection to sheet. Instead open connection read data and close the connection immediately.
2) If you are using managed code in the unmanaged application then consider using object of managed type instead of pointer(using gcnew) and use Stack Semantics to make sure that memory is cleaned up when object goes out of scope.

Exporting Gridview to Excel - External table is not in the expected format

This is my code, but I end up with an Unexpected format error. Something must be wrong with the MIME-type, however I found this one as being te original official MIME-type.
What am I missing? Each time I try to open any document I create by this method seems to be "corrupt", but when I click the message away everything works fine.
Message: The file you are trying to open, 'name.ext', is in a different format than specified by the file extension. Verify that the file is not corrupted and is from a trusted source before opening the file. Do you want to open the file now?
However, I need to get rid of this warning, since I cannot import the file because it's invalid. If I save the file as 2003-2007 format .xls, it seems to be fixed. But this is not a working solution.
protected void ExportToExcel(string fileName)
{
var cmd = new SqlCommand(query);
var dt = GetData(cmd);
var writer = new StringWriter();
var gridview = new GridView();
gridview.AllowPaging = false;
gridview.DataSource = dt;
gridview.DataBind();
Response.Clear();
var hw = new HtmlTextWriter(writer);
gridview.RenderControl(hw);
Response.ContentType = "application/vnd.ms-excel";
Response.AddHeader("Content-Disposition", "attachment;filename=" + fileName + ".xls");
// this.EnableViewState = false;
Response.Write(writer.ToString());
// Response.Flush();
Response.End();
}
To be direct, you are not doing excel export. You are sending html table with fake response headers to trick the browser to open this content with excel.
That can fail in many ways, in fact can be interpreded as malicious behaviour. I think that going this way is only calling for troubles.
Better way is to use native excel library and do export with it. Here is the example of exporting DataTable to real excel file (2007+)
https://stackoverflow.com/a/9569827/351383
Thought I'd add a comment for future reference. One of my macros that was doing a TransferSpreadsheet (export) started failing, giving the "External table is not in the expected format." error window. I went to the path of the destination file and opened it. Found that the last file created contained 'garbage' characters. I deleted the Excel file and ran my macro again with no issue.

Moving data from Sql Server to Excel via a Web Service

My users want data from my application in Excel. The data resides in a SQL Server database but I don't want the users to have direct access to the database, I would rather provide them a web service to get the data. What is the best way to move data from SQL Server to Excel via a web service?
You can do it as a straight asp.net page and return a .csv file. If you change the mimetype to text/csv, it should default to open in excel. This would be the easiest approach, and one that I've used in the past with great success.
The following code will generate an excel file from a datatable, you can just stream this to the user
public static void CreateExcelFromDataTable(string filename, DataTable dt) {
DataGrid grid = new DataGrid();
grid.HeaderStyle.Font.Bold = true;
grid.DataSource = dt;
grid.DataMember = dt.TableName;
grid.DataBind();
// render the DataGrid control to a file
using (StreamWriter sw = new StreamWriter(filename)) {
using (HtmlTextWriter hw = new HtmlTextWriter(sw)) {
grid.RenderControl(hw);
}
}
}
you can also blow back an html page with a table on it with a .xls filename, excel knows how to open this as well
Have the Web service emit a string buffer where cells are delimited by the tab character and rows by a carriage return/line feed. Then pipe the results of this into Excel with an Excel Web query. It's cheap, quick, a little bit dirty, but good for simple processes.
I'm in favor of Excel Web Query (Data/Import External)
Also, there is a way to post data from Excel back to the web site. At least it's working in MS Sharepoint

Categories