How to open generated PDF in MVC - ajax

I do AJAX call to generate PDF from crystal report.
Problem is , how to open PDF directly.
Below is my stuff. It looks, pdf is created but, it just return to view and not able to open PDF.
Please guide me.
Code:
public ActionResult CreatePDF(string paramValue)
{
DataTable dt = new DataTable();
DataColumn dc = new DataColumn("FieldName");
dt.Columns.Add(dcinitial);
DataRow dr = dt.NewRow();
dr[0]=paramValue;
dt.Rows.Add(dr);
ReportDocument oRpt = new ReportDocument();
string path = Server.MapPath("~/PDFDocs/crystalreport.rpt");
oRpt.Load(path);
oRpt.SetDataSource(dt);
MemoryStream oStream = new MemoryStream();
oStream = (MemoryStream)oRpt.ExportToStream(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat);
Response.Clear();
Response.Buffer = true;
Response.ContentType = "application/pdf";
string fileName = "Report";
Response.AppendHeader("content-disposition", "attachment; filename=" + fileName);
Response.BinaryWrite(oStream.ToArray());
Response.End();
return View();
}
Thanks

Don't write to the Response in the controller. Aside from coupling issues and testability, it's confusing the functionality of that controller action. This code is trying to both write the file to the response and return the view.
Instead, just return the file, which you can do directly from the stream:
return File(oStream, "application/pdf", fileName);
However...
I do AJAX call
That's going to complicate things a bit. Ideally this wouldn't be an AJAX call. This is because AJAX doesn't handle "files" in a way you might think. There are options, but it would probably be easier to make this a normal page-level request so the browser can handle the file natively.
Since it's returning a file and not a view, the browser won't unload the current page. (Unless it's configured to display the file instead of save it, though there isn't much you can do about that. The server-side code is properly suggesting that the browser save the file by providing a Content-Disposition header.)

Related

What is the alternative for HttpContext.Response.OutputStream to use in WebAPI's HttpResponseMessage

I'm writing a WebAPI for handling PDF documents. It was written in a ashx page earlier implementing IHttpHandler and getting the context using HttpContext. I'm now writing it using WebAPI. In WebAPI we have HttpResponseMessage. For HttpContext.Response.BinaryWrite we have new ByteArrayContent in HttpResponseMessage. But what is the alternative for HttpContext.Response.OutputStream in WebAPI? I need to have the alternative of OutputStram in WebAPI because im passing this OutputStream as a parameter to another dll.
Code in ashx:
SomeReport.PdfReport rpt = new SomeReport.PdfReport(docID);
rpt.CreateReport(context.Response.OutputStream);
Actually you can use any stream for example MemoryStream but result should be wrapped into StreamContent.
public HttpResponseMessage Get()
{
var response = Request.CreateResponse();
var outputStream = new MemoryStream();
//write data to output stream
//or passing it to somewhere
outputStream.WriteByte(83);
outputStream.Position = 0;
response.Content = new StreamContent(outputStream);
return response;
}
If you need direct writing to output stream, please consider using PushStreamContent. Example

Web API - Setting Response.Content with byte[] / MemoryStream Contents not working properly

My requirement is to use Web API to send across the network, a zip file (consisting a bunch of files in turn) which should not be written anywhere locally (not written anywhere on the server/client disk). For zipping, I am using DotNetZip - Ionic.Zip.dll
Code at Server:
public async Task<IHttpActionResult> GenerateZip(Dictionary<string, StringBuilder> fileList)
{
// fileList is actually a dictionary of “FileName”,”FileContent”
byte[] data;
using (ZipFile zip = new ZipFile())
{
foreach (var item in filelist.ToArray())
{
zip.AddEntry(item.Key, item.Value.ToString());
}
using (MemoryStream ms = new MemoryStream())
{
zip.Save(ms);
data = ms.ToArray();
}
}
var result = new HttpResponseMessage(HttpStatusCode.OK);
MemoryStream streams = new MemoryStream(data);
//, 0, data.Length-1, true, false);
streams.Position = 0;
//Encoding UTFEncode = new UTF8Encoding();
//string res = UTFEncode.GetString(data);
//result.Content = new StringContent(res, Encoding.UTF8, "application/zip");
<result.Content = new StreamContent(streams);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
//result.Content.Headers.ContentLength = data.Length;
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "test.zip";
return this.Ok(result);
}
The issue I am facing is that after the zip file downloaded at client end when modified as a test.bin has its stream contents (byte[] data in this example’s contents) missing. (I am getting back a test.zip file. When I change the file locally from test.zip to test.bin, I am seeing that the File’s contents as shown below. It does not contain the Response.Content values. P.S. I have also tried the MIME type “application/octet-stream” as well. No luck!)
Test.zip aka test.bin’s contents:
{"version":{"major":1,"minor":1,"build":-1,"revision":-1,"majorRevision":-1,"minorRevision":-1},
"content":{"headers":[{"key":"Content-Type","value":["application/zip"]},
{"key":"Content-Disposition","value":["attachment; filename=test.zip"]}]},
"statusCode":200,"reasonPhrase":"OK","headers":[],"isSuccessStatusCode":true}
Can someone please help me on how we can set result.Content with a MemoryStream object (I have seen example of “FileStream” at other places on google to set “result.Content” but I want to use MemoryStream object only!). I am highlighting this because I think the problem lies with setting the MemoryStream object to the result.Content (in order to properly save the streams content into the result.Content object)
P.S. I have also gone thru Uploading/Downloading Byte Arrays with AngularJS and ASP.NET Web API (and a bunch of other links) but it did not help me much… :(
Any help is greatly appreciated. Thanks a lot in advance :)
I got my issue solved!!
All I did was to change the Response Type to HttpResponseMessage and use "return result" in the last line rather than Ok(result) { i.e. HttpResponseMessage Type rather than OKNegiotatedContentResult Type)

Export To Excel from Asp Net Web API Controller

I am working on New web application which is Using Web API as Business Layer and Knock out Js as client side frame work to binding. I have a requirement like Pass the certain search criteria to Web API Controller and get the Data from DB and Create and Send the Excel/MS-Word file on the fly as a downloadable content.
I am new to both the Web API and Knock out, I am searching on the Net and get partial solution and I am looking here to get more optimal solution for this use case.
Below is my code:
Client:
function GetExcelFile() {
var $downloadForm = $("<form method='POST'>")
.attr("action", baseUrl + "api/FileHandler/GetExcelFileTest")
.attr("target", "_blank")
$("body").append($downloadForm);
$downloadForm.submit();
$downloadForm.remove();
}
On Button Click having this code snippet to create a form on the fly and Get response from Web API.
Web API Code:
[HttpPost]
public HttpResponseMessage GetExcelFileTest()
{
var response = new HttpResponseMessage();
//Create the file in Web App Physical Folder
string fileName = Guid.NewGuid().ToString() + ".xls";
string filePath = HttpContext.Current.Server.MapPath(String.Format("~/FileDownload/{0}", fileName));
StringBuilder fileContent = new StringBuilder();
//Get Data here
DataTable dt = GetData();
if (dt != null)
{
string str = string.Empty;
foreach (DataColumn dtcol in dt.Columns)
{
fileContent.Append(str + dtcol.ColumnName);
str = "\t";
}
fileContent.Append("\n");
foreach (DataRow dr in dt.Rows)
{
str = "";
for (int j = 0; j < dt.Columns.Count; j++)
{
fileContent.Append(str + Convert.ToString(dr[j]));
str = "\t";
}
fileContent.Append("\n");
}
}
// write the data into Excel file
using (StreamWriter sw = new StreamWriter(fileName.ToString(), false))
{
sw.Write(fileContent.ToString());
}
IFileProvider FileProvider = new FileProvider();
//Get the File Stream
FileStream fileStream = FileProvider.Open(filePath);
//Set response
response.Content = new StreamContent(fileStream);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = fileName;
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/ms-excel");
response.Content.Headers.ContentLength = fileStream.Length;
//Delete the file
//if(File.Exists(filePath))
//{
// File.Delete(filePath);
//}
return response;
}
Using this code I am able to download an Excel File. Still I have some more open questions to make this code optimal.
Q1) I need to Pass view model(Search Criteria) to API Controller Using the dynamically create form ? (OR) Any better ways to get Excel file from Web API.
Q2) I am sure it's not a good way to create Excel file in Physical folder and Get FileStream and send as a respone. How to do on the fly ? OR any other optimal ways.
Please suggest me to do better ways.. Thanks
Q1) You can quite easily pass the view-model, but it's also similarly easy to pull that information from the posted form.
Passing the view-model
If you want to pass the view-model to a WebAPI method then remember that said method must take as a parameter an object with the same properties. So if the object that you wish to post back always has the same properties then it's trivial to build a server-side class with the same properties and receive an instance of that class.
To post back this client-side object you can do something like this (uses jQuery, which I see you're already using):
$.ajax({
contentType: "application/json",
data: my-view-model.toJSON(),
type: "POST",
url: baseUrl + "api/FileHandler/GetExcelFileTest" });
I haven't attached any success or error handlers here because the JavaScript isn't concerned with the return, but you might wish to add some handlers in case an exception is thrown in your WebAPI method. I recommend doing that by adding the following to the above $.ajax() call:
statusCode: {
500: function(jqXhr, textStatus, errorThrown) {
},
[other HTTP error codes]
}
[Read the documentation for the $.ajax() call here.]
One additional tip here: when you call my-view-model.toJSON() (or self.toJSON(), if called from within your view-model) Knockout will first of all determine if your view-model contains a toJSON() method. If so, it will use this method; if not then it will call the browser's implementation of this function. However, the browser's implementation of this function will serialise everything, which can be particularly length if you have, for example, long select lists in your view-model. Therefore, if you wish only to send back a subset of the view-model's properties then define your own toJSON function on your view-model like so:
var toJSON = function() {
return {
Property1: ...,
Property2: ...
};
}
[Read more about converting a view-model to JSON here.]
Posting the form as-is
If you don't wish to expend the effort to do the view-model wiring then you can just post the form exactly like you have in your question. You can then retrieve the values from the form by using
Request.Form["my-field"];
Q2)
You're probably right in pointing out that it's not wise to create the Excel file in the physical folder. However, as far as I'm aware (interested if someone says otherwise) you'll have to use a 3rd-party library for this. Microsoft do offer an Office automation library but I have a suspicion that you also need Office to be installed at the same location.
Creating Excel spreadsheets dynamically is something I've done several times but for the actual creation I use Aspose.Cells, which requires a license. Although I do create a physical version and then delete it, I believe Aspose.Cells may allow you to create it as a stream. But take a look around, there are certainly other libraries which offer Excel automation.
Returning the File from the Server
Calling $.ajax({...}) alone won't allow you to present the user with a "Save as..." dialog. What I do in this situation - and this won't work if you wish to store the generated file only in memory (FileStream, for example) and not on the file system - is to respond to the $.ajax({...}) call with a filename for the generated file.
The next step is to direct the user towards that filename.
So I have something like this in my JavaScript:
$.ajax({
dataType: "json",
type: "GET", // you'll probably want POST in your case
url: ...,
success: function(response) {
if (response && response.Uri && response.Uri.length) {
window.location.href = [root URL] + response.Uri;
}
}
});
But don't be alarmed by this redirect. That window.location.href points directly to a folder on the server, no controller needed. Because the browser then receives a file it presents the "Save as..." dialog while remaining on the same webpage.

export to excel using existing excel template

I have used Export to Excel Functionality as below:
var table = getReport(param1, param2, param3);
Response.ClearContent();
Response.AddHeader("content-disposition", "attachment;filename=Report.xls");
Response.ContentType = "application/vnd.ms-excel";
System.Web.UI.WebControls.GridView grd = new System.Web.UI.WebControls.GridView();
grd.DataSource = table; // give datasource here
grd.DataBind();
StringWriter swr = new StringWriter();
HtmlTextWriter tw = new HtmlTextWriter(swr);
grd.RenderControl(tw);
Response.Write(swr.ToString());
Response.End();
return View();
where getReport returns data in table format.
Now this creates a new excel file but i want to use existing excel template to create excel output.
How to to add such template to project and how to bind data with it?
The approach you are using now is writing an HTML string to the Response and setting the ContentType to Excel in the Response headers. This does not create a true Excel file, it just forces the browser to launch the file in Excel, and Excel is able to handle HTML files. There is no way to add a template with this approach.
If you want to use an existing Excel file as a template, you will need to use an API that can create and modify real Excel files, such as OfficeWriter. Here is an example of how to bind your DataTable directly to a template file using OfficeWriter.
using SoftArtisans.OfficeWriter.ExcelWriter;
ExcelTemplate xlt = new ExcelTemplate();
//Open the template file
xlt.Open(pathToTemplateFile);
//Create a DataBindingProperties object
DataBindingProperties props = xlt.CreateDataBindingProperties();
// Call the BindData method, passing the DataTable and a name for the datasource
// which will be referenced in the "data markers" in the template
xlt.BindData(table, "DataSource1", props);
//Call the Process method, which binds the data and generates the output file
xlt.Process();
//Stream the file to the Response
xlt.Save(Response, "Report.xls", false);
Disclaimer: I work for SoftArtisans, the makers of OfficeWriter

MVC. Itextsharp write pdf to response

I am generating pdf using itexsharp.
I am creating MemoryStream, then when i am trying t write MemoryStream bytes in to response but no luck. When i am executing this code in my controller the pdf not coming in response. Memory stream is populaitng correctly i can see this in debugger, but for some reason this number of butes not coming in response.
Here is my code:
HttpContext.Current.Response.ContentType = "application/pdf";
...
using (Stream inputPdfStream = new FileStream(pdfFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (Stream outputPdfStream = new MemoryStream())
{
PdfReader reader = new PdfReader(inputPdfStream);
PdfStamper stamper = new PdfStamper(reader, outputPdfStream);
....
//try one
outputPdfStream.WriteTo(HttpContext.Current.Response.OutputStream); // NOT POPULATING Response
//try two
HttpContext.Current.Response.BinaryWrite(outputPdfStream.ToArray()); // NOT POPULATING Response Too
HttpContext.Current.Response.End();
}
May be some one have any ideas?
Could you not use
Response.ContentType = "application/pdf"
Response.AddHeader("Content-Type", "application/pdf")
Response.WriteFile(pdfFilePath)
Response.End()
You should use the FileContentResult Controller.File(byte[] content, string contentType) method:
public ActionResult GeneratePDF()
{
var outputStream = new MemoryStream(); // This will hold the pdf you want to send in the response
/*
* ... code here to create the pdf in the outputStrem
*/
return File(outputStream.ToArray(), "application/pdf");
}
Source: Building PDFs in Asp.Net MVC 2.
Probably the memorystream is still set at the position after the last written byte. It will write all bytes from the current position (which is none). If you do a outputPdfStream.Seek(0) it will set the position back to the first byte, and will write the contents of the whole stream to the response output.
Anyway, like Dean says, you should just use the Reponse.WriteFile method.

Resources