Good Evening ;
I have a problem that I am working on struts2 web application. I am dynamically creating a PDF using data base. i want to show it in a web page but I don`t know how I do it is any one can help me.
Thanks...
Action code:
public class PDFAction extends ActionSupport {
private InputStream inputStream;
public String getPDF(){
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
PdfWriter.getInstance(document, buffer);
document.open();
Paragraph p = new Paragraph();
p.add("INSTITUTO POLITÉCNICO NACIONAL, ESCUELA SUPERIOR DE CÓMPUTO, DIEGO A. RAMOS");
document.add(p);
document.close();
inputStream = new ByteArrayInputStream(buffer.toByteArray());
return SUCCESS;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
}
Struts.xml:
<action name="getPDF" class="action.PDFAction" method="getPDF">
<result name="success" type="stream">
<param name="inputName">inputStream</param>
<param name="contentType">application/pdf</param>
<param name="contentDisposition">filename="mypdf.pdf"</param>
<param name="bufferSize">2048</param>
</result>
</action>
Try it, it works like a charm, works perfect for me. If you are in doubt read more about stream result type that Struts 2 provides. The answer to this is so simple yet it was hard to get to it.
You can write the content using the input stream or best way is to create custom result type where you can set appropriate header and other things here is a link for some help
Struts2 Custom Result Type
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
PdfWriter.getInstance(document, buffer);
document.open();
////Do your stuff here
document.close();
DataOutput dataOutput = new DataOutputStream(response.getOutputStream());
byte[] bytes = buffer.toByteArray();
response.setContentLength(bytes.length);
for(int i = 0; i < bytes.length; i++)
{
dataOutput.writeByte(bytes[i]);
}
I m using iText for creating pdf. You can put this scriptlet in a jsp and call this jsp to show the pdf generated
Related
I have an MVC cross Web API app that has an ApiFileController, headed:
[Produces("application/json")]
[Route("api/File")]
public class ApiFileController : ApiBaseController
It has the following action method
[HttpPost("PostDir")]
[DisableRequestSizeLimit]
public async Task<IActionResult> PostDir(string serverPath)
{
using (var fileStream = new FileStream(serverPath, FileMode.Create, FileAccess.Write))
{
await Request.Body.CopyToAsync(fileStream);
}
return Ok();
}
that is supposed to received a zipfile containing a directory, and unzip it into the serverPath parameter. Yet when I try and post a file as follows:
sourcePath = Path.Combine("Temp", Guid.NewGuid() + ".zip");
System.IO.Compression.ZipFile.CreateFromDirectory(localPath, sourcePath, CompressionLevel.Fastest, true);
...
using (var fileStream = File.Open(sourcePath, FileMode.Open, FileAccess.Read))
{
using (var reader = new StreamReader(fileStream))
using (var content = new StreamContent(reader.BaseStream))
{
var uri = $"api/File/PostDir?serverPath={WebUtility.UrlEncode(serverPath)}";
var resp = await _client.PostAsync(uri, content);
resp.EnsureSuccessStatusCode();
}
}
I get a 404 - Not found. If I post a plain text file, as follows,
using (var fileStream = File.Open(localPath, FileMode.Open, FileAccess.Read))
{
using (var reader = new StreamReader(fileStream))
using (var content = new StreamContent(reader.BaseStream))
{
var uri = $"api/File/PostDir?serverPath={WebUtility.UrlEncode(serverPath)}";
var resp = await _client.PostAsync(uri, content);
resp.EnsureSuccessStatusCode();
}
}
where localPath points to a plain text file, the PostDir action is correctly invoked and properly saves the text file.
I am using HttpClient, in a wrapper class, to make the requests, and it is initialized in the wrapper's ctor as follows:
public ApiClient()
{
var baseAddress = ConfigurationManager.AppSettings["ApiUrl"];
_client = new HttpClient();
_client.BaseAddress = new Uri(baseAddress);
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.ConnectionClose = false;
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
I suspect that when posting the binary zip files, I am missing a content type header or such, but have no idea what I am missing or doing wrong. Could someone please help?
Don't take my code as gospel on how to upload a zip file, but it turns out the error was that I had only used the [DisableRequestSizeLimit] attribute on the action method, and that only disables Kestrel's request size limit. IIS still has a 30MB limit which I disabled by adding a web.config with the following:
<system.webServer>
<security>
<requestFiltering>
<!-- 1 GB -->
<requestLimits maxAllowedContentLength="1073741824" />
</requestFiltering>
</security>
</system.webServer>
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 generating pdf and displaying it in separate window/tab using the approach described in The BalusC Code: PDF handling.I need to display blockui ajax loader when i select the commandlink to display pdf.The pdf gets generated but the ajax loader image remains as it is.I need to manually refresh the page to hide it.Is there any way using which it can be hidden as soon as the pdf gets displayed.
My code snippet is as below
JSF page
<h:form id="subFrm">
<p:commandLink value="Download PDF" action="#{pdfBean.downloadPDF}"
onclick="blkUi.show()" oncomplete="blkUi.hide()" id="cmdLink"
ajax="false" />
<p:blockUI block="subFrm" trigger="cmdLink" widgetVar="blkUi">
processing...<br />
<p:graphicImage value="/images/ajaxLoader.gif" />
</p:blockUI>
</h:form>
snippet of Managed bean which is of request scope
#ManagedBean
#RequestScoped
public class PdfBean {
// Constants ----------------------------------------------------------------------------------
private static final int DEFAULT_BUFFER_SIZE = 10240; // 10KB.
// Actions ------------------------------------------------------------------------------------
public void downloadPDF() throws IOException {
// Prepare.
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
String filePath=externalContext.getRealPath("/pdf");
File file = new File(filePath, "modified.pdf");
BufferedInputStream input = null;
BufferedOutputStream output = null;
try {
// Open file.
input = new BufferedInputStream(new FileInputStream(file), DEFAULT_BUFFER_SIZE);
// Init servlet response.
response.reset();
response.setHeader("Content-Type", "application/pdf");
response.setHeader("Content-Length", String.valueOf(file.length()));
response.setHeader("Content-Disposition", "inline; filename=\"" + "modified.pdf" + "\"");
output = new BufferedOutputStream(response.getOutputStream(), DEFAULT_BUFFER_SIZE);
// Write file contents to response.
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);
}
// Finalize task.
output.flush();
} finally {
// Gently close streams.
close(output);
close(input);
}
// Inform JSF that it doesn't need to handle response.
// This is very important, otherwise you will get the following exception in the logs:
// java.lang.IllegalStateException: Cannot forward after response has been committed.
//externalContext.redirect(((HttpServletRequest)externalContext.getRequest()).getRequestURI());
facesContext.responseComplete();
/*FacesContext.getCurrentInstance().getExternalContext()
.redirect("index.xhtml");*/
}
// Helpers (can be refactored to public utility class) ----------------------------------------
private static void close(Closeable resource) {
if (resource != null) {
try {
resource.close();
} catch (IOException e) {
// Do your thing with the exception. Print it, log it or mail it. It may be useful to
// know that this will generally only be thrown when the client aborted the download.
e.printStackTrace();
}
}
}
}
You don't need to open the blockui explicitly. Just remove the code from your commandLink
onclick="blkUi.show()" oncomplete="blkUi.hide()"
remove above. The BlockUI will show up and hides itself.
In Struts2 Application I tried to have my Custome Result Type. but am getting no Effect, My JSP page image based action is not getting called.And No Exception am getting also.Please correct me where am doing wrong.
HTTPFox says 404 but am not getting anything in JAVA Console.
HTML :
<img src=" <s:url action='ExternalImageAction' />" />
XML :
<package name="externalImage_package" extends="struts-default">
<result-types>
<result-type name="myBytesResult" class="leo.struts.CustomeImageResult" />
</result-types>
<action name="ExternalImageAction" class="leo.struts.ExternalImageAction">
<result name="myImageResult" type="myBytesResult">
</result>
</action>
</package>
HTTPFOX :
00:18:06.762 0.044 432 1258 GET 404 text/html (NS_ERROR_FAILURE) http://localhost:8888/Struts2Whole/%3Cs:url%20action=%27ExternalImageAction%27%20/%3E
CustomeImageResult:
public void execute(ActionInvocation invocation) throws Exception {
ExternalImageAction action = (ExternalImageAction) invocation.getAction();
HttpServletResponse response = ServletActionContext.getResponse();
response.setContentType(action.getContentType());
response.getOutputStream().write(action.getImageInBytes());
response.getOutputStream().flush();
}
ExternalImageAction :
public String execute()
{
System.out.println("execute of the ExternalImageAction...........");
setContentType("jpg");
setImageInBytes(getFileBytes("C:/Users/Joseph.M/Desktop/ocwcd5.jpg"));
return "myImageResult";
}
public static byte[] getFileBytes(String filePath)
{
File file = new File(filePath);
System.out.println("file : "+file.getName());
byte[] b = new byte[(int) file.length()];
try {
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.read(b);
for (int i = 0; i < b.length; i++) {
System.out.print((char)b[i]);
}
fileInputStream.close();
} catch (FileNotFoundException e) {
System.out.println("File Not Found.");
e.printStackTrace();
}
catch (IOException e1) {
System.out.println("Error Reading The File.");
e1.printStackTrace();
}
System.out.println("byes of image size : "+b.length);
return b;
}
If you return something to the src attribute of an <img /> tag, it thinks it is an URL, try to open it and receives 404 Not Found.
Since you are not returning an URL, but the actual image in a byte array, you need to use a Data URI scheme as defined in RFC 2397.
Assuming your result only return the bytes, you should put the Data URI in the html, like described here: https://stackoverflow.com/a/20019398/1654265
Otherwise, you could return the complete Data URI (that must include the Mime Type) in the struts result itself, and keeping your current JSP unchanged.
Simply turn the byte[] to a Base64 String with Apache Commons's encodeBase64URLSafeString, append it to a String like data:image/jpeg;base64, and return that.
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();
}
}