14

我正在尝试使用ServiceStack以 RESTful 方式将文件返回给 ServiceStack 客户端。

我已经阅读了关于 SO(此处此处)的其他问题,这些问题建议使用 HttpResult 和 FileInfo 对象或 MemoryStream 以允许将 ContentType 标头更改为相关的文件类型。

当我通过浏览器调用服务时,这对我有用,正确的文件会自动开始下载。但是,如何使用 ServiceStack 客户端之一使用文件?

我正在使用 Request DTO 并尝试使用类似于

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

例如,我将如何使用 JsonServiceClient 来使用它?

4

4 回答 4

15

我有一个类似的要求,也要求我跟踪流文件下载的进度。我大致是这样做的:

服务器端

服务:

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

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

客户端

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

“CopyFrom”扩展方法直接从这里项目中的源代码文件“StreamHelper.cs”借用:Copy a Stream with Progress Reporting (Kudos to Henning Dieterichs)

对 mythz 和 ServiceStack 的任何贡献者表示敬意。伟大的项目!

于 2013-10-08T15:29:22.060 回答
13

您不会使用ServiceStack 的 .NET ServiceClients使用文件,因为它们主要用于发送 DTO。

您可以使用任何普通的 WebRequest 来下载文件,在 ServiceStack 的 v3.9.33 中引入了一些方便的 WebRequest 扩展 HTTP Utils使这变得容易,例如:

对于文本文件:

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

其中 fileInfoHeaders 包含W3C ContentDisposition HTTP Header,例如,当返回 a 时FileInfoServiceStack 返回

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

要下载二进制文件,您可以使用:

var rawBytes = downloadUrl.GetBytesFromUrl(httpRes => ...);
于 2013-01-04T05:09:30.900 回答
2

我发现 mythz 的答案效果很好,但也可以使用他们内置的JSonServiceClient来处理文件请求,只是以一种有点不直观的方式,因为你实际上不能使用你期望的返回类型。

对于这样的模型定义:

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

您可以定义您的服务以返回一个 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);
    }
}

然后在客户端,您可以使用此 Get 模板正确获取 Web 上下文:

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

我发现不可能使用新的 API Get 方法,因为它们会尝试反序列化 HttpResult,这实际上不是真正的返回类型,而是代表服务堆栈创建的 Web 上下文的类。

于 2013-10-30T19:46:00.320 回答
1

您可以在使用响应过滤器处理响应之前拦截响应,如下所示:

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

但是,这不是处理它的最佳方法,因为实际调用client.Method()将导致ArgumentException客户端尝试读取响应流(因为它之前已被 读取response.DownloadFile(...)。我还没有想出处理的方法优雅地,但如果我这样做,我会更新我的答案。

于 2013-01-03T11:55:55.163 回答