1

I'm quite new to REST-services in general and I'm playing around with ServiceStack (which is awesome!). I have some services running and now I want to be able to download files (zip) via the service.

My idea is to set a route (/download) to receive files and download them with the client to store them locally.

My current approach looks like this:

[Route("/download")] public class DownloadRequest : IReturn<HttpResult> { } public class FileDownloadService : Service { public object Any(DownloadRequest request) { string fileFullPath = @"C:\Users\marcel\Downloads\test.zip"; string mimeType = "application/zip"; FileInfo fi = new FileInfo(fileFullPath); byte[] reportBytes = File.ReadAllBytes(fi.FullName); HttpResult result = new HttpResult(reportBytes, mimeType); result.Headers.Add("Content-Disposition", "attachment;filename=Download.zip;"); return result; } } 

I'd like to change this implementation to send data as stream. I stumbled upon IStreamWriterAsync, but couldn't really find documentation on usage for this. I'd also like to be able to handle client-side download with the ServiceStack C#-Client.

What would be a good strategy do implement my plan?

Edit: Something like that?

[Route("/download")] public class DownloadRequest : IReturn<Stream> { } public class FileDownloadService : Service, IHasOptions { public IDictionary<string, string> Options { get; private set; } public Stream Any(DownloadRequest request) { string fileFullPath = @"C:\Users\marcel\Downloads\test.zip"; FileInfo fi = new FileInfo(fileFullPath); Options = new Dictionary<string, string> { {"Content-Type","application/zip" }, {"Content-Disposition", "attachment;filename=Download.zip;" } }; return fi.OpenRead(); } } 

2 Answers 2

3

An easy way to download a file is to return the fileInfo in a HttpResult, e.g:

return new HttpResult(new FileInfo(fileFullPath), asAttachment:true); 

Or by using the Virtual File System

return new HttpResult( VirtualFileSources.GetFile(virtualPath), asAttachment:true); 

Both of these APIs already write the file bytes as a Stream so there's no need to try manually doing it yourself.

Note: HttpResult is just a server wrapper object not the response body itself so it should never be used in an IReturn<T> interface whose purpose is to tell clients what Response Type the Service returns.

The IReturn<T> should specify what the Response Body is, in this case since it's not a Response DTO it can be either:

IReturn<byte[]> or IReturn<Stream> 

Or you can just leave it unspecified as you'll still be able to download it using the ServiceClient's raw data APIs:

With IReturn<Stream> interface:

using (Stream stream = client.Get(new DownloadRequest())) { ... } 

Or you can just easily download the response as a Stream without the IReturn<T> by specifying how you want to access the raw data on the call-site, e.g:

Stream stream = client.Get<Stream>(new DownloadRequest()); byte[] bytes = client.Get<byte[]>("/download"); 

If you want to also access the Response HTTP Headers you can also request the raw HttpWebResponse to be returned which will let you access the Response HTTP Headers:

using (var webRes = client.Get<HttpWebResponse>(new DownloadRequest())) using (var stream = webRes.GetResponseStream()) { var contentDisposition = webRes.Headers[HttpHeaders.ContentDisposition]; } 

Alternatively you can also use HTTP Utils to download arbitrary files, e.g:

 string info = null; var bytes = baseUrl.CombineWith("download").GetBytesFromUrl( responseFilter: res => info = res.Headers[HttpHeaders.ContentDisposition]); 
Sign up to request clarification or add additional context in comments.

2 Comments

Sorry, I don't get it completely yet. When I have a request dto which returns a Stream via IReturn, how can I return an HttpResult with its response dto? To clarify, what must the response dto look like?
Nevermind, I think I got it running! Thanks for your great support!
1

Have a look at this article. Basically, just return a Stream. You can use fi.OpenRead and return that stream.

To combine headers and stream, an option is a custom return type instead, something like this

public class DownloadFileResult : IStreamWriterAsync, IHasOptions { private readonly Stream _stream; public IDictionary<string, string> Options { get; } public DownloadFileResult(Stream responseStream, string mime, string filename) { _stream = responseStream; Options = new Dictionary<string, string>() { {"Content-Disposition", $"attachment; filename=\"{filename}\";"}, {"Content-Type", mime} }; } public async Task WriteToAsync(Stream responseStream, CancellationToken token) { if (_stream == null) { return; } await _stream.CopyToAsync(responseStream); responseStream.Flush(); } } 

4 Comments

Thanks! I think I get what you say, but how would I then connect stream, options and service? I've added some code to my question.
Sorry my first answer was not very clear and not very correct either, see edit
IStreamWriter is now deprecated.
I changed my example to reflect this.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.