Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return a PDF from a Web API application

I have a Web API project that is running on a server. It is supposed to return PDFs from two different kinds of sources: an actual portable document file (PDF), and a base64 string stored in a database. The trouble I'm having is sending the document back to a client MVC application. The rest of this is the details on everything that's happened and that I've already tried.

I have written code that successfully translates those two formats into C# code and then (back) to PDF form. I have successfully transferred a byte array that was supposed to represent one of those documents, but I can't get it to display in browser (in a new tab by itself). I always get some kind of "cannot be displayed" error.

Recently, I made a way to view the documents on the server side to make sure I can at least get it to do that. It gets the document into the code and creates a FileStreamResult with it that it then returns as an (implicit cast) ActionResult. I got that to return to a server side MVC controller and threw it into a simple return (no view) that displays the PDF just fine in the browser. However, trying to simply go straight to the Web API function simply returns what looks like a JSON representation of a FileStreamResult.

When I try to get that to return properly to my client MVC application, it tells me that "_buffer" can't be directly set. Some error message to the effect that some of the properties being returned and thrown into an object are private and can't be accessed.

The aforementioned byte-array representation of the PDF, when translated to a base64 string, doesn't seem to have the same number of characters as the "_buffer" string returned in the JSON by a FileStreamResult. It's missing about 26k 'A's at the end.

Any ideas about how to get this PDF to return correctly? I can provide code if necessary, but there has to be some known way to return a PDF from a server-side Web API application to a client-side MVC application and display it as a web page in a browser.

P.S. I do know that the "client-side" application isn't technically on the client side. It will also be a server application, but that shouldn't matter in this case. Relative to the Web API server, my MVC application is "client-side".

Code For getting pdf:

private System.Web.Mvc.ActionResult GetPDF()
{
    int bufferSize = 100;
    int startIndex = 0;
    long retval;
    byte[] buffer = new byte[bufferSize];
    MemoryStream stream = new MemoryStream();
    SqlCommand command;
    SqlConnection sqlca;
    SqlDataReader reader;

    using (sqlca = new SqlConnection(CONNECTION_STRING))
    {
        command = new SqlCommand((LINQ_TO_GET_FILE).ToString(), sqlca);
        sqlca.Open();
        reader = command.ExecuteReader(CommandBehavior.SequentialAccess);
        try
        {
            while (reader.Read())
            {
                do
                {
                    retval = reader.GetBytes(0, startIndex, buffer, 0, bufferSize);
                    stream.Write(buffer, 0, bufferSize);
                    startIndex += bufferSize;
                } while (retval == bufferSize);
            }
        }
        finally
        {
            reader.Close();
            sqlca.Close();
        }
    }
    stream.Position = 0;
    System.Web.Mvc.FileStreamResult fsr = new System.Web.Mvc.FileStreamResult(stream, "application/pdf");
    return fsr;
}

API Function that gets from GetPDF:

    [AcceptVerbs("GET","POST")]
    public System.Web.Mvc.ActionResult getPdf()
    {
        System.Web.Mvc.ActionResult retVal = GetPDF();
        return retVal;
    }

For displaying PDF server-side:

public ActionResult getChart()
{
    return new PDFController().GetPDF();
}

The code in the MVC application has changed a lot over time. The way it is right now, it doesn't get to the stage where it tries to display in browser. It gets an error before that.

public async Task<ActionResult> get_pdf(args,keys)
{
    JObject jObj;
    StringBuilder argumentsSB = new StringBuilder();
    if (args.Length != 0)
    {
        argumentsSB.Append("?");
        argumentsSB.Append(keys[0]);
        argumentsSB.Append("=");
        argumentsSB.Append(args[0]);
        for (int i = 1; i < args.Length; i += 1)
        {
            argumentsSB.Append("&");
            argumentsSB.Append(keys[i]);
            argumentsSB.Append("=");
            argumentsSB.Append(args[i]);
        }
    }
    else
    {
        argumentsSB.Append("");
    }
    var arguments = argumentsSB.ToString();
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        var response = await client.GetAsync(URL_OF_SERVER+"api/pdf/getPdf/" + arguments).ConfigureAwait(false);
        jObj = (JObject)JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);
    }
    return jObj.ToObject<ActionResult>();
}

The JSON I get from running the method directly from the Web API controller is:

{
    "FileStream":{
        "_buffer":"JVBER...NjdENEUxAA...AA==",
        "_origin":0,
        "_position":0,
        "_length":45600,
        "_capacity":65536,
        "_expandable":true,
        "_writable":true,
        "_exposable":true,
        "_isOpen":true,
        "__identity":null},
    "ContentType":"application/pdf",
    "FileDownloadName":""
}

I shortened "_buffer" because it's ridiculously long. I currently get the error message below on the return line of get_pdf(args,keys)

Exception Details: Newtonsoft.Json.JsonSerializationException: Could not create an instance of type System.Web.Mvc.ActionResult. Type is an interface or abstract class and cannot be instantiated. Path 'FileStream'.

Back when I used to get a blank pdf reader (the reader was blank. no file), I used this code:

public async Task<ActionResult> get_pdf(args,keys)
{
    byte[] retArr;
    StringBuilder argumentsSB = new StringBuilder();
    if (args.Length != 0)
    {
        argumentsSB.Append("?");
        argumentsSB.Append(keys[0]);
        argumentsSB.Append("=");
        argumentsSB.Append(args[0]);
        for (int i = 1; i < args.Length; i += 1)
        {
            argumentsSB.Append("&");
            argumentsSB.Append(keys[i]);
            argumentsSB.Append("=");
            argumentsSB.Append(args[i]);
        }
    }
    else
    {
        argumentsSB.Append("");
    }
    var arguments = argumentsSB.ToString();
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/pdf"));
        var response = await client.GetAsync(URL_OF_SERVER+"api/webservice/" + method + "/" + arguments).ConfigureAwait(false);
        retArr = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
    }
    var x = retArr.Skip(1).Take(y.Length-2).ToArray();
    /*Response.Clear();
    Response.ClearContent();
    Response.ClearHeaders();
    Response.ContentType = "application/pdf";
    Response.AppendHeader("Content-Disposition", "inline;filename=document.pdf");
    Response.BufferOutput = true;
    Response.BinaryWrite(x);
    Response.Flush();
    Response.End();*/
    return new FileStreamResult(new MemoryStream(x),MediaTypeNames.Application.Pdf);
    }

Commented out is code from some other attempts. When I was using that code, I was returning a byte array from the server. It looked like:

JVBER...NjdENEUx
like image 851
Doctor93 Avatar asked Mar 16 '16 17:03

Doctor93


People also ask

Can an API return a file?

Return a File in ASP.NET Core Web API In ASP.NET Core, a Web API action method usually returns an ActionResult object. When we want to return a file response, we can explicitly set the return type for the action method to be FileResult , which is a type inherited from ActionResult .


2 Answers

Some Server side code to return PDF (Web Api).

[HttpGet] [Route("documents/{docid}")] public HttpResponseMessage Display(string docid) {     HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.BadRequest);     var documents = reader.GetDocument(docid);     if (documents != null && documents.Length == 1) {         var document = documents[0];         docid = document.docid;         byte[] buffer = new byte[0];         //generate pdf document         MemoryStream memoryStream = new MemoryStream();         MyPDFGenerator.New().PrintToStream(document, memoryStream);         //get buffer         buffer = memoryStream.ToArray();         //content length for use in header         var contentLength = buffer.Length;         //200         //successful         var statuscode = HttpStatusCode.OK;         response = Request.CreateResponse(statuscode);         response.Content = new StreamContent(new MemoryStream(buffer));         response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");         response.Content.Headers.ContentLength = contentLength;         ContentDispositionHeaderValue contentDisposition = null;         if (ContentDispositionHeaderValue.TryParse("inline; filename=" + document.Name + ".pdf", out contentDisposition)) {             response.Content.Headers.ContentDisposition = contentDisposition;         }     } else {         var statuscode = HttpStatusCode.NotFound;         var message = String.Format("Unable to find resource. Resource \"{0}\" may not exist.", docid);         var responseData = responseDataFactory.CreateWithOnlyMetadata(statuscode, message);         response = Request.CreateResponse((HttpStatusCode)responseData.meta.code, responseData);     }     return response; } 

On my a View you could do something like this

<a href="api/documents/1234" target = "_blank" class = "btn btn-success" >View document</a> 

which will call the web api and open the PDF document in a new tab in the browser.

Here is how i basically do the same thing but from a MVC controller

// NOTE: Original return type: FileContentResult, Changed to ActionResult to allow for error results [Route("{docid}/Label")] public ActionResult Label(Guid docid) {     var timestamp = DateTime.Now;     var shipment = objectFactory.Create<Document>();     if (docid!= Guid.Empty) {         var documents = reader.GetDocuments(docid);         if (documents.Length > 0)             document = documents[0];              MemoryStream memoryStream = new MemoryStream();             var printer = MyPDFGenerator.New();             printer.PrintToStream(document, memoryStream);              Response.AppendHeader("Content-Disposition", "inline; filename=" + timestamp.ToString("yyyyMMddHHmmss") + ".pdf");             return File(memoryStream.ToArray(), "application/pdf");         } else {             return this.RedirectToAction(c => c.Details(id));         }     }     return this.RedirectToAction(c => c.Index(null, null)); } 

Hope this helps

like image 133
Nkosi Avatar answered Sep 25 '22 04:09

Nkosi


I needed to return a pdf file from a .NET core 3.1 web api, and found this excellent article:

https://codeburst.io/download-files-using-web-api-ae1d1025f0a9

Basically, you call:

var bytes = await System.IO.File.ReadAllBytesAsync(pathFileName);
return File(bytes, "application/pdf", Path.GetFileName(pathFileName));

Whole code is:

using Microsoft.AspNetCore.Mvc;
using System.IO;

using Reportman.Drawing;
using Reportman.Reporting;
using System.Text;
using System.Threading.Tasks;

[Route("api/[controller]")]
[ApiController]
public class PdfController : ControllerBase
{
    [HttpGet]
    [Route("ot/{idOT}")]
    public async Task<ActionResult> OT(string idOT)
    {
        Report rp = new Report();
        rp.LoadFromFile("ot-net.rep"); // File created with Reportman designer
        rp.ConvertToDotNet();

        // FixReport
        rp.AsyncExecution = false;
        PrintOutPDF printpdf = new PrintOutPDF();

        // Perform the conversion from one encoding to the other.
        byte[] unicodeBytes = Encoding.Convert(Encoding.ASCII, Encoding.Unicode, Encoding.ASCII.GetBytes($"Orden de trabajo {idOT}"));
        string unicodeString = new string(Encoding.Unicode.GetChars(unicodeBytes));
        // todo: convert to unicode
        // e = Encoding.GetEncoding(unicodeString);
        // System.Diagnostics.Trace.WriteLine(e);
        if (rp.Params.Count > 0)
        {
            rp.Params[0].Value = unicodeString;
        }

        printpdf.FileName = $"ot-{idOT}.pdf";
        printpdf.Compressed = false;

        if (printpdf.Print(rp.MetaFile))
        {
            // Download Files using Web API. Changhui Xu. https://codeburst.io/download-files-using-web-api-ae1d1025f0a9
            var bytes = await System.IO.File.ReadAllBytesAsync(printpdf.FileName);
            return File(bytes, "application/pdf", Path.GetFileName(printpdf.FileName));
        }

        return null;
    }

Call to this API looks like: https://localhost:44387/api/pdf/ot/7

Reportman is a pdf generator you can found at: https://reportman.sourceforge.io/

Enjoy!

like image 35
Chesare Avatar answered Sep 24 '22 04:09

Chesare