Export to CSV using MVC, C# and jQuery - c#

I am trying to export a list to a CSV file.
I got it all working up to the point I want to write to file to the response stream. This doesn't do anything.
Here is my code:
Call the the method from the page.
$('#btn_export').click(function () {
$.post('NewsLetter/Export');
});
The code in the controller is as follows:
[HttpPost]
public void Export()
{
try
{
var filter = Session[FilterSessionKey] != null ? Session[FilterSessionKey] as SubscriberFilter : new SubscriberFilter();
var predicate = _subscriberService.BuildPredicate(filter);
var compiledPredicate = predicate.Compile();
var filterRecords = _subscriberService.GetSubscribersInGroup().Where(x => !x.IsDeleted).AsEnumerable().Where(compiledPredicate).GroupBy(s => s.Subscriber.EmailAddress).OrderBy(x => x.Key);
ExportAsCSV(filterRecords);
}
catch (Exception exception)
{
Logger.WriteLog(LogLevel.Error, exception);
}
}
private void ExportAsCSV(IEnumerable<IGrouping<String, SubscriberInGroup>> filterRecords)
{
var sw = new StringWriter();
//write the header
sw.WriteLine(String.Format("{0},{1},{2},{3}", CMSMessages.EmailAddress, CMSMessages.Gender, CMSMessages.FirstName, CMSMessages.LastName));
//write every subscriber to the file
var resourceManager = new ResourceManager(typeof(CMSMessages));
foreach (var record in filterRecords.Select(x => x.First().Subscriber))
{
sw.WriteLine(String.Format("{0},{1},{2},{3}", record.EmailAddress, record.Gender.HasValue ? resourceManager.GetString(record.Gender.ToString()) : "", record.FirstName, record.LastName));
}
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=adressenbestand.csv");
Response.ContentType = "text/csv";
Response.Write(sw);
Response.End();
}
But after Response.Write(sw) nothing is happening. Is it even possible to save a file this way?
Regards
Edit
The response headers I see when I click the button are:
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/csv; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 2.0
Content-Disposition: attachment; filename=adressenbestand.csv
X-Powered-By: ASP.NET
Date: Wed, 12 Jan 2011 13:05:42 GMT
Content-Length: 113
Which seem OK to me..
Edit
I got rid of the jQuery part en replaced it by an hyperlink and this is working fine for me now:
<a class="export" href="NewsLetter/Export">exporteren</a>

yan.kun was on the right track but this is much much easier.
public FileContentResult DownloadCSV()
{
string csv = "Charlie, Chaplin, Chuckles";
return File(new System.Text.UTF8Encoding().GetBytes(csv), "text/csv", "Report123.csv");
}

With MVC you can simply return a file like this:
public ActionResult ExportData()
{
System.IO.FileInfo exportFile = //create your ExportFile
return File(exportFile.FullName, "text/csv", string.Format("Export-{0}.csv", DateTime.Now.ToString("yyyyMMdd-HHmmss")));
}

In addition to Biff MaGriff's answer. To export the file using JQuery, redirect the user to a new page.
$('#btn_export').click(function () {
window.location.href = 'NewsLetter/Export';
});

What happens if you get rid of the stringwriter:
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=adressenbestand.csv");
Response.ContentType = "text/csv";
//write the header
Response.Write(String.Format("{0},{1},{2},{3}", CMSMessages.EmailAddress, CMSMessages.Gender, CMSMessages.FirstName, CMSMessages.LastName));
//write every subscriber to the file
var resourceManager = new ResourceManager(typeof(CMSMessages));
foreach (var record in filterRecords.Select(x => x.First().Subscriber))
{
Response.Write(String.Format("{0},{1},{2},{3}", record.EmailAddress, record.Gender.HasValue ? resourceManager.GetString(record.Gender.ToString()) : "", record.FirstName, record.LastName));
}
Response.End();

Respect to Biff, here's a few tweaks that let me use the method to bounce CSV from jQuery/Post against the server and come back as a CSV prompt to the user.
[Themed(false)]
public FileContentResult DownloadCSV()
{
var csvStringData = new StreamReader(Request.InputStream).ReadToEnd();
csvStringData = Uri.UnescapeDataString(csvStringData.Replace("mydata=", ""));
return File(new System.Text.UTF8Encoding().GetBytes(csvStringData), "text/csv", "report.csv");
}
You'll need the unescape line if you are hitting this from a form with code like the following,
var input = $("<input>").attr("type", "hidden").attr("name", "mydata").val(data);
$('#downloadForm').append($(input));
$("#downloadForm").submit();

From a button in view call .click(call some java script). From there call controller method by window.location.href = 'Controller/Method';
In controller either do the database call and get the datatable or call some method get the data from database table to a datatable and then do following,
using (DataTable dt = new DataTable())
{
sda.Fill(dt);
//Build the CSV file data as a Comma separated string.
string csv = string.Empty;
foreach (DataColumn column in dt.Columns)
{
//Add the Header row for CSV file.
csv += column.ColumnName + ',';
}
//Add new line.
csv += "\r\n";
foreach (DataRow row in dt.Rows)
{
foreach (DataColumn column in dt.Columns)
{
//Add the Data rows.
csv += row[column.ColumnName].ToString().Replace(",", ";") + ',';
}
//Add new line.
csv += "\r\n";
}
//Download the CSV file.
Response.Clear();
Response.Buffer = true;
Response.AddHeader("content-disposition", "attachment;filename=SqlExport"+DateTime.Now+".csv");
Response.Charset = "";
//Response.ContentType = "application/text";
Response.ContentType = "application/x-msexcel";
Response.Output.Write(csv);
Response.Flush();
Response.End();
}

Even if you have resolved your issue, here is another one try to export csv using mvc.
return new FileStreamResult(fileStream, "text/csv") { FileDownloadName = fileDownloadName };

I Think you have forgot to use
Response.Flush();
under
Response.Write(sw);
please check

Related

ASP.NET download csv file as zip?

I've been reading through:
https://www.aspsnippets.com/Articles/Export-data-from-SQL-Server-to-CSV-file-in-ASPNet-using-C-and-VBNet.aspx
Rather than only have the option to download as csv as described there in:
//Download the CSV file.
Response.Clear();
Response.Buffer = true;
Response.AddHeader("content-disposition", "attachment;filename=SqlExport.csv");
Response.Charset = "";
Response.ContentType = "application/text";
Response.Output.Write(csv);
Response.Flush();
Response.End();
is there a way using native asp.net to first zip the csv output from the csv variable in Response.Output.Write(csv); so that the user downloads SqlExport.zip rather than SqlExport.csv?
Roughly based on this, you can create a zip file while streaming it to the client;
Response.ContentType = "application/octet-stream";
Response.Headers.Add("Content-Disposition", "attachment; filename=\"SqlExport.zip\"");
using var archive = new ZipArchive(Response.Body, ZipArchiveMode.Create);
var entry = archive.CreateEntry("SqlExport.csv");
using var entryStream = entry.Open();
entryStream.Write(csv); // write the actual content here
entryStream.Flush();
Though rather than appending to a single csv string, you should probably consider using a StreamWriter to write each snippet of text directly into the response stream. Substituting from your linked csv example;
using var sw = new StreamWriter(entryStream);
// TODO write header
foreach (DataRow row in dt.Rows)
{
foreach (DataColumn column in dt.Columns)
{
//Add the Data rows.
await sw.WriteAsync(row[column.ColumnName].ToString().Replace(",", ";") + ',');
}
//Add new line.
await sw.WriteLineAsync();
}
Though that is a terrible example of a csv file. Rather than substituting ';' characters, the string should be quoted & all quotes escaped.
However Response.Body is only available in .net 5 / core. To write directly to a http response in .net 4.8 or earlier, you'll have to write your own HttpContent. Putting everything together, including a better csv formatter;
public class ZipContent : HttpContent
{
private DataTable dt;
private string name;
public ZipContent(DataTable dt, string name = null)
{
this.dt = dt;
this.name = name ?? dt.TableName;
Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"{name}.zip"
};
}
private string formatCsvValue(string value)
{
if (value == null)
return "";
if (value.Contains('"') || value.Contains(',') || value.Contains('\r') || value.Contains('\n'))
return $"\"{value.Replace("\"", "\"\"")}\"";
return value;
}
private IEnumerable<DataColumn> Columns()
{
// Why is this not already an IEnumerable<DataColumn>?
foreach (DataColumn col in dt.Columns)
yield return col;
}
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
using var archive = new ZipArchive(stream, ZipArchiveMode.Create);
var entry = archive.CreateEntry($"{name}.csv");
using var entryStream = entry.Open();
using var sw = new StreamWriter(entryStream);
await sw.WriteLineAsync(
string.Join(",",
Columns()
.Select(c => formatCsvValue(c.ColumnName))
));
foreach (DataRow row in dt.Rows)
{
await sw.WriteLineAsync(
string.Join(",",
row.ItemArray
.Select(o => formatCsvValue(o?.ToString()))
));
}
}
protected override bool TryComputeLength(out long length)
{
length = 0;
return false;
}
}
Have a look at the ZipArchive Class
you can use public System.IO.Compression.ZipArchiveEntry CreateEntry (string entryName); to create an ZipEntry nd add it to an archive

Export to Excel in ASP.Net Core 2.0

I used to export data to excel in asp.net mvc using below code
Response.AppendHeader("content-disposition", "attachment;filename=ExportedHtml.xls");
Response.Charset = "";
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.ContentType = "application/vnd.ms-excel";
this.EnableViewState = false;
Response.Write(ExportDiv.InnerHtml);
Response.End();
When this Code Run it create a file and ask for a location to save
I tried working with NPOI and create Excel file very well but cant save file on client location .
Is there any way to make above code works on asp.net core 2.0 or any other way where I can save data in excel format on client machine ?
There are many ways to achieve that.
Option 1: save to wwwroot
You can generate the Excel and save it to the wwwroot folder. And then you can serve it as static content on the page.
For example you have a folder called 'temp' inside the wwwroot folder to contain all the newly generated excels.
<a href="\temp\development\user1\2018\5\9\excel1.xlsx" download>Download</a>
There are limitations on this approach. 1 of them is the new download attribute. It only works on modern browsers.
Option 2: byte array
Another way is to generate the Excel, convert it into byte array and send it back to the controller. For that I use a library called "EPPlus" (v: 4.5.1) which supports .Net Core 2.0.
The following is just some sample codes I put together to give you an idea. It's not production ready.
using OfficeOpenXml;
using OfficeOpenXml.Style;
namespace DL.SO.Web.UI.Controllers
{
public class ExcelController : Controller
{
public IActionResult Download()
{
byte[] fileContents;
using (var package = new ExcelPackage())
{
var worksheet = package.Workbook.Worksheets.Add("Sheet1");
// Put whatever you want here in the sheet
// For example, for cell on row1 col1
worksheet.Cells[1, 1].Value = "Long text";
worksheet.Cells[1, 1].Style.Font.Size = 12;
worksheet.Cells[1, 1].Style.Font.Bold = true;
worksheet.Cells[1, 1].Style.Border.Top.Style = ExcelBorderStyle.Hair;
// So many things you can try but you got the idea.
// Finally when you're done, export it to byte array.
fileContents = package.GetAsByteArray();
}
if (fileContents == null || fileContents.Length == 0)
{
return NotFound();
}
return File(
fileContents: fileContents,
contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
fileDownloadName: "test.xlsx"
);
}
}
}
Agreed with David Liang's answer.
Slide modifications if want to export whole DataTable.
string export="export";
DataTable dt = new DataTable();
//Fill datatable
dt = *something*
byte[] fileContents;
using (var package = new ExcelPackage())
{
var worksheet = package.Workbook.Worksheets.Add(export);
worksheet.Cells["A1"].LoadFromDataTable(dt, true);
fileContents = package.GetAsByteArray();
}
if (fileContents == null || fileContents.Length == 0)
{
return NotFound();
}
return File(
fileContents: fileContents,
contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
fileDownloadName: export + ".xlsx"
);
Here is our solution to this:
using OfficeOpenXml;
public class XmlService
{
// [...]
public void getXlsxFile(SomeTableObject tbl, ref byte[] bytes)
{
using (ExcelPackage pck = new ExcelPackage())
{
ExcelWorksheet ws = pck.Workbook.Worksheets.Add(tbl.name);
ws.Cells["A1"].LoadFromDataTable(tbl, true);
bytes = pck.GetAsByteArray();
}
}
}
More information on EPPlus is available here and the source code above can be found at our open source repo (GPL).
Just use this code instead of your own:
Response.Clear();
Response.ContentType = "application/vnd.ms-excel";
Response.Headers[HeaderNames.ContentDisposition] = "attachment; filename=ExportedHtml.xls";
Response.WriteAsync(sb.ToString()).Wait();

export dataset to excel using c# (Mvc)

I tried to export the dataset which have 2 tables to the excel sheet, unfortunately I can't.
I have code to export data table to excel. So instead of dataset, I called the ExportToExcel function which I have in my code to export datatable to excel 4 times. But once it created the first sheet, it stops the control flow. Control doesn't call the second function
ExportToExcel(getReports.Tables[1], "ConsumerBookedSlots");
Here is the code:
public ActionResult GetCuratorsAvailability(string availabilitydate)
{
string fromDate = "";
string endDate = "";
if (availabilitydate != "")
{
fromDate = availabilitydate.Split('-')[0];
endDate = availabilitydate.Split('-')[1];
if (DateTime.Parse(endDate) >= DateTime.Parse(fromDate))
{
DataSet getReports = AdminBLL.GetCuratorsAvailability(fromDate, endDate);
ExportToExcel(getReports.Tables[0], "CuratorsAvailableSlots");
ExportToExcel(getReports.Tables[1], "ConsumerBookedSlots");
}
}
return View("Reports");
}
public void ExportToExcel(DataTable dt, string FileName)
{
if (dt.Rows.Count > 0)
{
if (System.Web.HttpContext.Current.Response.RedirectLocation == null)
{
string filename = FileName + ".xls";
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();
//Get the HTML for the control.
dgGrid.RenderControl(hw);
//Write the HTML back to the browser.
//Response.ContentType = application/vnd.ms-excel;
Response.AppendHeader("Content-Disposition", "attachment; filename=" + filename + "");
Response.ContentType = "application/vnd.ms-excel";
Response.Write(tw.ToString());
Response.Flush();
Response.End();
}
}
}
I am unable to download getReports.Tables[1] data because I am getting this error:
server cannot append header after http headers have been sent. mvc
And it is downloading firstfile after error in the browser.
After the download of the first file, the execution hits this line-
Response.End();
That means, the response is ended, and headers have been sent to the client. There's no way you can initiate the download of the second file. If you want to download multiple files in a single button click, you need to zip it into one, and then initiate the download.
To zip the files you can do this-
using System;
using System.IO;
using System.IO.Compression;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
string startPath = #"c:\example\start";
string zipPath = #"c:\example\result.zip";
string extractPath = #"c:\example\extract";
ZipFile.CreateFromDirectory(startPath, zipPath);
ZipFile.ExtractToDirectory(zipPath, extractPath);
}
}
}
Snippet from here.
If you want to make both excel spreadsheets a part of single excel document by putting each one of them as a worksheet, you can use ClosedXML.
I've used ClosedXML (an OpenXML implementation) several times, including in an ASP.NET MVC application and it works like a charm.
Exporting data is a breeze:
using (var memoryStream = new MemoryStream())
{
using (XLWorkbook workbook = new XLWorkbook())
{
using (IXLWorksheet worksheet = workbook.AddWorksheet("WorksheetName"))
{
var toExport = GetData();
worksheet.Row(1).Style.Font.Bold = true;
worksheet.Cell(1, 1).Value = "Column 1";
worksheet.Cell(1, 2).Value = "Column 2";
worksheet.Cell(1, 3).Value = "Column 3";
// Export the data and set some properties
worksheet.Cell(2, 1).Value = toExport.AsEnumerable();
worksheet.RangeUsed().SetAutoFilter();
worksheet.Columns().AdjustToContents();
workbook.SaveAs(memoryStream);
memoryStream.Position = 0;
return File(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "filename.xlsx");
}
}
}

Export data as CSV with line breaks

I'm trying to export data as a CSV file in C#, but the problems starts when i'm trying to import the csv file in excel.
In excel I'm using the function "import from text", and afterwards I set the delimiter to semicolon
My problem is that some of the columns have linebreaks and then the import in excel is wrong.
I have tried with single and doubles quotes with no luck.
I have searched for a solution, but has not found one yet.
Anyone knows if lumenworks has a export function, because i'm using this for the import function
The problem is the export function and the linebreaks are required.
if (list.Any())
{
result = list.Select(i => new
{
i.Product.ProductIdentifier,
i.Product.Header,
body = string.Format("\"" + "{0}" + "\"", i.Product.Body),
Active = i.Product.Active ? 1 : 0,
Approved = i.Product.Approved ? 1 : 0,
i.Product.Sort,
i.Product.MetaDescription,
i.Product.MetaKeywords,
i.CatalogMenuItemContentItemId
}).ToList().ToCsv(";");
}
string attachment = "attachment; filename=myfile.csv";
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ClearHeaders();
HttpContext.Current.Response.ClearContent();
HttpContext.Current.Response.AddHeader("content-disposition", attachment);
HttpContext.Current.Response.ContentType = "text/csv";
HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.GetEncoding("ISO-8859-1");
HttpContext.Current.Response.Write(result);
HttpContext.Current.Response.End();
public static string ToCsv<T>(this IEnumerable<T> items, string seperator = ",")
where T : class
{
var csvBuilder = new StringBuilder();
var properties = typeof(T).GetProperties();
foreach (T item in items)
{
string line = string.Join(seperator, properties.Select(p => p.GetValue(item, null).ToCsvValue()).ToArray());
csvBuilder.AppendLine(line);
}
return csvBuilder.ToString();
}
private static string ToCsvValue<T>(this T item)
{
return string.Format("{0}", HttpUtility.HtmlDecode(item.ToString()));
}
Any idea ?

Server cannot append header after HTTP headers have been sent

Below shown is my code after creating the CSV file, i want to download the file so i am using below code. but its throwing error " Server cannot append header after HTTP headers have been sent" at "Response.AddHeader("Content-disposition", "attachment; filename=" + fileCSV + "\"");" place. Its downloading but browser not redirecting to same page.
string[] header = { "Error Occurred On", "Controller Name", "Action Name", "Exception Occurred", "Stack Trace Description", "InnerException Occurred", "Stack Trace InnerException Occurred " };
The code:
DataTable dt = new DataTable();
for (int e = 0; e < header.Length; e++)
{
dt.Columns.Add(header[e], typeof(string));
}
StringBuilder sb = new StringBuilder();
IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().Select(column => column.ColumnName);
sb.AppendLine(string.Join(",", columnNames));
dt.DefaultView.RowFilter = "[Exception Occurred] LIKE '%" + keyword + "%'";
DataTable dtFilter = new DataTable();
dtFilter = dt.DefaultView.ToTable();
foreach (DataRow row in dtFilter.Rows)
{
IEnumerable<string> fields = row.ItemArray.Select(field => field.ToString());
sb.AppendLine(string.Join(",", fields));
}
System.IO.File.WriteAllText(fileCSV, sb.ToString());
byte[] bytes = Encoding.ASCII.GetBytes(sb.ToString());
if (bytes != null)
{
Response.Clear();
Response.ContentType = "text/csv";
//Response.AddHeader("Content-Length", bytes.Length.ToString());
Response.AddHeader("Content-disposition", "attachment; filename=" + fileCSV);
Response.BinaryWrite(bytes);
Response.Flush();
Response.End();
}
You cannot redirect after downloading the File, you are attempting to perform 2 actions where you can only do the first.
I suggest that you download the file in a new (popup) window and redirect the main page if required.
Edit:-
You could force the download by opening the file-download action using window.open.
Example:-
Download File
<script>
$(function() {
$('a.file-download').click(function() {
window.open($(this).data('file'));
});
});
</script>
In HTTP there is a single response for each request. So this error means that you've already send something to response.
Not sure you are still looking for an answer, but rather than Response.Flush & Response.End, try HttpContext.ApplicationInstance.CompleteRequest(). It has solved a lot of my problems when trying to write directly to request stream.
Some few minutes ago I had the the same problem with a file download. I want to share what worked for me.
The most important thing add:
Response.ClearHeaders();
in the begin of action result.
And, to be honest, I read it here https://www.codeproject.com/Questions/1038819/How-to-resolve-this-error-Server-cannot-append-hea

Categories

Resources