damaged pdf using ITextSharp and mvc - asp.net-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.

Related

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);
}
}
}

Export IEnumerable object To Docx File (MVC)

We are using Kendo Grid for listing and needs to export record with filter to doc/Docx file.
Does Any one has idea about how to export IEumarable object to Doc/Docx file.
Here Code Snippet:
public FileResult ExportToWord([DataSourceRequest]DataSourceRequest request)
{
IEnumerable rows = dtView.ToDataSourceResult(request).Data;
MemoryStream output = new MemoryStream();
workbook.Write(output);
return File(output.ToArray(), "application/vnd.ms-word", "GridExcelExport.doc");
}
Any Help ?
You can try below code
public ActionResult ExportData()
{
GridView gv = new GridView();
gv.DataSource = db.Studentrecord.ToList();
gv.DataBind();
Response.ClearContent();
Response.Buffer = true;
Response.AddHeader("content-disposition", "attachment; filename=Marklist.doc");
Response.ContentType = "application/vnd.ms-word ";
Response.Charset = string.Empty;
StringWriter sw = new StringWriter();
HtmlTextWriter htw = new HtmlTextWriter(sw);
gv.RenderControl(htw);
Response.Output.Write(sw.ToString());
Response.Flush();
Response.End();
return RedirectToAction("StudentDetails");
}

Using CSVHelper to output stream to browser

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();
}
}

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 :)

iTextSharp Storing barcodes as Elements - Null reference exception

Im working on a class that will render PDF documents on an intranet web server. The problem that I am having is that iTextSharp uses the PDFWriter to render the barcode images and I am trying to build a collection of IElements before I render the document. Is there a way to create a collection that incorporates barcode images or is there a better way to code this class?
Hopefully my code can explain it better. Thanks.
public class PDFDoc
{
public float Width { get; set; }
public float Height { get; set; }
private List<IElement> Elements { get; set;}
...
private MemoryStream ms = new MemoryStream();
private Document doc = new Document();
private PdfWriter writer;
private void initializeDocument()
{
doc = new Document(new Rectangle(Width, Height), marginLeft, marginRight, marginTop, marginBottom);
writer = PdfWriter.GetInstance(doc, ms);
Elements = new List<IElement>();
}
private FileStreamResult GenerateLabel(IEnumerable<IElement> elements)
{
doc.Open();
foreach (var e in elements)
{
doc.Add(e);
}
//doc.Add(GenerateBarcodeImage("123456")); //GenBC works here but not below
writer.CloseStream = false;
doc.Close();
byte[] byteinfo = ms.ToArray();
ms.Write(byteinfo, 0, byteinfo.Length);
ms.Position = 0;
ms.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(ms, "application/pdf");
}
private Image GenerateBarcodeImage(string code)
{
Barcode128 code128 = new Barcode128();
code128.Code = code;
code128.X = 1f;
return code128.CreateImageWithBarcode(new PdfContentByte(writer), null, null);
}
//Program
public FileStreamResult RenderLabel()
{
Elements.Add(new Paragraph("This is a test"));
Elements.Add(GenerateBarcodeImage("123456")); //This gives a null ref exception.
return GenerateLabel(Elements);
}
The problem with the code snippet is that you're trying to access the MemoryStream after calling Document.Close(). You can move the Close() call below you call ms.ToArray(), or do away with the MemoryStream entirely.
When you instantiate a PdfWriter, you can use any available Stream, so why not use Response.OutputStream:
List<IElement> Elements = new List<IElement>();
using (Document document = new Document()) {
PdfWriter writer = PdfWriter.GetInstance(
document, Response.OutputStream
);
document.Open();
PdfContentByte cb = writer.DirectContent;
for (int i = 0; i < 9; ++i) {
Barcode128 code128 = new Barcode128();
code128.Code = "code " + i.ToString();
Elements.Add(code128.CreateImageWithBarcode(cb, null, null));
}
for (int i = 0; i < Elements.Count; ++i) {
document.Add(new Paragraph("Barcode: " + i.ToString()));
document.Add(Elements[i]);
}
}

Resources