2013-01-03 22 views
14

Próbuję użyć ServiceStack, aby zwrócić plik do klienta ServiceStack w trybie RESTOWN.Jak spożywać pliki za pomocą klienta ServiceStack

Czytałem inne pytania na temat SO (here i here), które zalecają użycie HttpResult i obiektu FileInfo lub MemoryStream, aby umożliwić zmianę nagłówka ContentType na odpowiedni typ pliku.

Działa to dla mnie, gdy zadzwonię do usługi za pośrednictwem przeglądarki, automatycznie rozpoczyna się pobieranie właściwego pliku. Jak jednak wykorzystać plik przy użyciu jednego z klientów ServiceStack?

używam żądania dto i próbuje powrócić używając coś podobnego do

return new HttpResult(new FileInfo("file.xml"), asAttachment:true) { 
    ContentType = "text/xml" 
}; 

Jak będę konsumować to z JsonServiceClient na przykład?

Odpowiedz

13

Nie zużywałbyś plików z ServiceStack's .NET ServiceClients, ponieważ są one głównie do wysyłania DTO.

można po prostu użyć każdy normalny WebRequest do pobierania plików, w v3.9.33 z ServiceStack wprowadził pewne handy WebRequest extensions HTTP Utils które to łatwe, np:

Dla pliku tekstowego:

var xmlFile = downloadUrl.GetXmlFromUrl(responseFilter: httpRes => { 
     var fileInfoHeaders = httpRes.Headers[HttpHeaders.ContentDisposition]; 
    }); 

Gdzie fileInfoHeaders zawiera W3C ContentDisposition HTTP Header, np po powrocie do FileInfo, ServiceStack returns:

attachment;filename="file.xml";size={bytesLen}; 
creation-date={date};modification-date={date};read-date={date}; 

Aby pobrać plik binarny można użyć:

var rawBytes = downloadUrl.GetBytesFromUrl(httpRes => ...); 
+0

Dzięki Mythz, znalazłem rozszerzenia WebRequest podczas badania odpowiedzi loannisa poniżej i zacząłem podejrzewać, że wymagany był standardowy WebRequest, a nie ServiceClient. Z powodzeniem skorzystałem już z DownloadAsString, ale przełączyłem się na DownloadUrl w twoim przykładzie dzięki dostępowi do nagłówków, które zapewnia. Jeszcze raz dziękuję za potwierdzenie, że do pobierania plików powinienem użyć WebRequest, znalazłem mnóstwo kodu Service dla dostarczania plików, ale szaleję próbując znaleźć przykład konsumowania usługi :) – thudbutt

+0

+1: Ładne, proste, praktyczne. –

+0

Nie wiedziałem o tej funkcjonalności, to proste bezkompromisowe zadziwiające! –

1

Można przechwycić odpowiedź przed jej obsługiwane za pomocą filtru odpowiedzi, jak poniżej:

ServiceClientBase.HttpWebResponseFilter = response => 
{ 
    if (response.Headers["Content-Disposition"] != null) 
    { 
     var t = response.DownloadText(); 
     Console.WriteLine(t); 
    } 
}; 

Jednakże, nie jest to najlepszy sposób, aby sobie z tym poradzić, ponieważ rzeczywista wywołanie client.Method() spowoduje w ArgumentException, gdy klient próbuje odczytać strumień odpowiedzi (ponieważ został wczytany wcześniej przez response.DownloadFile(...).Na jeszcze nie wymyśliłem sposobu, aby obsłużyć go z wdziękiem, ale zaktualizuję moją odpowiedź, jeśli zrobię.

+0

Wielkie dzięki loannis, które rzeczywiście działa, ale jak mówisz, zgłasza wyjątek, gdy oryginalne połączenie próbuje odczytać. Jeśli spojrzysz na kod źródłowy ServiceStack, zobaczysz, że podczas wywoływania funkcji Get with void return filtry odpowiedzi są stosowane przed wywołaniem ReadFully w strumieniu odpowiedzi, który spowoduje wyjątek. Nie jestem pewien, czy jest w stanie to zrobić w zgrabny sposób (uchylając wyjątek), ponieważ nie sądzę, że jest to przeznaczeniem filtrów odpowiedzi. – thudbutt

+0

Masz rację; Poza tym, to jest złe praktyki IMHO napisać kod, który obsługuje wyjątki, które zawsze będą miały miejsce. –

15

Miałem podobny wymóg, który również wymaganą mnie śledzić postęp pobierania plików strumieniowych.Zrobiłem to z grubsza tak:

server-side:

usługa:

public object Get(FooRequest request) 
{ 
    var stream = ...//some Stream 
    return new StreamedResult(stream); 
} 

klasa StreamedResult:

public class StreamedResult : IHasOptions, IStreamWriter 
{ 
    public IDictionary<string, string> Options { get; private set; } 
    Stream _responseStream; 

    public StreamedResult(Stream responseStream) 
    { 
     _responseStream = responseStream; 

     long length = -1; 
     try { length = _responseStream.Length; } 
     catch (NotSupportedException) { } 

     Options = new Dictionary<string, string> 
     { 
      {"Content-Type", "application/octet-stream"}, 
      { "X-Api-Length", length.ToString() } 
     }; 
    } 

    public void WriteTo(Stream responseStream) 
    { 
     if (_responseStream == null) 
      return; 

     using (_responseStream) 
     { 
      _responseStream.WriteTo(responseStream); 
      responseStream.Flush(); 
     } 
    } 
} 

client-side:

metoda
string path = Path.GetTempFileName();//in reality, wrap this in try... so as not to leave hanging tmp files 
var response = client.Get<HttpWebResponse>("/foo/bar"); 

long length; 
if (!long.TryParse(response.GetResponseHeader("X-Api-Length"), out length)) 
    length = -1; 

using (var fs = System.IO.File.OpenWrite(path)) 
    fs.CopyFrom(response.GetResponseStream(), new CopyFromArguments(new ProgressChange((x, y) => { Console.WriteLine(">> {0} {1}".Fmt(x, y)); }), TimeSpan.FromMilliseconds(100), length)); 

W "copyfrom" Rozszerzenie zostało zapożyczone bezpośrednio z pliku kod źródłowy "StreamHelper.cs" w tym projekcie tutaj: Copy a Stream with Progress Reporting (Kudos do Henning Dieterichs)

And Uznanie mythz i wszelkie przyczynia do ServiceStack. Świetny projekt!

+2

Świetna odpowiedź, podoba mi się :). Również powiązany jest @ bambus [Reactive Obavingable Streaming Client] (https://gist.github.com/bamboo/5078236). – mythz

+0

Podejrzewam, że istnieją zagrożenia, które nie są tutaj poprawnie obsługiwane. HttpWebResponse musi zostać formalnie unieszkodliwiony, po unieszkodliwieniu strumienia odpowiedzi.Jest to zgodne z [dokumentami MSDN] (http://msdn.microsoft.com/en-us/library/system.net.webresponse.getresponsestream.aspx) i innymi pytaniami, w przypadku których ludzie doświadczają wycieków pamięci, gdy nie postępują zgodnie z tą procedurą . –

2

Znalazłem odpowiedź mitów, aby działała dobrze, ale możliwe jest również użycie ich wbudowanego w JSonServiceClient do przetwarzania również żądań plików, tylko w nieco nieintuicyjny sposób, ponieważ nie można w rzeczywistości użyć typ zwrotu, jakiego można się spodziewać.

Dla definicji modelu jak ten:

[Route("/filestorage/outgoing/{Name}.{Extension}", "GET")] 
[Route("/filestorage/outgoing", "GET")] 
public class GetFileStorageStream : IReturn<HttpResult> 
{ 
    public string Name { get; set; } 
    public string Extension { get; set; } 
    public bool ForDownload { get; set; } 
} 

można zdefiniować swoją usługę do zwracania HttpResult:

public class FileStorageService : Service 
{ 
    public HttpResult Get(GetFileStorageStream fileInformation) 
    { 
     var internalResult = GetFromFileStorage(fileInformation); 
     var fullFilePath = Path.Combine("C:\Temp", internalResult.FileName); 
     return new HttpResult(new FileInfo(fullFilePath), asAttachment: fileInformation.ForDownload); 
    } 
} 

Następnie po stronie klienta można użyć tego Get szablon właściwie dostać kontekst internetowy:

var client = new JsonServiceClient("http://localhost:53842"); 
var httpResponse = client.Get<HttpWebResponse>("/filestorage/outgoing/test.jpg"); 
pictureBox1.Image = Image.FromStream(httpResponse.GetResponseStream()); 

Znalazłem, że nie można użyć new API Get methods jako spróbują deserializować HttpResult, który w rzeczywistości nie jest prawdziwym typem zwracania, ale klasą reprezentującą kontekst sieciowy utworzony przez stos usługi.

Powiązane problemy