【问题标题】:How to consume a file with a ServiceStack client如何使用 ServiceStack 客户端使用文件
【发布时间】:2012-12-17 13:33:30
【问题描述】:

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

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

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

我正在使用 Request DTO 并尝试使用类似的东西返回

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

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

【问题讨论】:

    标签: c# servicestack


    【解决方案1】:

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

    服务器端

    服务:

    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(感谢 Henning Dieterichs)

    感谢mythz 和ServiceStack 的任何贡献者。很棒的项目!

    【讨论】:

    • 很好的答案,我喜欢它:)。同样相关的是@bamboo 的Reactive Observable Streaming Client
    • 我怀疑这里存在未正确处理的危险。 HttpWebResponse 需要在处理响应流后正式处理。这是根据MSDN docs 和其他人们在未能遵循此过程时遇到内存泄漏的问题。
    【解决方案2】:

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

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

    对于文本文件:

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

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

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

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

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

    【讨论】:

    • 感谢mythz,我在调查下面的loannis 答案时发现了WebRequest 扩展,并开始怀疑需要标准WebRequest 而不是ServiceClient。我已经成功使用了 DownloadAsString,但由于可以访问它提供的 Headers,我将切换到您的示例中的 DownloadUrl。再次感谢您确认应使用 WebRequest 进行文件下载,我发现了大量用于提供文件的服务代码,但试图找到使用该服务的示例时发疯了 :)
    • 我不知道这个功能,这很简单,没有摩擦,太棒了!
    【解决方案3】:

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

    对于这样的模型定义:

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

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

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

      • 非常感谢loannis,这确实有效,但正如您所说,当原始调用尝试读取时会引发异常。如果您查看 ServiceStack 源代码,您可以看到,当调用 Get 并返回 void 时,响应过滤器在调用 ReadFully 之前应用到响应流上的行,这将导致异常。我不确定是否有一种优雅的方式来处理它(将异常放在一边),因为我认为这不是响应过滤器的用途。
      • 你是对的;此外,恕我直言,编写处理总会发生的异常的代码是不好的做法。
      猜你喜欢
      • 2015-11-28
      • 1970-01-01
      • 2013-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-07
      相关资源
      最近更新 更多