Using CSVHelper to output stream to browser - asp.net-mvc

I'm trying to use CSVHelper to generate a CSV file and send it back to a browser, so the user can select a save location and filename and save the data.
The website is MVC based. Here' the jQuery button code I'm using to make the call (data is some serialised Json representation of a DTO list):
$.ajax({
type: "POST",
url: unity.baseUrl + "common/ExportPayments",
data: data
});
Here's the controller code:
[HttpPost]
public FileStreamResult ExportPayments()
{
MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms);
CsvWriter writer = new CsvWriter(sw);
List<Payment_dto> pd = _commonService.GetPayments();
foreach (var record in pd)
{
writer.WriteRecord(record);
}
sw.Flush();
return new FileStreamResult(ms, "text/csv");
}
Which seems to achieve precisely nothing - invoking the method steps into the correct bit of code but the response is empty, let alone offering the user a file dialog to save the data. I've stepped through this code, and it brings back data from the service, writes it, and throws no errors. So what am I doing wrong?
EDIT: Returning this ...
return File(ms.GetBuffer(), "text/csv", "export.csv");
... gives me a response, consisting of the csv-formatted data that I'm expecting. But the browser still doesn't seem to know what to do with it - no download option is offered to the user.

Try below code:
public FileStreamResult ExportPayments()
{
var result = WriteCsvToMemory(_commonService.GetPayments()());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
public byte[] WriteCsvToMemory(IEnumerable<Payment_dto> records)
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(records);
streamWriter.Flush();
return memoryStream.ToArray();
}
}
Update
Below is how to pass a complex type model to an action method which is using GET HTTP method. I don't prefer this approach, it just gives you an idea there is an approach to achieve this.
Model
public class Data
{
public int Id { get; set; }
public string Value { get; set; }
public static string Serialize(Data data)
{
var serializer = new JavaScriptSerializer();
return serializer.Serialize(data);
}
public static Data Deserialize(string data)
{
var serializer = new JavaScriptSerializer();
return serializer.Deserialize<Data>(data);
}
}
Action:
[HttpGet]
public FileStreamResult ExportPayments(string model)
{
//Deserialize model here
var result = WriteCsvToMemory(GetPayments());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
View:
#{
    var data = new Data()
    {
        Id = 1,
        Value = "This is test"
    };
}
#Html.ActionLink("Export", "ExportPayments", new { model = Data.Serialize(data) })

ASP.NET Core solution:
var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8); // No 'using' around this as it closes the underlying stream. StreamWriter.Dispose() is only really important when you're dealing with actual files anyhow.
using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture, true)) // Note the last argument being set to 'true'
csvWriter.WriteRecords(...);
streamWriter.Flush(); // Perhaps not necessary, but CsvWriter's documentation does not mention whether the underlying stream gets flushed or not
memoryStream.Position = 0;
Response.Headers["Content-Disposition"] = "attachment; filename=somename.csv";
return File(memoryStream, "text/csv");

Try in the controller:
HttpContext.Response.AddHeader("content-disposition", "attachment; filename=payments.csv");

Could also user dynamic keyword for converting any data
Code from #Lin
public FileStreamResult ExportPayments()
{
var result = WriteCsvToMemory(_commonService.GetPayments()());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
public byte[] WriteCsvToMemory(dynamic records)
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(records);
streamWriter.Flush();
return memoryStream.ToArray();
}
}

Related

How to insert HTML into Response body using .NET Core Middleware

I've been trying to cobble up some middleware that will allow me to measure the processing time on a request. This example gave me a good starting point, but I've run into trouble.
In the code below, I am able to measure the process time and insert it in a div (using HTML Agility Pack). However, the original contents of the page get duplicated. I think I'm doing something incorrectly with the context.Response.Body property in UpdateHtml(), but cannot figure out what it is. (I made some comments in the code.) If you see anything that looks incorrect, could you please let me know?
Thanks.
public class ResponseMeasurementMiddleware
{
private readonly RequestDelegate _next;
public ResponseMeasurementMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var watch = new Stopwatch();
watch.Start();
context.Response.OnStarting(async () =>
{
var responseTime = watch.ElapsedMilliseconds;
var newContent = string.Empty;
var existingBody = context.Response.Body;
string updatedHtml = await UpdateHtml(responseTime, context);
await context.Response.WriteAsync(updatedHtml);
});
await _next.Invoke(context);
}
private async Task<string> UpdateHtml(long responseTime, HttpContext context)
{
var newContent = string.Empty;
var existingBody = context.Response.Body;
string updatedHtml = "";
//I think I'm doing something incorrectly in this using...
using (var newBody = new MemoryStream())
{
context.Response.Body = newBody;
await _next(context);
context.Response.Body = existingBody;
newBody.Position = 0;
newContent = await new StreamReader(newBody).ReadToEndAsync();
updatedHtml = CreateDataNode(newContent, responseTime);
}
return updatedHtml;
}
private string CreateDataNode(string originalHtml, long responseTime)
{
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(originalHtml);
HtmlNode testNode = HtmlNode.CreateNode($"<div><h2>Inserted using Html Agility Pack: Response Time: {responseTime.ToString()} ms.</h2><div>");
var htmlBody = htmlDoc.DocumentNode.SelectSingleNode("//body");
htmlBody.InsertBefore(testNode, htmlBody.FirstChild);
string rawHtml = htmlDoc.DocumentNode.OuterHtml; //using this results in a page that displays my inserted HTML correctly, but duplicates the original page content.
//rawHtml = "some text"; uncommenting this results in a page with the correct format: this text, followed by the original contents of the page
return rawHtml;
}
}
For duplicated html, it is caused by await _next(context); in UpdateHtml which will invoke the rest middlware like MVC to handle the requests and response.
Withtout await _next(context);, you should not modify the Reponse body in context.Response.OnStarting.
For a workaround, I would suggest you place the ResponseMeasurementMiddleware as the first middleware and then calculate the time like
public class ResponseMeasurementMiddleware
{
private readonly RequestDelegate _next;
public ResponseMeasurementMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var originalBody = context.Response.Body;
var newBody = new MemoryStream();
context.Response.Body = newBody;
var watch = new Stopwatch();
long responseTime = 0;
watch.Start();
await _next(context);
//// read the new body
// read the new body
responseTime = watch.ElapsedMilliseconds;
newBody.Position = 0;
var newContent = await new StreamReader(newBody).ReadToEndAsync();
// calculate the updated html
var updatedHtml = CreateDataNode(newContent, responseTime);
// set the body = updated html
var updatedStream = GenerateStreamFromString(updatedHtml);
await updatedStream.CopyToAsync(originalBody);
context.Response.Body = originalBody;
}
public static Stream GenerateStreamFromString(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
private string CreateDataNode(string originalHtml, long responseTime)
{
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(originalHtml);
HtmlNode testNode = HtmlNode.CreateNode($"<div><h2>Inserted using Html Agility Pack: Response Time: {responseTime.ToString()} ms.</h2><div>");
var htmlBody = htmlDoc.DocumentNode.SelectSingleNode("//body");
htmlBody.InsertBefore(testNode, htmlBody.FirstChild);
string rawHtml = htmlDoc.DocumentNode.OuterHtml; //using this results in a page that displays my inserted HTML correctly, but duplicates the original page content.
//rawHtml = "some text"; uncommenting this results in a page with the correct format: this text, followed by the original contents of the page
return rawHtml;
}
}
And register ResponseMeasurementMiddleware like
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<ResponseMeasurementMiddleware>();
//rest middlwares
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
For this way app.UseMiddleware<ResponseMeasurementMiddleware>();, the action will be last opertion before sending the response, and then processing time would be suitable for processing time.

HttpContent does not contain a definition for ReadAsMultipartAsync

I'm using VS2015 to create a WebApi project. Specifically, I'm trying to write a simple web service to upload files using POST.
I have done this before using previous versions of Visual Studio, but VS2015 utilizes ASP.NET 5 and MVC 6 which apparently has some major api changes.
Before, I could use something like this:
Request.Content.ReadAsMultipartAsync().Result.Contents
BTW, this is what every site I googled recommends. But my copy of VS2015 gives me the following compile time error message:
'HttpContent does not contain a definition for ReadAsMultipartAsync'
The overall project structure for WebApi's seem to be revamped in VS2015. At this point I have no idea what's wrong (whether it's a code issue or a VS2015 issue). I've spent the last couple of days trying to get this resolved. Can anyone provide insight as to what I'm doing wrong?
Below is my controller in it's entirety:
[Route("api/[controller]")]
public class UploadController : Controller
{
private readonly IApplicationEnvironment appEnvironment;
public UploadController(IApplicationEnvironment appEnvironment)
{
this.appEnvironment = appEnvironment;
}
// POST api/values
[HttpPost]
public void Post(HttpRequestMessage request)
{
string logFolder = "logs";
string fileName = "uploader.log";
string logFile = Path.Combine(this.appEnvironment.ApplicationBasePath, logFolder, fileName);
string directory = Path.GetDirectoryName(logFile);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
using (Stream requestStream = GetStreamFromUploadedFile(request))
{
using (Stream fileStream = System.IO.File.Create(logFile))
{
try
{
requestStream.CopyTo(fileStream);
}
finally
{
// No longer supported?
//fileStream.Close();
//requestStream.Close();
}
}
}
}
private static Stream GetStreamFromUploadedFile(HttpRequestMessage request)
{
IEnumerable<HttpContent> parts = null;
Task.Factory
.StartNew(() => parts = request.Content.ReadAsMultipartAsync().Result.Contents,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default)
.Wait();
Stream stream = null;
Task.Factory
.StartNew(() => stream = parts.First().ReadAsStreamAsync().Result,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default)
.Wait();
return stream;
}
}
Following is an example of how you can do file uploads in ASP.NET 5/MVC 6:
https://github.com/aspnet/Mvc/blob/dev/test/WebSites/ModelBindingWebSite/Controllers/FileUploadController.cs#L16
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Http;
using Microsoft.Net.Http.Headers;
using ModelBindingWebSite.Models;
namespace ModelBindingWebSite.Controllers
{
public class FileUploadController : Controller
{
public FileDetails UploadSingle(IFormFile file)
{
FileDetails fileDetails;
using (var reader = new StreamReader(file.OpenReadStream()))
{
var fileContent = reader.ReadToEnd();
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
}
return fileDetails;
}
public FileDetails[] UploadMultiple(IEnumerable<IFormFile> files)
{
var fileDetailsList = new List<FileDetails>();
foreach (var file in files)
{
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
using (var reader = new StreamReader(file.OpenReadStream()))
{
var fileContent = reader.ReadToEnd();
var fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
fileDetailsList.Add(fileDetails);
}
}
return fileDetailsList.ToArray();
}
public IDictionary<string, IList<FileDetails>> UploadMultipleList(IEnumerable<IFormFile> filelist1,
IEnumerable<IFormFile> filelist2)
{
var fileDetailsDict = new Dictionary<string, IList<FileDetails>>
{
{ "filelist1", new List<FileDetails>() },
{ "filelist2", new List<FileDetails>() }
};
var fileDetailsList = new List<FileDetails>();
foreach (var file in filelist1.Concat(filelist2))
{
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
using (var reader = new StreamReader(file.OpenReadStream()))
{
var fileContent = reader.ReadToEnd();
var fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
fileDetailsDict[parsedContentDisposition.Name].Add(fileDetails);
}
}
return fileDetailsDict;
}
public KeyValuePair<string, FileDetails> UploadModelWithFile(Book book)
{
var file = book.File;
var reader = new StreamReader(file.OpenReadStream());
var fileContent = reader.ReadToEnd();
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
var fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
return new KeyValuePair<string, FileDetails>(book.Name, fileDetails);
}
}
}

How do I to pass value my ViewBag for View in mvc

I have a method which convert my html in a string for export to PDF
only I need convert my html (View) to a string in order to export to PDF
Code:
public string RenderViewToString(Controller controller, string viewName, object viewData)
{
var renderedView = new StringBuilder();
using (var responseWriter = new StringWriter(renderedView))
{
var fakeResponse = new HttpResponse(responseWriter);
var fakeContext = new HttpContext(HttpContext.Current.Request, fakeResponse);
var fakeControllerContext = new ControllerContext(new HttpContextWrapper(fakeContext), controller.ControllerContext.RouteData, controller.ControllerContext.Controller);
var oldContext = HttpContext.Current;
HttpContext.Current = fakeContext;
using (var viewPage = new ViewPage())
{
var html = new HtmlHelper(CreateViewContext(responseWriter, fakeControllerContext), viewPage);
html.RenderPartial(viewName, viewData);
HttpContext.Current = oldContext;
}
}
return renderedView.ToString();
}
But the value is not going to view my ViewBag:
ViewBag.Email = usuario.strUsuarioEmail;
ViewBag.Nome = usuario.strUsuarioNome;
Code Export PDF:
public ActionResult EventoVisualizarPDF()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(ConfigurationManager.AppSettings["UrlAPI"]);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var id = Session["intCodigoGrupoUsuario"];
var intUsuarioId = Session["intUsuarioId"];
string url = "";
url = "api/usuario/GetBuscaUsuario/" + intUsuarioId;
HttpResponseMessage resposta = client.GetAsync(url).Result;
if (resposta.IsSuccessStatusCode)
{
var usuario = resposta.Content.ReadAsAsync<Usuario>().Result;
ViewBag.Email = usuario.strUsuarioEmail;
ViewBag.Nome = usuario.strUsuarioNome;
}
url = "api/evento/GetEventoByUsuario/" + id;
HttpResponseMessage response = client.GetAsync(url).Result;
if (response.IsSuccessStatusCode)
{
var eventos = response.Content.ReadAsAsync<IEnumerable<Evento>>().Result;
return this.ViewPdf("Customer report", "RelatorioEventoPDF", eventos.OrderBy(m => m.strEventoCodigo));
}
else
{
string msg = response.IsSuccessStatusCode.ToString();
throw new Exception(msg);
}
}
how do to retrieve the value from my ViewBag in view?
If you're familiar with blob storage in Azure, you can think of the ViewBag as the same abstract type of data store.
I'm assuming that you want to output these values so they can be consumed by your View -> PDF conversion method, so all you need on your view is :
<label id="vbEmail">#ViewBag.Email</label>
<label id="vbNome">#ViewBag.Nome</label>
That will output the values you've set in the controller as HTML.

Using memorystream and DotNetZip in MVC gives "Cannot access a closed Stream"

I'm trying to create a zipfile in a MVC method using the DotNetZip components.
Here is my code:
public FileResult DownloadImagefilesAsZip()
{
using (var memoryStream = new MemoryStream())
{
using (var zip = new ZipFile())
{
zip.AddDirectory(Server.MapPath("/Images/"));
zip.Save(memoryStream);
return File(memoryStream, "gzip", "images.zip");
}
}
}
When I run it I get a "Cannot access a closed Stream" error, and I'm not sure why.
Don't dispose the MemoryStream, the FileStreamResult will take care once it has finished writing it to the response:
public ActionResult DownloadImagefilesAsZip()
{
var memoryStream = new MemoryStream();
using (var zip = new ZipFile())
{
zip.AddDirectory(Server.MapPath("~/Images"));
zip.Save(memoryStream);
return File(memoryStream, "application/gzip", "images.zip");
}
}
By the way I would recommend you writing a custom action result to handle this instead of writing plumbing code inside your controller action. Not only that you will get a reusable action result but bear in mind that your code is hugely inefficient => you are performing the ZIP operation inside the memory and thus loading the whole ~/images directory content + the zip file in memory. If you have many users and lots of files inside this directory you will very quickly run out of memory.
A much more efficient solution is to write directly to the response stream:
public class ZipResult : ActionResult
{
public string Path { get; private set; }
public string Filename { get; private set; }
public ZipResult(string path, string filename)
{
Path = path;
Filename = filename;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var response = context.HttpContext.Response;
response.ContentType = "application/gzip";
using (var zip = new ZipFile())
{
zip.AddDirectory(Path);
zip.Save(response.OutputStream);
var cd = new ContentDisposition
{
FileName = Filename,
Inline = false
};
response.Headers.Add("Content-Disposition", cd.ToString());
}
}
}
and then:
public ActionResult DownloadImagefilesAsZip()
{
return new ZipResult(Server.MapPath("~/Images"), "images.zip");
}
Couldn't comment.
Darin's answer is great! Still received a memory exception though so had to add response.BufferOutput = false; and because of that had to move content-disposition code higher.
So you have:
...
var response = context.HttpContext.Response;
response.ContentType = "application/zip";
response.BufferOutput = false;
var cd = new ContentDisposition
{
FileName = ZipFilename,
Inline = false
};
response.Headers.Add("Content-Disposition", cd.ToString());
using (var zip = new ZipFile())
{
...
Just in case it wasn't obvious :)

damaged pdf using ITextSharp and mvc

I am trying to generate a pdf out of an MVC3 webpage. I've viewed all the usual tutorials, but as is often the case when one is in a hurry and doesn't really know what one is doing, I'm making a dog's breakfast of it.
When I click the action link on the view to generate the pdf, the file appears to be created, but when I try to open it, I get the ever so helpful message from Adobe Reader that "... the file is damaged and cannot be repaired".
Where have I gone wrong?
public FileStreamResult PDFGenerator()
{
Stream fileStream = GeneratePDF();
HttpContext.Response.AddHeader("content-disposition", "attachment; filename=form.pdf");
return new FileStreamResult(fileStream, "application/pdf");
}
private Stream GeneratePDF()
{
MemoryStream ms = new MemoryStream();
Document doc = new Document();
PdfWriter writer = PdfWriter.GetInstance(doc, ms);
doc.Open();
doc.Add(new Paragraph("Hello"));
ms.Position = 0;
ms.Flush();
writer.Flush();
return ms;
}
You must close the document. Try like this:
public ActionResult PDFGenerator()
{
var doc = new Document();
using (var stream = new MemoryStream())
{
var writer = PdfWriter.GetInstance(doc, stream);
doc.Open();
doc.Add(new Paragraph("Hello"));
doc.Close();
return File(stream.ToArray(), "application/pdf", "test.pdf");
}
}
But that's ugly. I would recommend you a more MVCish approach which consists in writing a custom ActionResult. As an additional advantage of this is that your controller actions will be more easier to unit test in isolation:
public class PdfResult : FileResult
{
public PdfResult(): base("application/pdf")
{ }
public PdfResult(string contentType): base(contentType)
{ }
protected override void WriteFile(HttpResponseBase response)
{
var cd = new ContentDisposition
{
Inline = false,
FileName = "test.pdf"
};
response.AppendHeader("Content-Disposition", cd.ToString());
var doc = new Document();
var writer = PdfWriter.GetInstance(doc, response.OutputStream);
doc.Open();
doc.Add(new Paragraph("Hello"));
doc.Close();
}
}
and then in your controller action:
public ActionResult PDFGenerator()
{
return new PdfResult();
}
Of course this can be taken a step further and have this PdfResult take a view model as constructor argument and generate the PDF based on some properties on this view model:
public ActionResult PDFGenerator()
{
MyViewModel model = ...
return new PdfResult(model);
}
Now things are beginning to look nice.

Resources