Convert LINQ result to CSV or Excel - c#

Using a LINQ query I need to export to Excel when a WebApi method is called. I have built the LINQ query that will return the correct data, now I need it to export to .csv or Excel file format.
I have tried using MemoryStream and StreamWriter but I think I am just chasing my tail now.
[HttpGet]
[Route("Download")]
public Task<IActionResult> Download(int memberId)
{
var results = (from violations in _db.tblMappViolations
where violations.MemberID == memberId
select new IncomingViolations
{
Contact = violations.ContactName,
Address = violations.str_Address,
City = violations.str_City,
State = violations.str_State,
Zip = violations.str_Zipcode,
Country = violations.str_Country,
Phone = violations.str_Phone,
Email = violations.str_Email,
Website = violations.str_WebSite,
}).FirstOrDefault();
MemoryStream stream = new MemoryStream(results);
StreamWriter writer = new StreamWriter(stream);
writer.Flush();
stream.Position = 0;
FileStreamResult response = File(stream, "application/octet-stream");
response.FileDownloadName = "violations.csv";
return response;
}

Here is how you can send CSV file to the user from server.
string attachment = "attachment; filename=MyCsvLol.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.AddHeader("Pragma", "public");
var sb = new StringBuilder();
// Add your data into stringbuilder
sb.Append(results.Contact);
sb.Append(results.Address);
sb.Append(results.City);
// and so on
HttpContext.Current.Response.Write(sb.ToString());
For Sending it from API
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
// Write Your data here in writer
writer.Write("Hello, World!");
writer.Flush();
stream.Position = 0;
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "Export.csv" };
return result;
Update:-
public HttpResponseMessage Download()
{
var results = (from violations in _db.tblMappViolations
where violations.MemberID == memberId
select new IncomingViolations
{
Contact = violations.ContactName,
Address = violations.str_Address,
City = violations.str_City,
State = violations.str_State,
Zip = violations.str_Zipcode,
Country = violations.str_Country,
Phone = violations.str_Phone,
Email = violations.str_Email,
Website = violations.str_WebSite,
});
var sb = new StringBuilder();
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
foreach(var tempResult in results)
{
sb.Append(tempResult.Contact+",");
sb.Append(tempResult.Address+",");
sb.Append(tempResult.City+",");
sb.Append(tempResult.State+",");
sb.Append(tempResult.Zip+",");
sb.Append(tempResult.Country+",");
sb.Append(tempResult.Phone+",");
sb.Append(tempResult.Email+",");
sb.Append(tempResult.Website+",");
sb.Append(Enviroment.NewLine);
}
writer.Write(sb.ToString());
writer.Flush();
stream.Position = 0;
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "Export.csv" };
return result;
}

First, to reuse the code in other areas, always create helper classes.
I adopted this method of converting list into a stream with headers as property names, if you want a file from this, essentially, I would just add another step to this:
STEP 1:
public static Stream ConvertToCSVStream<T>(IEnumerable<T> objects)
{
Type itemType = typeof(T);
var properties = itemType.GetProperties();
var mStream = new MemoryStream();
StreamWriter sWriter = new StreamWriter(mStream);
var values = objects.Select(o =>
{
return string.Join(",", properties.Select(p =>
{
var value = p.GetValue(o).ToString();
if (!Regex.IsMatch(value, "[,\"\\r\\n]"))
{
return value;
}
value = value.Replace("\"", "\"\"");
return string.Format("\"{0}\"", value);
})) + sWriter.NewLine;
});
var valuesInStrings = values.Aggregate((current, next) => current + next);
try
{
sWriter.Write(string.Join(",", properties.Select(x => x.Name.Replace("_", " "))) + sWriter.NewLine);
sWriter.Write(valuesInStrings);
}
catch (Exception e)
{
mStream.Close();
throw e;
}
sWriter.Flush();
mStream.Position = 0;
return mStream;
}
if your data is text, just convert it directly to a file result but if not, you must convert it to binary array and write it to stream, refer to this article for converting it to binary data, in our case, for csv, you could just use the FileStream result that you've implemented in a separate method:
STEP 2:
public FileStreamResult CreateFile(MemoryStream mStream, string path, string name)
{
//set values, names, content type, etc
//return filestream
}
or any other method you find better.

Save your result in a DataTable and then just use this
XLWorkbook workbook = new XLWorkbook();
DataTable table = GetYourTable();
workbook.Worksheets.Add(table);
And you should definitely use stream writer for this if you know which file its going to write to from the start, else stream reader and then stream writer.

Related

iText 7 return Pdf from Asp.Net WebApi

So I am alreay searching about this the entire day and I just don't know how to get it working. Basically I want to create a PDF server side with iText 7 in my ASP.Net WebApi. Very straightforward and easy Pdf creation:
[HttpGet]
public HttpResponseMessage CreateLieferschein()
{
MemoryStream stream = new MemoryStream();
PdfWriter writer = new PdfWriter(stream);
var pdf = new PdfDocument(writer);
var document = new Document(pdf);
document.Add(new Paragraph("Hello World!"));
}
From here on I have no idea on how to return the file from the controller. I would really appreciate any help since I am just lost.
I haven't tried this out, just doing it freehand so bear with me, but I think you can get the idea of what's going on here.
public HttpResponseMessage CreateLieferschein() {
// Create the itext pdf
MemoryStream stream = new MemoryStream();
PdfWriter writer = new PdfWriter(stream);
var pdf = new PdfDocument(writer);
var document = new Document(pdf);
document.Add(new Paragraph("Hello World!"));
document.Close(); // don't forget to close or the doc will be corrupt! ;)
// Load the mem stream into a StreamContent
HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
// Prep the response with headers, filenames, etc.
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "WebApi2GeneratedFile.pdf"
};
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
ResponseMessageResult responseMessageResult = ResponseMessage(httpResponseMessage);
// Cross your fingers...
return responseMessageResult;
}
try this :
[HttpGet]
public HttpResponseMessage CreateLieferschein()
{
Document doc = new Document(iTextSharp.text.PageSize.LETTER, 10, 10, 42, 35);
byte[] buffer;
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.BadRequest);
using(MemoryStream stream = new MemoryStream())
{
using(PdfWriter wri = PdfWriter.GetInstance(doc, mem))
{
PdfWriter writer = new PdfWriter(stream);
var pdf = new PdfDocument(writer);
var document = new Document(pdf);
document.Add(new Paragraph("Hello World!"));
}
buffer = stream.ToArray();
var contentLength = buffer.Length;
var statuscode = HttpStatusCode.OK;
response = Request.CreateResponse(statuscode);
response.Content = new StreamContent(new MemoryStream(buffer));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
response.Content.Headers.ContentLength = contentLength;
ContentDispositionHeaderValue contentDisposition = null;
if (ContentDispositionHeaderValue.TryParse("inline; filename=" + document.Name + ".pdf", out contentDisposition)) {
response.Content.Headers.ContentDisposition = contentDisposition;
}
} else {
var statuscode = HttpStatusCode.NotFound;
var message = String.Format("Unable to find file. file \"{0}\" may not exist.", docid);
var responseData = responseDataFactory.CreateWithOnlyMetadata(statuscode, message);
response = Request.CreateResponse((HttpStatusCode)responseData.meta.code, responseData);
}
return response;
}

How to convert GZipStream to HttpContent?

I have written a Web API code to return a zip file. But I am not able to convert the GZipStream content to HttpContent. I get the following error:
cannot implicitly convert type 'system.io.compression.GZipStream' to
'System.Net.Http.HttpContent'
Where did I go wrong?
My WebApi Code:
var content =
new GZipStream(memStream, CompressionMode.Compress);
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
httpResponseMessage.Content = content;
httpResponseMessage.Content.Headers.Add("x-filename", document.FileName);
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
httpResponseMessage.Content.Headers.ContentDisposition.FileName = "xyz.zip";
httpResponseMessage.StatusCode = HttpStatusCode.OK;
I got a solution using Zip archive and I have created a static class to push the memory stream into a zip archive as below,
var pushStreamContent = ZipStreamContent.Create("MultipleDocument.zip", memStList);
ZipStreamContent class,
public static class ZipStreamContent
{
public static PushStreamContent Create(string fileName, List<MemoryStream> msList)
{
var content = new PushStreamContent((outputStream, httpContent, transportContext) =>
{
using (var zip = new ZipArchive(outputStream, ZipArchiveMode.Create, leaveOpen: false))
{
msList[0].Position = 0;
var createenter = zip.CreateEntry("xyz.jpg", CompressionLevel.Optimal);
using (var s = createenter.Open())
{
msList[0].CopyTo(s);
}
}
});
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
content.Headers.ContentDisposition.FileName = fileName;
return content;
}
}
I have simplified the above given GipStreamContent static class like following, It's working well, so I hope It will help all others.
CloudBlockBlob blob = null;
//azure storage connection
var container = GetBlobClient(tenantInfo);
//directory reference
var directory = container.GetDirectoryReference(
string.Format(DirectoryNameConfigValue, tenantInfo.TenantId.ToString(), documentList[0].ProjectId));
var pushStreamContent = new PushStreamContent(async (outputStream, httpContent, transportContext) =>
{
//zip the multiple files
using (var zipEntry = new ZipArchive(outputStream, ZipArchiveMode.Create, leaveOpen: false))
{
for (int docId = 0; docId < documentList.Count; docId++)
{
blob = directory.GetBlockBlobReference(DocumentNameConfigValue + documentList[docId].DocumentId);
if (!blob.Exists()) continue;
MemoryStream memStream = new MemoryStream();
await blob.DownloadToStreamAsync(memStream);
memStream.Position = 0;
var createEntry = zipEntry.CreateEntry(documentList[docId].FileName, CompressionLevel.Fastest);
using (var stream = createEntry.Open())
{
memStream.CopyTo(stream);
}
}
}
});

Unable to download Excel file when call from Postman

I am trying to download Excel file using web API but I am unable to download file in postman where as I am able to download Excel file when I enter URL in browser though while opening file I get warning message like below :
When i hit endpoint using POSTMAN then file get corrupted and it is showing junk characters.
Code :
protected virtual byte[] ExportToXlsx<T>(IEnumerable<T> itemsToExport)
{
using (var stream = new MemoryStream())
{
using (var xlPackage = new ExcelPackage())
{
// get handles to the worksheets
var worksheet = xlPackage.Workbook.Worksheets.Add(typeof(T).Name);
//create Headers and format them
var manager = new PropertyManager<T>(itemsToExport.First());
manager.WriteCaption(worksheet, SetCaptionStyle);
var row = 2;
foreach (var items in itemsToExport)
{
manager.CurrentObject = items;
manager.WriteToXlsx(worksheet, row++, false);
}
xlPackage.Save();
}
return stream.ToArray();
}
}
private readonly IServiceContext ctx;
public void Download(string guid)
{
var bytes = ExportToXlsx(list);
ctx.reqobj.HttpContext.Response.Headers.Add("Content-Disposition", "attachment; filename=\"demo.xlsx\"");
ctx.reqobj.HttpContext.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
ctx.reqobj.HttpContext.Response.Body.Write(bytes, 0, bytes.Length);
}
Note : I am using OfficeOpenXml for Excel file creation.
I will appreciate any help.
Update :
Try using "Send and download" instead of "Send"
https://www.getpostman.com/docs/v6/postman/sending_api_requests/responses
Postman doesn't download any file just return you the data that the server or your service provides. i have a project that download an excel to with the OpenXML here is an example with which you can guide with some styles to.
[HttpGet]
public void DownloadTable(int id)
{
List<Employee> all = db.Employees.Where(x => x.ManagerId == id).ToList();
String file = "Example.xlsx";
String path = Path.Combine(HttpContext.Current.Server.MapPath("~/App_Data"), file);
List<string[]> headerRow = new List<string[]>() { new string[] { "EmployeeId", "Name", "Shift", "Timestamp" } };
string headerRange = "A2:" + Char.ConvertFromUtf32(headerRow[0].Length + 64) + "2";
ExcelPackage excel = new ExcelPackage();
excel.Workbook.Worksheets.Add("Employees");
var page = excel.Workbook.Worksheets["Employees"];
page.Cells["A1:D1"].Merge = true;
page.Cells["A1:D1"].Value = "Supervisor: " + all.FirstOrDefault().Manager + " - " + id;
page.Cells["A1:D1"].Style.Font.Bold = true;
page.Cells[headerRange].LoadFromArrays(headerRow);
int z = 3;
foreach (Reporte r in all)
{
page.Cells["A" + z].Value = r.Id;
page.Cells["B" + z].Value = r.Name;
page.Cells["C" + z].Value = r.Shift;
page.Cells["D" + z].Value = r.Timestamp;
z++;
}
page.Cells["D3:D" + z].Style.Numberformat.Format = "dddd dd MMMM YYYY";
page.Cells["A2:D2"].AutoFilter = true;
page.Cells["A1:D" + z].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
page.Cells["A1:D" + z].Style.VerticalAlignment = ExcelVerticalAlignment.Center;
page.Cells["A2:D" + z].AutoFitColumns();
page.Cells["A1:D1"].Style.Fill.PatternType = ExcelFillStyle.Solid;
page.Cells["A1:D1"].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(1, 183, 222, 232));
FileInfo excelFile = new FileInfo(path);
excel.SaveAs(excelFile);
System.Web.HttpResponse response = System.Web.HttpContext.Current.Response;
response.ClearContent();
response.Clear();
response.ContentType = "text/plain";
response.AddHeader("Content-Disposition",
"attachment; filename=" + file + ";");
response.TransmitFile(path);
response.Flush();
response.End();
File.Delete(path);
}
The stream needs to be passed to the package.
Right now nothing is being given to the package,
//...
using (var xlPackage = new ExcelPackage())
//...
So nothing is being saved to the stream, which is why the error is shown when trying to open the file.
There is no need to convert the memory stream to an array. Return the stream and pass that along for the response.
protected virtual Stream ExportToXlsx<T>(IEnumerable<T> itemsToExport) {
var stream = new MemoryStream();
using (var xlPackage = new ExcelPackage(stream)) { //<<< pass stream
// get handles to the worksheets
var worksheet = xlPackage.Workbook.Worksheets.Add(typeof(T).Name);
//create Headers and format them
var manager = new PropertyManager<T>(itemsToExport.First());
manager.WriteCaption(worksheet, SetCaptionStyle);
var row = 2;
foreach (var items in itemsToExport) {
manager.CurrentObject = items;
manager.WriteToXlsx(worksheet, row++, false);
}
xlPackage.Save();
}
return stream;
}
A controller action to return the file would look like this
public IActionResult Download(string guid) {
//...get list
var file = ExportToXlsx(list);
var contentType = "application/vnd.openxmlformats";
var fileName = "demo.xlsx";
return File(file, contentType, fileName); //returns a FileStreamResult
}
It was indicated in comments that the above is done in a support method.
Using the same approach
private readonly IServiceContext ctx;
//...
public void Download(string guid) {
//...get list
using(var fileStream = ExportToXlsx(list)) {
if (fileStream.CanSeek && fileStream.Position != 0) {
fileStream.Seek(0, SeekOrigin.Begin);
}
var contentType = "application/vnd.openxmlformats";
var fileName = "demo.xlsx";
var response = ctx.reqobj.HttpContext.Response;
response.Headers.Add("Content-Disposition", $"attachment; filename=\"{fileName}\"");
response.Headers.Add("Content-Length", fileStream.Length.ToString());
response.ContentType = contentType;
fileStream.CopyTo(response.Body);
}
}
the generated file is copied over to the body of the response.
As for postman, the tool is simply showing the content return in the response. It does not try to download the actual file as an attachment.

How to generate a zip file within the HttpResponseMessage from within an Api Controller [duplicate]

I have a web service that I can call and save the returned csv file. Everything seems to be working OK. What I am now interested in doing is returning multiple CSV files for the user to download. What is the proper way to handle this? I'm guessing I need a way to package them up (zip? perhaps)?
[HttpPost]
[Route("OutputTemplate")]
public HttpResponseMessage OutputTemplate()
{
HttpResponseMessage msg = new HttpResponseMessage();
string body = this.Request.Content.ReadAsStringAsync().Result;
try
{
string contents = DoStuff(body) // get contents based on body
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(contents);
writer.Flush();
stream.Position = 0;
msg.StatusCode = HttpStatusCode.OK;
msg.Content = new StreamContent(stream);
msg.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
msg.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "fileexport"
};
return msg;
}
...
}
Using the following model to abstract file name and content
public class FileModel {
public string FileName { get; set; }
public byte[] FileContent { get; set; }
}
The following extension was derived to compress the file content
public static class ZipArchiveExtensions {
public static Stream Compress(this IEnumerable<FileModel> files) {
if (files.Any()) {
var ms = new MemoryStream();
using(var archive = new ZipArchive(
stream: ms,
mode: ZipArchiveMode.Create,
leaveOpen: true
)){
foreach (var file in files) {
var entry = archive.add(file);
}
}
ms.Position = 0;
return ms;
}
return null;
}
private static ZipArchiveEntry add(this ZipArchive archive, FileModel file) {
var entry = archive.CreateEntry(file.FileName, CompressionLevel.Fastest);
using (var stream = entry.Open()) {
stream.Write(file.FileContent, 0, file.FileContent.Length);
}
return entry;
}
}
With that in place, the example API controller action could look something like this.
public class ExampleApiController : ApiController {
public async Task<IHttpActionResult> OutputTemplate() {
IHttpActionResult result = BadRequest();
var body = await Request.Content.ReadAsStreamAsync();
List<FileModel> files = DoSomething(body);
if (files.Count > 1) {
//compress the files.
var archiveStream = files.Compress();
var content = new StreamContent(archiveStream);
var response = Request.CreateResponse(System.Net.HttpStatusCode.OK);
response.Content = content;
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {
FileName = "fileexport.zip"
};
result = ResponseMessage(response);
} else if (files.Count == 1) {
//return the single file
var fileName = files[0].FileName; //"fileexport.csv"
var content = new ByteArrayContent(files[0].FileContent);
var response = Request.CreateResponse(System.Net.HttpStatusCode.OK);
response.Content = content;
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {
FileName = fileName
};
result = ResponseMessage(response);
}
return result;
}
private List<FileModel> DoSomething(System.IO.Stream body) {
//...TODO: implement file models
throw new NotImplementedException();
}
}

Web API HttpResponseMessage content return is incomplete

I would like to return a csv generated from my database using Web API 5.0
It is working fine except that the csv returned is truncated.
I assume the issue is on the MemoryBuffer management, but I can't find where it is.
My code (solved):
IEnumerable<MasterObsTrip> masterTripList = _obsvMasterRepo.GetObsTrips(vesselName, dateYear, port, obsvCode, obsvTripCode, obsvProgCode, lastModifiedDateYear, lastModifiedBy, statusCode);
IList<MasterObsTripModel> masterTripModelList = new List<MasterObsTripModel>();
foreach (MasterObsTrip trip in masterTripList)
masterTripModelList.Add(new MasterObsTripModel(trip));
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
CsvFileDescription outputFileDescription = new CsvFileDescription
{
SeparatorChar = ',', // comma delimited
FirstLineHasColumnNames = true, // no column names in first record
FileCultureName = "nl-NL" // use formats used in The Netherlands
};
CsvContext cc = new CsvContext();
cc.Write(masterTripModelList,writer,outputFileDescription);
writer.Flush();
stream.Position = 0;
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "ObserverTripList.csv";
stream.Flush();
return response;
Thanks
I would try doing writer.Flush() just before you reset the stream.Position = 0
Also, if you often have a need for CSV content I would also suggest creating yourself a CsvContent class.
public class CsvContent<T> : HttpContent
{
private readonly MemoryStream _stream = new MemoryStream();
public CsvContent(CsvFileDescription outputFileDescription, string filename, IEnumerable<T> data)
{
var cc = new CsvContext();
var writer = new StreamWriter(_stream);
cc.Write(data, writer, outputFileDescription);
writer.Flush();
_stream.Position = 0;
Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
Headers.ContentDisposition.FileName = filename;
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
return _stream.CopyToAsync(stream);
}
protected override bool TryComputeLength(out long length)
{
length = _stream.Length;
return true;
}
}
Then your controller action reduces to...
IEnumerable<MasterObsTrip> masterTripList = _obsvMasterRepo.GetObsTrips(vesselName, dateYear, port, obsvCode, obsvTripCode, obsvProgCode, lastModifiedDateYear, lastModifiedBy, statusCode);
IList<MasterObsTripModel> masterTripModelList = new List<MasterObsTripModel>();
foreach (MasterObsTrip trip in masterTripList)
masterTripModelList.Add(new MasterObsTripModel(trip));
CsvFileDescription outputFileDescription = new CsvFileDescription
{
SeparatorChar = ',', // comma delimited
FirstLineHasColumnNames = true, // no column names in first record
FileCultureName = "nl-NL" // use formats used in The Netherlands
};
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK) {
Content = new CsvContent<MasterObsTripModel> (outputFileDescription,
"ObserverTripList.csv",
masterTripModelList);
}
return response;
This could be simplified further by including the creation of the CsvFileDescription inside the CsvContent class.

Categories