beaudetious
beaudetious

Reputation: 2416

How to return a URL Access generated PDF from a Web API method

I've seen a number of examples of returning a PDF (or other file type) using Web API from a file that was stored on disk. However, in my case, I'm attempting to generate the document on the fly using SSRS's URL Access. Here is an example of the URL:

https://reporting.mydomain.biz/_layouts/ReportServer/RSViewerPage.aspx?rv:RelativeReportUrl=%2fquest%2fQUEST%2520Reports%2fReview.rdl&rp%3aReview=220

I've tried a number of approaches but most of them were from way back in 2012 and relevant to ASP.Net MVC such as:

public async Task<FileStreamResult> GenerateReport()
{

    CredentialCache credentialCache = new CredentialCache();
    credentialCache.Add(new Uri("http://domainORipaddress"), "NTLM", new NetworkCredential(
        ConfigurationManager.AppSettings["username"],
        ConfigurationManager.AppSettings["password"]
    ));

    Stream report = null;
    using (var httpClient = new HttpClient(new HttpClientHandler { Credentials = credentialCache }))
    {

        httpClient.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
        report = await httpClient.GetStreamAsync("reportUrl");
    }

    var contentDisposition = new ContentDisposition
    {
        FileName = "Report.pdf",
        Inline = false
    };
    Response.AppendHeader("Content-Disposition", contentDisposition.ToString());
    return File(report, "application/pdf");    

    //or use
    //Response.AppendHeader("Content-Disposition", String.Format("attachment;filename=\"{0}\"", reportName));
    //return File(reportPath, MediaTypeNames.Application.Pdf);
}

This example code came from this website: http://webstackoflove.com/sql-server-reporting-service-with-asp-net-mvc/

Here's a similar question but based on ASP.NET: Reporting services: Get the PDF of a generated report

I tried to use this code above returning a HttpResponseMessage but I'm getting back a file with no data.

Here's what I'm using now and it's returning a file with 0 KB:

public HttpResponseMessage PrintQualityReview([FromUri] int reviewId)
{
    var reportUrl = String.Format("https://reporting.mydomain.biz/quest/_vti_bin/reportserver?https://reporting.mydomain.biz/quest/QUEST%20Reports/{0}Review.rdl&rs:Format=PDF&Review={1}",
    ConfigurationManager.AppSettings["ReportingServiceDatabase"],
    reviewId);

    CredentialCache credentialCache = new CredentialCache();
    credentialCache.Add(new Uri("https://reporting.mydomain.biz"), "NTLM", new NetworkCredential(
        ConfigurationManager.AppSettings["ReportingServiceAccount"],
        ConfigurationManager.AppSettings["ReportingServiceAccountPwd"]
    ));

    MemoryStream mStream = new MemoryStream();

    using (WebClient wc = new WebClient()) 
    {
        wc.Credentials = credentialCache;

        using (Stream stream = wc.OpenRead(reportUrl))
        {
            stream.CopyTo(mStream);
        }
    }            

    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StreamContent(mStream);
    response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
    response.Content.Headers.ContentDisposition.FileName = "Report.pdf";

    return response;
}

I'm wondering how to do his with ASP.Net Web API?

Upvotes: 0

Views: 5308

Answers (2)

beaudetious
beaudetious

Reputation: 2416

Unfortunately, simply adding content-length to the header didn't do the trick. The trick was to have the AngularJS $http service return an ArrayBuffer response.

Web API

        [System.Web.Http.HttpGet]
        [System.Web.Http.Route("api/print/qualityreview/{reviewId:int}")]
        public HttpResponseMessage PrintQualityReview([FromUri] int reviewId)
        {
            logger.Info("Printing quality review for QR Id {0}", reviewId);

            HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
            var reportUrl = String.Format("https://example.com/quest/_vti_bin/reportserver?https://example.com/quest/QUEST%20Reports/{0}Review.rdl&rs:Format=PDF&Review={1}",
                ConfigurationManager.AppSettings["ReportingServiceDatabase"],
                reviewId);            
            CredentialCache credentialCache = new CredentialCache();
            credentialCache.Add(new Uri("https://example.com"), "NTLM", new NetworkCredential(
                ConfigurationManager.AppSettings["ReportingServiceAccount"],
                ConfigurationManager.AppSettings["ReportingServiceAccountPwd"]
            ));


            using (WebClient webClient = new WebClient())
            {
                webClient.Credentials = credentialCache;
                var reportStream = webClient.OpenRead(reportUrl);

                using (MemoryStream memoryStream = new MemoryStream())
                {
                    reportStream.CopyTo(memoryStream);

                    response.Content = new ByteArrayContent(memoryStream.ToArray());  //new StreamContent(reportStream);
                    response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
                    response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
                    //response.Content.Headers.ContentLength = reportStream.Length;
                    response.Content.Headers.ContentDisposition.FileName = "QualityReview.pdf";
                    response.StatusCode = HttpStatusCode.OK;

                    return response;
                }
            }
        }
    }

AngularJS

In the controller:

/**
* Prints the quality review
*/
function printQualityReview() {
    questAPIService.printQualityReview(reviewId)
        .then(function (response) {
            var outputFilename = vm.reviewee.lastName + ', ' + vm.reviewee.firstName + ' ' + $filter('date')(vm.review.reviewDate, 'MMddyyyy') + '.pdf';
            var blob = new Blob([response.data], { type: 'application/pdf' });                    
            saveAs(blob, outputFilename);

        }, function (error) {
            $log.error('Printing failed: ', error);
            toastr.error('There was an error attempting to print the quality review.');
        });
}

The service call:

/**
* Prints a quality review
* 
* @param {number} reviewId Id of the quality review to print
* @returns {Promise} Promise including content and response data
*/
function printQualityReview(reviewId) {
    return $http({
        url: 'api/print/qualityReview/' + reviewId,
        method: 'GET',
        responseType: 'arraybuffer'
    });
}

The trick was to make sure to return a byte array in the response content and set the response type as ArrayBuffer when calling the API method.

Upvotes: 0

Nkosi
Nkosi

Reputation: 247451

The reason you are getting 0 bytes is because you are not setting the ContentLength of the response header.

Here is your updated code

public HttpResponseMessage PrintQualityReview([FromUri] int reviewId) {
        var reportUrl = String.Format("https://reporting.mydomain.biz/quest/_vti_bin/reportserver?https://reporting.mydomain.biz/quest/QUEST%20Reports/{0}Review.rdl&rs:Format=PDF&Review={1}",
            ConfigurationManager.AppSettings["ReportingServiceDatabase"],
            reviewId);

        CredentialCache credentialCache = new CredentialCache();
        credentialCache.Add(new Uri("https://reporting.mydomain.biz"), "NTLM", new NetworkCredential(
            ConfigurationManager.AppSettings["ReportingServiceAccount"],
            ConfigurationManager.AppSettings["ReportingServiceAccountPwd"]
        ));

        MemoryStream mStream = new MemoryStream();

        using (WebClient wc = new WebClient()) {
            wc.Credentials = credentialCache;

            using (Stream stream = wc.OpenRead(reportUrl)) {
                stream.CopyTo(mStream);
            }
        }

        var contentLength = mStream.Length; //content length for header
        var contentType = new MediaTypeHeaderValue("application/pdf");
        var contentDispositionValue = "attachment; filename=Report.pdf";

        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
        response.Content = new StreamContent(mStream);
        response.Content.Headers.ContentType = contentType;
        response.Content.Headers.ContentLength = contentLength;
        ContentDispositionHeaderValue contentDisposition = null;            
        if (ContentDispositionHeaderValue.TryParse(contentDispositionValue , out contentDisposition)) {
            response.Content.Headers.ContentDisposition = contentDisposition;
        }

        return response;
    }

Upvotes: 0

Related Questions