0

I based some code in an ASP.NET MVC app to generate and download a PDF file on some working code from an ASP.NET Web API app that downloads Excel files.

In the working (Web API/Excel) code, when I create this html (link) on the page:

builder.Append(string.Format("<a href=\"api/deliveryperformance/excel/{0}/{1}/{2}\">{3}</a>", unit, startDateYYYYMMDD, endDateYYYYMMDD, fileBaseName));

...and the user clicks it, this Controller is called:

[Route("{unit}/{begindate}/{enddate}")]
public HttpResponseMessage Get(string unit, string begindate, string enddate)
{
    byte[] excelContents;

    string selectStmt = "SELECT BinaryData FROM ReportsGenerated WHERE FileBaseName = @fileBaseName";
    string fbn = string.Format("deliveryperformance/{0}/{1}/{2}", unit, begindate, enddate);
    using (SqlConnection connection = new SqlConnection(PlatypusWebReportsConstsAndUtils.CPSConnStr))
    using (SqlCommand cmdSelect = new SqlCommand(selectStmt, connection))
    {
        cmdSelect.Parameters.Add("@fileBaseName", SqlDbType.VarChar).Value = fbn;
        connection.Open();
        excelContents = (byte[])cmdSelect.ExecuteScalar();
        connection.Close();
    }

    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(excelContents)
    };
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
    {
        FileName = string.Format("{0}.xlsx", fbn)
    };
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

    return result;
}

...which successfully downloads an Excel file to the user's machine.

So, I've got code that is as-similar-as-possible in my MVC app to generate and download a PDF file like so:

internal static HttpResponseMessage GeneratePDFOfReportFutures()
{
    HttpResponseMessage result = null;
    futureReports = GetAllFutureReports();
    try
    {
        using (var ms = new MemoryStream())
        {
            using (var doc = new Document(PageSize.A4.Rotate(), 25, 25, 25, 25))
            {
                using (PdfWriter.GetInstance(doc, ms))
                {
                    //Open the document for writing                            
                    doc.Open();

                    . . .
                    doc.Add(titleTable);

                    . . .
                    doc.Add(tblHeadings);
                    . . .   
                    doc.Add(tblRow);
                    } // foreach
                    doc.Close();
                    var bytes = ms.ToArray();
                    result = new HttpResponseMessage(HttpStatusCode.OK)
                    {
                        Content = new ByteArrayContent(bytes)
                    };
                    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                    {
                        FileName = string.Format("{0}.pdf", "test")
                    };
                    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                    return result;
                } // pdfWriter
            } // doc
        } // memoryStream               
    } // try
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    return result;
} // GeneratePDFOfReportFutures

...which I call from the client via an Ajax call:

$("#btnViewList").click(function () {
    $.ajax({
        type: "GET",
        url: '@Url.Action("GeneratePDF", "GenerateScheduledRptsPDF")',
        success: function () {
            alert('success from btnViewList');
        },
        error: function () {
            alert('error in btnViewList');
        }
    });
});

After working through some issues detailed here I am to the point where the Controller method runs without throwing an exception (and I see the success msg ("success from btnViewList")); however, no (PDF) file is downloaded/written to the hard disk.

Why not?

Community
  • 1
  • 1
B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862
  • 2
    When you use ajax to make a request to the URL, nothing is downloaded, that's just not how it works at all, you have to redirect the browser to that URL for the content-disposition headers to work, and start a download in the browser, you can't do that with ajax, and you can't do "silent" downloads, the user is always prompted, it's a security thing. – adeneo May 09 '16 at 22:45
  • Makes sense; not saying I'm thrilled beyond belief, but it makes sense. – B. Clay Shannon-B. Crow Raven May 09 '16 at 22:49
  • However, come to think of it, no, it doesn't make a lot of sense, because in my Web API app, they click a link and the download is made; in this case, it's the same thing - they click a button to download the file. The difference is only between clicking a link in the ASP.NET Web API app, which takes them to the matching Controller method, and clicking a button in the ASP.NET MVC app, which has jQuery behind it to make an AJAX call when the button is clicked. I don't see how that is any less/more safe than the Web API click. – B. Clay Shannon-B. Crow Raven May 09 '16 at 22:57
  • 2
    When you do an ajax call, you don't see the content, it's returned in the callback. The XMLHttpRequest is made in the background, and doesn't affect the browser, so the headers from the server won't start a download in the browser in the same way as actually visiting the URL would. – adeneo May 09 '16 at 23:10
  • 1
    If the user initiates the download with an action, such as a visiting an URL with a proper content-disposition, or clicking an anchor with the HTML5 download attribute etc, a download will start in the browser, and the user will be prompted in some manner that a download is happening. You can get the data with an ajax call, and then trigger a download, and somehow get the binary pdf data in the file etc. but it's just so much easier to redirect to the URL directly, as it would be exactly the same as the ajax mess. – adeneo May 09 '16 at 23:11
  • There is no URL. I have only one page. – B. Clay Shannon-B. Crow Raven May 09 '16 at 23:26
  • 1
    @StephenMuecke - while the link to the _duplicate_ answer does show how to send a file via ajax, it's an _outdated_ workaround. To quote from the first sentence of that accepted answer: **You can't directly return a file for download via an AJAX call**. [That's **definitely not** true](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data). – kuujinbo May 11 '16 at 18:10
  • 1
    [A simple Gist](https://gist.github.com/kuujinbo/779005c26f35d55a0d0f6460aa22444e) of the answer I was going to post, specific to your situation - Razor view, WebAPI, and jQuery. – kuujinbo May 12 '16 at 04:05

0 Answers0