2016-03-16 14 views
11

Mam projekt Web API, który działa na serwerze. Ma on zwracać pliki PDF z dwóch różnych źródeł: rzeczywistego przenośnego pliku dokumentu (PDF) oraz łańcucha base64 przechowywanego w bazie danych. Problem, który mam, polega na wysłaniu dokumentu do klienta aplikacji MVC. Reszta to szczegóły na temat wszystkiego, co się wydarzyło i które już wypróbowałem.Jak zwrócić plik PDF z aplikacji Web API

Napisałem kod, który z powodzeniem przetłumaczył te dwa formaty na kod C#, a następnie (z powrotem) na formularz PDF. Pomyślnie przesłałem tablicę bajtów, która miała reprezentować jeden z tych dokumentów, ale nie mogę jej wyświetlić w przeglądarce (w nowej zakładce samodzielnie). Zawsze pojawia się błąd "nie można wyświetlić".

Niedawno zrobiłem sposób, aby przejrzeć dokumenty po stronie serwera, aby upewnić się, że przynajmniej mogę to zrobić. Dostaje dokument do kodu i tworzy z nim obiekt FileStreamResult, który następnie zwraca jako (niejawny rzut) ActionResult. Dostałem to, aby powrócić do kontrolera MVC po stronie serwera i wrzuciłem go do prostego zwrotu (bez widoku), który wyświetla plik PDF w porządku w przeglądarce. Jednak próba przejścia od razu do funkcji Web API po prostu zwraca to, co wygląda na reprezentację JSON obiektu FileStreamResult.

Kiedy próbuję uzyskać to, aby powrócić poprawnie do mojej aplikacji MVC klienta, to mówi mi, że "_buffer" nie może być ustawiony bezpośrednio. Niektóre komunikaty o błędach wskazujące, że niektóre właściwości zwracane i wrzucane do obiektu są prywatne i nie można uzyskać do nich dostępu.

Powyższa reprezentacja tablic bajtowych pliku PDF, po przetłumaczeniu na ciąg base64, wydaje się nie mieć tej samej liczby znaków, co łańcuch "_buffer" zwrócony w JSON przez obiekt FileStreamResult. Brakuje około 26k A na końcu.

Wszelkie pomysły na temat tego, jak poprawnie pobrać ten plik PDF? Mogę podać kod, jeśli jest to konieczne, ale musi istnieć jakiś znany sposób, aby zwrócić plik PDF z aplikacji Web API po stronie serwera do aplikacji MVC po stronie klienta i wyświetlić ją jako stronę internetową w przeglądarce.

P.S. Wiem, że aplikacja "po stronie klienta" nie jest technicznie po stronie klienta. Będzie to również aplikacja serwera, ale nie powinno to mieć znaczenia w tym przypadku. W stosunku do serwera Web API moja aplikacja MVC jest "stroną klienta".

Kod Uzyskania 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, który dostaje od GetPDF:

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

Do wyświetlania PDF po stronie serwera:

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

kodu w aplikacja MVC uległa zmianie d dużo z biegiem czasu. Tak jak teraz, nie przechodzi do etapu, w którym próbuje wyświetlać w przeglądarce. Przed tym dostaje błąd.

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

JSON dostaję od uruchamiania metody bezpośrednio z kontrolera API Web jest:

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

ja skrócić "_buffer" bo to absurdalnie długi. Obecnie pojawia się komunikat o błędzie poniżej na linii powrotnej get_pdf (args, klucze)

Wyjątek szczegóły: Newtonsoft.Json.JsonSerializationException: nie można utworzyć wystąpienie typu System.Web.Mvc.ActionResult. Typ to interfejs lub klasa abstrakcyjna i nie można utworzyć instancji. Ścieżka "FileStream".

Powrót kiedy użyłem, aby uzyskać pusty czytnik PDF (. Czytelnik nie był pusty plik), kiedyś ten kod:

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

wykomentowane jest kod od innych prób. Kiedy używałem tego kodu, zwróciłem tablicę bajtów z serwera. Wyglądało to następująco:

JVBER...NjdENEUx 
+0

Pokaż przykładowy kod, konkretne komunikaty o błędach itd –

+0

@ Mr.T mam dodałem wszystko, co mogłem teraz dodać w mojej edycji. – Doctor93

+0

Dlaczego używasz 'JObject' w ogóle w' get_pdf'? – Jacob

Odpowiedz

12

Niektóre kody po stronie serwera do zwrotu 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; 
} 

Na moim widokiem można zrobić coś takiego

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

które nazywamy api internetową i otwórz dokument PDF w nowej karcie w przeglądarce.

Oto jak I w zasadzie to samo, ale z kontrolera 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)); 
} 

nadzieję, że to pomaga

+0

OK, to działa, ale próbuję usunąć link z przedniego końca do końca. Przód powinien zasadniczo działać jako środkowy człowiek, ukrywając WSZYSTKIE logiki biznesowej. Moja próba dokonania tego zwraca ten tekst do przeglądarki "Kod stanu: 200, ReasonPhrase:" OK ", Wersja: 1.1, Treść: System.Net.Http.StreamContent, Nagłówki: {Pragma: no-cache Cache-Control: no- cache Data: środa, 16 marca 2016 18:48:38 Serwer GMT: Microsoft-IIS/10.0 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET Długość treści: 45600 Content-Disposition: inline; filename = chart.pdf Content-Type: application/pdf Wygasa: -1} " – Doctor93

+1

Mam to. Dziękuję Ci!Po prostu musiałem odczytać zawartość jako strumień i wrzucić go do pliku FileStreamResult niejawnie rzutowanego jako ActionResult – Doctor93

+0

Hi! Refaktoryzowałem twój przykład i uderzyłem w ścianę, gdy próbowałem uprościć linię 'response.Content = new StreamContent (new MemoryStream (buffer)); - możesz pomóc mi zrozumieć ** dlaczego ** nie możesz po prostu przekazać MemoryStream został wcześniej utworzony w StreamContent? Próbowałem i to nie działało, ale nie rozumiem, dlaczego nie. –