I want to create a HttpResponse that streams a local file.
I want to use a MemoryStream, so that I can delete the file afterwards (well actually before returning the repsonse).
I always end up with an empty response although the stream seems to be valid.
Working with a FileStream in API Controller works, though.
public HttpResponseMessage GetExcelFile(Guid id)
{
// this model is needed to internally create an .xls file that represents this model
var exportModel = this.myService.GetExport(id);
// this approach does not work -> respone always empty although memory stream has content
// var stream = new MemoryStream();
// internally creates a .xls file (using lib) and returns its content as memory stream
// this.myService.ConvertToStream(exportModel, stream));
// this works fine
var stream = File.OpenRead(#"D:\test0815.xls");
var result = this.Request.CreateResponse(HttpStatusCode.OK);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentLength = stream.Length;
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"{exportModel.Name}-Sheet.xls"
};
return result;
}
this is my method that actually converts to memorystream:
private MemoryStream SaveToStream(MemoryStream stream)
{
using (FileStream source = File.Open(
#"D:\test0815.xls",
FileMode.Open))
{
Console.WriteLine("Source length: {0}", source.Length.ToString());
// Copy source to destination.
source.CopyTo(stream);
}
return stream;
}
I also tried writing to memory stream but this did not work either.
It seems that result.Content = new StreamContent(stream); is just not working with an memory stream.
Any ideas?
finally I found a working solution:
var memoryStream = new MemoryStream((int)fileStream.Length);
fileStream.CopyTo(memoryStream);
fileStream.Close();
memoryStream.Seek(0, SeekOrigin.Begin);
HttpContent content = new StreamContent(memoryStream);
var result = this.Request.CreateResponse(HttpStatusCode.OK);
result.Content =content;
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
Related
I have a controller action that creates a zip file and sends back to user for download. The problem is that the zip file gets created but it is empty. Somehow it's not writing the image files to the MemoryStream. I wonder what I am missing. If I write the zip file to the disk everything will work as expected, but I'd rather not save files to the disk if I can avoid it. This is what I have tried using dotnetzip:
public ActionResult DownloadGraphs()
{
var state = Session["State"];
using (ZipFile zip = new ZipFile())
{
if (state == "IA")
{
zip.AddFile(Server.MapPath("~/Content/DataVizByState/FallGraphs/Watermarked/Fall_IA.jpg"), "");
zip.AddFile(Server.MapPath("~/Content/DataVizByState/SpringGraphs/Watermarked/Spring_IA.jpg"), "");
}
MemoryStream output = new MemoryStream();
zip.Save(output);
output.Seek(0, SeekOrigin.Begin);
var fileName = state + "Graphs.zip";
return File(output, "application/zip", fileName);
}
}
This forces download in the view based on click of a button:
$('#graphDwnldBtn').click(function (evt) {
window.location = '#Url.Action("DownloadGraphs", "DataSharing")';
})
Do I need to use StreamWriter or Reader or something? This is the first time I have ever attempted something like this and it's been cobbled together by reading various stackoverflow posts...
Dumb mistakes: Session["State"] is an object, so the state variable was coming out as object instead of a string like I need it to be for my conditional statement to evaluate correctly. I cast state to a string to fix it. Fixed code:
public ActionResult DownloadGraphs()
{
var state = Session["State"].ToString();
using (ZipFile zip = new ZipFile())
{
if (state == "IA")
{
zip.AddFile(Server.MapPath("~/Content/DataVizByState/FallGraphs/Watermarked/Fall_IA.jpg"), "");
zip.AddFile(Server.MapPath("~/Content/DataVizByState/SpringGraphs/Watermarked/Spring_IA.jpg"), "");
}
MemoryStream output = new MemoryStream();
zip.Save(output);
output.Seek(0, SeekOrigin.Begin);
var fileName = state + "Graphs.zip";
return File(output, "application/zip", fileName);
}
}
I'm trying to export a list, but when i open the file download it just shows a bunch of characteres that don't make sense (kinda looks like machine language). I've looked at some codes here and all of them are similar to mine, what am I missing?
Here's my code:
The method I call:
[HttpGet]
public HttpResponseMessage Get()
{
HttpResponseMessage response;
response = Request.CreateResponse(HttpStatusCode.OK);
MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/ms-excel");
response.Content = new StreamContent(GetExcelSheet());
response.Content = response.Content;
response.Content.Headers.ContentType = mediaType;
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "PivotGrid_Orders.xls";
return response;
}
The method that format cells:
public MemoryStream GetExcelSheet()
{
using (var package = new ExcelPackage())
{
var worksheet = package.Workbook.Worksheets.Add("Orders");
//worksheet.Cells["A1"].LoadFromCollection()
worksheet.Cells["A1"].LoadFromCollection(Orders(), false);
package.Save();
var stream = new MemoryStream(package.GetAsByteArray()); //capacidade
return stream;
}
}
The list i've created to test:
public List<ExListModel> Orders()
{
List<ExListModel> lst = new List<ExListModel>();
orders.Add(new ExListModel{ Nome = "Developer"});
return lst;
}
As I didn't know EPPlus, I googled it, and its Github page states it produces Open XML excel files (.xlsx). You produce the file with an extension and mimetype of the old binary excel filetype. Change the contenttype to application/vnd.openxmlformats-officedocument.spreadsheetml.sheet and the filename extension to xlsx.
I have these two methods in my controller.I want to open the FilestreamResult pdf() that returns a file stream result.However, i am getting OutputStream is not available when a custom TextWriter is used error.I am using itextsharp for pdf.
Here is my code :
public FileStreamResult pdf()
{
MemoryStream workStream = new MemoryStream();
Document document = new Document();
PdfWriter.GetInstance(document, workStream).CloseStream = false;
List<Plant> plants = new List<Plant>();
foreach (var item in context.Plants)
{
plants.Add(item);
}
byte[] byteInfo = GeneratePdf(plants);
workStream.Write(byteInfo, 0, byteInfo.Length);
workStream.Position = 0;
return new FileStreamResult(workStream, "application/pdf");
}
and the Generate pdf method is
private static byte[] GeneratePdf(List<Plant> plants)
{
using (MemoryStream memoryStream = new MemoryStream())
{
using (var doc = new Document())
{
PdfWriter.GetInstance(doc, memoryStream);
doc.Open();
doc.SetMargins(120, 120, 270, 270);
BaseFont font = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, false);
Font normalFont = new Font(font, 12, Font.NORMAL, BaseColor.BLACK);
Paragraph pgTitle = new Paragraph();
pgTitle.Font = new Font(font, 20, Font.NORMAL, BaseColor.BLACK);
pgTitle.Add("American University of Beirut");
doc.Add(pgTitle);
Paragraph pgPlantTitle = new Paragraph();
pgPlantTitle.Font = new Font(font, 18, Font.NORMAL, BaseColor.BLACK);
pgPlantTitle.Add("Plant Description");
doc.Add(pgPlantTitle);
foreach (Plant p in plants)
{
Paragraph plantDisc = new Paragraph();
plantDisc.Font = new Font(font, 14, Font.NORMAL, BaseColor.BLACK);
plantDisc.Add(p.ScientificName);
plantDisc.Add(p.TypeOfPlants.ToString());
plantDisc.Add(p.PlantHeightRanges.ToString());
plantDisc.Add(p.PlantSpreadRanges.ToString());
plantDisc.Add(p.PlantShapes.ToString());
plantDisc.Add(p.NativeOrigin);
plantDisc.Add(p.Colors.ToString());
plantDisc.Add(p.Colors1.ToString());
plantDisc.Add(p.LightRequirements.ToString());
plantDisc.Add(p.WaterRequirements.ToString());
doc.Add(plantDisc);
doc.Add(new Paragraph(" "));
}
doc.Close();
memoryStream.Close();
return memoryStream.ToArray();
}
}
}
Any help?
You're using the Document and PdfWriter classes incorrectly in your first method. I'm going to throw some comments into that method to better explain what's going on.
public FileStreamResult pdf()
{
//Create a generic Stream for someone to write their bytes to
MemoryStream workStream = new MemoryStream();
//Create an iText Document helper object which is a friendly way to create new PDFs using things like tables and paragraphs.
//No where in the code below will this helper object be used so that's the first problem.
Document document = new Document();
//Bind our document helper and stream to a PdfWriter.
//This writer will _exclusively own_ the Stream from now on.
//If _anyone_ else writes to the stream (as you are doing below) it will break the PDF or possibly just throw an exception
PdfWriter.GetInstance(document, workStream).CloseStream = false;
//Business logic here unrelated to the problem
List<Plant> plants = new List<Plant>();
foreach (var item in context.Plants)
{
plants.Add(item);
}
//Create a byte array that represents a PDF. The GeneratePdf appears to be correct.
byte[] byteInfo = GeneratePdf(plants);
//Even though we declared above that we want our PdfWriter to have exclusive access to the Stream,
//ignore that and write our byte array to it.
workStream.Write(byteInfo, 0, byteInfo.Length);
//Rewind the stream
workStream.Position = 0;
return new FileStreamResult(workStream, "application/pdf");
}
Hopefully those comments make sense. Your GeneratePdf() method is what makes a PDF. Once you have a valid PDF, unless you want to modify it or inspect it you no longer have any need for iTextSharp. So your first method should be changed to something like the below. (I don't have VS available right now but this should probably compile except for a possible typo or two.)
//Business logic
List<Plant> plants = new List<Plant>();
foreach (var item in context.Plants)
{
plants.Add(item);
}
//Create our PDF
byte[] byteInfo = GeneratePdf(plants);
//Wrap the bytes in a Stream and return
using( var workStream = new MemoryStream( byteInfo ) )
{
return new FileStreamResult(workStream, "application/pdf");
}
The way you've described the Exception, am making an educated guess that your problem is in a view linking to your controller/action. For example if you have a View and are creating a hyperlink like the commented section:
#* remove comment to see Exception
<h2>Exception: "OutputStream is not available when a custom TextWriter is used."</h2>
<p>
This throws an Exception
</p>
*#
<h2>Correct!</h2>
<p>
<a href="#Url.Action("IndexPdf")" target='_blank'>This works!</a>
</p>
The exact Exception you describe is thrown:
System.Web.HttpException: OutputStream is not available when a custom
TextWriter is used.
So use Url.Action().
Aside from that, a few notes about the GeneratePdf() method in your code:
Remove the Close() calls on MemoryStream and Document, since they're both in using statements.
Move the MemoryStream call to ToArray() outside the Document using block. Otherwise the PDF result may be corrupted.
A shortened example based on your code:
private static byte[] GeneratePdf(List<Plant> plants)
{
using (MemoryStream memoryStream = new MemoryStream())
{
using (var doc = new Document())
{
PdfWriter.GetInstance(doc, memoryStream);
doc.Open();
doc.SetMargins(120, 120, 270, 270);
Paragraph pgTitle = new Paragraph("TEST");
doc.Add(pgTitle);
// initialize title, etc, here
// and iterate over plants here
}
// return **AFTER** Document is disposed
return memoryStream.ToArray();
}
}
And a few notes on your pdf() Action:
Maybe a typo or copy/paste error, but there's no reason for Document or PdfWriter.GetInstance()
If you return a less specific ActionResult instead of the FileStreamResult, you do not have to make an in-memory copy of the PDF. I.e. you can eliminate the MemoryStream, and instead call Controller.File(), since the first parameter is a byte array:
Another shortened example based on your code, this time for the controller action:
public ActionResult IndexPdf()
{
var plants = new List<Plant>();
// get your plants here
byte[] byteInfo = GeneratePdf(plants);
return File(byteInfo, "application/pdf");
}
I am currently assembling and displaying a PDF using RazorPDF in MVC4 and would like to save the PDF file to the file system at the same time I return the view.
The following line of code in the controller action is calling the view:
return new PdfResult(claims, "PDF");
I was able to finally write the pdf to the directory system by changing the code base of the RazorPDF render method. The Rendor method creates a PdfWriter object that is associated to the response stream:
// Associate output with response stream
var pdfWriter = PdfWriter.GetInstance(document, viewContext.HttpContext.Response.OutputStream);
pdfWriter.CloseStream = false;
The solution was to create another PdfWriter object that was associated to a FileStream object as illustrated below:
// Create the pdf file in the directory system
var fileStream = new FileStream(myPdfFilePath, FileMode.Create);
var pdfWriter2 = PdfWriter.GetInstance(document, fileStream);
I then closed the objects:
fileStream.Close();
pdfWriter.Close();
pdfWriter2.Close();
I had to essentially incorporate the PdfResult and PdfView classes of RazorPDF into my own project and significantly alter the code. The reason is because I also had to encorporate calls to an email class that sent the pdf to a user.
The full Render method is displayed below:
public void Render(ViewContext viewContext, TextWriter writer)
{
// generate view into string
var sb = new System.Text.StringBuilder();
TextWriter tw = new System.IO.StringWriter(sb);
myResult.View.Render(viewContext, tw);
var resultCache = sb.ToString();
// detect itext (or html) format of response
XmlParser parser;
using (var reader = GetXmlReader(resultCache))
{
while (reader.Read() && reader.NodeType != XmlNodeType.Element)
{
// no-op
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "itext")
parser = new XmlParser();
else
parser = new HtmlParser();
}
// Create a document processing context
var document = new Document();
document.Open();
// Associate output with response stream
var pdfWriter = PdfWriter.GetInstance(document, viewContext.HttpContext.Response.OutputStream);
pdfWriter.CloseStream = false;
// Create the pdf file in the directory system
var fileStream = new FileStream(myPdfFilePath, FileMode.Create);
var pdfWriter2 = PdfWriter.GetInstance(document, fileStream);
// this is as close as we can get to being "success" before writing output
// so set the content type now
viewContext.HttpContext.Response.ContentType = "application/pdf";
// parse memory through document into output
using (var reader = GetXmlReader(resultCache))
{
parser.Go(document, reader);
}
fileStream.Close();
// Send an email to the claimant
Thread.Sleep(100);
if (File.Exists(myPdfFilePath))
{
var subject = "PDF Documents";
var body = Config.GetContent(ContentParams.CLAIM_DOCUMENT_EMAIL_BODY_TEXT);
bool success;
string errorMessage;
Email.Send(myEmailAddress, subject, body, out success, out errorMessage, myPdfFilePath);
}
pdfWriter.Close();
pdfWriter2.Close();
}
It would be nice if this capability were somehow incorporated into the current RazorPDF project.
why not just get the stream via a web request to the url?
string razorPdfUrl="http://...";
var req = HttpWebRequest.Create(RazorPDFURL);
using (Stream pdfStream = req.GetResponse().GetResponseStream())
{
...
}
I'm trying to serve a txt file made from the database using an action. The action is the following:
public ActionResult ATxt()
{
var articulos = _articulosService.ObteTotsArticles();
return File(CatalegATxt.ATxt(articulos), "text/plain");
}
and the CatalegATxt class is:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using WebDibaelsaMVC.DTOs.Busqueda;
namespace WebDibaelsaMVC.TxtLib
{
public static class CatalegATxt
{
public static Stream ATxt(IEnumerable<ArticuloBusquedaDTO> articles)
{
var stream = new MemoryStream();
var streamWriter = new StreamWriter(stream, Encoding.UTF8);
foreach (ArticuloBusquedaDTO article in articles)
{
streamWriter.WriteLine(article.ToStringFix());
}
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
public static string ToStringFix(this ArticuloBusquedaDTO article)
{
string result = "";
result += article.CodigoArticulo.PadRight(10, ' ').Substring(0, 10);
result += article.EAN.Trim().PadLeft(13, '0').Substring(0, 13);
result += article.NombreArticulo.PadRight(100, ' ').Substring(0, 100);
result += article.Marca.PadRight(100, ' ').Substring(0, 100);
result += article.Familia.PadRight(50, ' ').Substring(0, 50);
result += article.PrecioCesion.ToStringFix();
result += article.PVP.ToStringFix();
return result;
}
private static string ToStringFix(this double numero)
{
var num = (int)Math.Round(numero * 100, 0);
string result = num.ToString().PadLeft(10, '0');
return result;
}
}
}
it just writes the file lines based on the stuff I got from the database. But when I look at the file it looks truncated. The file is about 8Mb. I also tried converting to byte[] before returning from ATxt with the same result.
Any idea?
Thanks,
Carles
Update: I also tried to serve XML from the same content and it also gets truncated. It doesn't get truncated on the data (I thought it might have been an EOF character in it) but it truncates in the middle of a label...
I was having the exact same problem. The text file would always be returned as truncated.
It crossed my mind that it might be a "flushing" problem, and indeed it was. The writer's buffer hasn't been flushed at the end of the operation - since there's no using block, or the Close() call - which would flush automatically.
You need to call:
streamWriter.Flush();
before MVC takes over the stream.
Here's how your method should look like:
public static Stream ATxt(IEnumerable<ArticuloBusquedaDTO> articles)
{
var stream = new MemoryStream();
var streamWriter = new StreamWriter(stream, Encoding.UTF8);
foreach (ArticuloBusquedaDTO article in articles)
{
streamWriter.WriteLine(article.ToStringFix());
}
// Flush the stream writer buffer
streamWriter.Flush();
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
Why are you using an ActionResult?
ASP.NET MVC 1 has a FileStreamResult for just what you are doing. It expects a Stream object, and returns it.
public FileStreamResult Test()
{
return new FileStreamResult(myMemoryStream, "text/plain");
}
Should work fine for what you want to do. No need to do any conversions.
In your case, just change your method to this:
public FileStreamResult ATxt()
{
var articulos = _articulosService.ObteTotsArticles();
return new FileStreamResult(CatalegATxt.ATxt(articulos), "text/plain");
}
You probably want to close the MemoryStream. It could be getting truncated because it expects more data still. Or to make things even simpler, try something like this:
public static byte[] ATxt(IEnumerable<ArticuloBusquedaDTO> articles)
{
using(var stream = new MemoryStream())
{
var streamWriter = new StreamWriter(stream, Encoding.UTF8);
foreach (ArticuloBusquedaDTO article in articles)
{
streamWriter.WriteLine(article.ToStringFix());
}
return stream.ToArray();
}
}