14

I am trying to use ServiceStack to return a file to a ServiceStack client in a RESTful manner.

I have read other questions on SO (here and here) which advise using HttpResult and a FileInfo object or MemoryStream to allow the ContentType header to be changed to the relevant file type.

This works for me when I call the service via a browser, the correct file automatically starts to download. How do I consume the file using one of the ServiceStack clients though?

I'm using a Request DTO and trying to return using something similar to

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

How would I consume this with the JsonServiceClient for example?

4 Answers 4

15

I had a similar requirement which also required me to track progress of the streaming file download. I did it roughly like this:

server-side:

service:

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

StreamedResult class:

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:

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

The "CopyFrom" extension method was borrowed directly from the source code file "StreamHelper.cs" in this project here: Copy a Stream with Progress Reporting (Kudos to Henning Dieterichs)

And kudos to mythz and any contributor to ServiceStack. Great project!

2
  • 2
    Great answer, I like it :). Also related is @bamboo's Reactive Observable Streaming Client.
    – mythz
    Commented Oct 8, 2013 at 17:14
  • I suspect there are dangers which are not handled correctly here. The HttpWebResponse needs to be formally disposed, after disposing the response stream. This is according to the MSDN docs and other questions where people experience memory leaks when failing to follow this procedure. Commented Feb 25, 2014 at 21:24
13

You wouldn't consume files with the ServiceStack's .NET ServiceClients as they're mainly for sending DTO's.

You can just use any normal WebRequest to download files, in the v3.9.33 of ServiceStack introduced some handy WebRequest extensions HTTP Utils that make this easy, e.g:

For a text file:

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

Where fileInfoHeaders contains the W3C ContentDisposition HTTP Header, e.g. when returning a FileInfo, ServiceStack returns:

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

To download a binary file you can use:

var rawBytes = downloadUrl.GetBytesFromUrl(httpRes => ...);
2
  • Thanks mythz, I found the WebRequest extensions whilst investigating loannis' answer below and started to suspect that a standard WebRequest was required rather than a ServiceClient. I had already successfully used DownloadAsString but will switch to the DownloadUrl in your example thanks to the access to the Headers it provides. Thanks again for confirming that a WebRequest should be used for file downloads, I found plenty of Service code for provision of files but was going mad trying to find an example of consuming the service :)
    – thudbutt
    Commented Jan 4, 2013 at 9:50
  • I wasn't aware of that functionality, that's simple friction-less amazing! Commented Sep 21, 2013 at 0:54
2

I have found mythz answer to work well, but it is also possible to use their built in JSonServiceClient to also process file requests as well, just in a slightly non-intuitive way because you can't actually use the return type you would expect.

For a model definition like this:

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

You can define your service to return an 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);
    }
}

Then on the client side you can use this Get template to properly get the web context:

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

I found it was not possible to use the new API Get methods as they would attempt to deserialize the HttpResult which isn't actually a true return type but a class representing the web context that service stack has created.

1

You can intercept the response prior to it being handled by using a response filter, like below:

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

However, this is not the best way to handle it, since the actual call to client.Method() will result in an ArgumentException when the client attempts to read the response stream (since it has been read previously by response.DownloadFile(...). I haven't yet figured out a way to handle it gracefully, but I 'll update my answer if I do.

2
  • Many thanks loannis, that does indeed work but as you say, throws an exception when the original call attempts the read. If you look at the ServiceStack source code you can see that when calling Get with a void return, the response filters are applied the line before calling ReadFully on the response stream which will be causing the exception. I'm not sure there is a graceful way of handling it (catching the exception aside) as I don't think it's what the response filters were intended for.
    – thudbutt
    Commented Jan 4, 2013 at 9:35
  • You 're right; Besides, it 's bad practice IMHO to write code that handles exceptions that will always happen. Commented Jan 4, 2013 at 10:11

Not the answer you're looking for? Browse other questions tagged or ask your own question.