web-dev-qa-db-fra.com

Comment renvoyer un PDF à partir d'une application API Web

J'ai un projet d'API Web qui s'exécute sur un serveur. Il est supposé renvoyer des PDF à partir de deux types de sources différents: un fichier de document portable (PDF) et une chaîne base64 stockée dans une base de données. Le problème que j'ai est de renvoyer le document à une application cliente MVC. Le reste de ce sont les détails de tout ce qui s'est passé et que j'ai déjà essayé.

J'ai écrit un code qui traduit avec succès ces deux formats en code C #, puis (retour) en forme PDF. J'ai transféré avec succès un tableau d'octets censé représenter l'un de ces documents, mais je n'arrive pas à l'afficher dans le navigateur (dans un nouvel onglet par lui-même). J'obtiens toujours un type d'erreur "ne peut pas être affiché".

Récemment, j'ai créé un moyen de visualiser les documents côté serveur afin de pouvoir au moins le faire faire. Il insère le document dans le code et crée un FileStreamResult qu'il renvoie ensuite sous la forme d'un ActionResult (distribution implicite). J'ai eu cela pour revenir à un contrôleur MVC côté serveur et jeté dans un simple retour (pas de vue) qui affiche le PDF tout va bien dans le navigateur. Cependant, essayez simplement d'aller directement à la La fonction API Web renvoie simplement ce qui ressemble à une représentation JSON d'un FileStreamResult.

Lorsque j'essaie d'obtenir que cela retourne correctement à mon application cliente MVC, cela me dit que "tampon" ne peut pas être défini directement. Certains messages d'erreur indiquant que certaines des propriétés renvoyées et renvoyées dans un objet sont privées et inaccessibles.

La représentation d'octets du tableau PDF susmentionnée, lorsqu'elle est traduite en chaîne base64, ne semble pas avoir le même nombre de caractères que la chaîne "_buffer" renvoyée dans le code JSON par un FileStreamResult. Il manque environ 26k 'A à la fin.

Des idées sur la façon d'obtenir ce PDF pour retourner correctement? Je peux fournir du code si nécessaire, mais il doit exister un moyen connu de renvoyer un PDF à partir de une application API Web côté serveur vers une application MVC côté client et l'afficher sous forme de page Web dans un navigateur.

P.S. Je sais que l'application "côté client" n'est pas techniquement du côté client. Ce sera également une application serveur, mais cela ne devrait pas avoir d'importance dans ce cas. Par rapport au serveur API Web, mon application MVC est "côté client".

Code Pour obtenir un 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;
}

Fonction API obtenue à partir de GetPDF:

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

Pour afficher PDF côté serveur:

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

Le code dans l'application MVC a beaucoup changé au fil du temps. À l’heure actuelle, il n’arrive pas au stade où il essaie de s’afficher dans le navigateur. Il y a une erreur avant ça.

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>();
}

Le JSON généré par l'exécution de la méthode directement à partir du contrôleur API Web est:

{
    "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":""
}

J'ai raccourci "_buffer" parce que c'est ridiculement long. Je reçois actuellement le message d'erreur ci-dessous sur la ligne de retour de get_pdf (arguments, clés)

Détails des exceptions: Newtonsoft.Json.JsonSerializationException: Impossible de créer une instance de type System.Web.Mvc.ActionResult. Le type est une classe d'interface ou abstraite et ne peut pas être instancié. Chemin 'FileStream'.

À l'époque où j'avais un lecteur de pdf vierge (le lecteur était vierge. Pas de fichier), j'ai utilisé ce 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);
    }

Commenté est le code de certaines autres tentatives. Lorsque j'utilisais ce code, je renvoyais un tableau d'octets à partir du serveur. Cela ressemblait à:

JVBER...NjdENEUx
21
Doctor93

Certains codes côté serveur à retourner 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;
}

Sur ma vue, vous pourriez faire quelque chose comme ça

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

qui appellera l’API Web et ouvrira le PDF) dans un nouvel onglet du navigateur.

Voici comment je fais essentiellement la même chose mais à partir d'un contrôleur MVC

// 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));
}

J'espère que cela t'aides

26
Nkosi