从远程Web服务返回附件

时间:2011-09-09 17:19:35

标签: asp.net-mvc web-services download

摘要

我需要从子应用程序的客户端中的链接检索存储在父应用程序中的附件。附件在父应用程序中通过Web服务调用提供 - 它返回标准的FileContentResult,内容类型为“application / octet-stream”。我能想到的最好的方法是通过WebRequest检索它并将生成的响应流传递给FileStreamResult,尽管我有一些替代方案。

有人知道,在创建WebRequest时,一旦返回响应的第一部分或缓冲后响应流是否立即可用,所以在检索完所有数据之前我都没有得到响应?

除了以下完整问题中列出的选项之外还有其他选项吗? (除了将附件保留在子DB和父DB中之外 - 我真的不想这样做,因为那时我也需要定期同步它们。)

TLDR版本

我有两个相关的应用程序通过RESTful Web服务进行通信。父应用程序维护可能具有附件的实体集合。例如,请求可能将Excel电子表格作为附件。实体及其附件存储在数据库中,使用与访问请求相同的逻辑来控制对附件的访问。也就是说,如果您无法查看请求,则无法下载附件。

在子应用程序中,我为分配给特定机构的实体维护了一些集成胶 - 该应用程序用于在我们的董事会和每个Regents学校之间进行通信。我不想维护和同步完整的实体/附件。我只想保留足够的信息以允许我连接到父应用程序中的Web服务,并获取子应用程序的特定实例可以访问的实体的详细信息。

这适用于实体数据本身。数据量很小,并且子应用程序中缓冲的开销不会在访问数据时出现明显的延迟。如有必要,我可以在本地缓存数据,以避免性能损失。

我关心的是附件。我已经考虑过三种不同的机制来提供对子应用程序客户端的附件的访问。

  1. 生成一次性使用令牌和关联的URL,允许客户端直接从父应用程序下载附件。令牌生成Web服务调用将确保子应用程序的用户应该有权访问附件。这样做的缺点是您只能在客户端中单击链接一次。再次单击将导致错误而不是获取附件。

  2. 缓冲子应用程序中的附件。在这种情况下,我将提供一个控制器/操作来下载子应用程序中的附件,然后调用Web服务方法来获取附件并让子应用程序将附件作为FileContentResult发送。这消除了仅能够单击链接一次的问题,但附件可能相当大,并且缓冲子应用程序中的数据可能会使下载附件的时间增加一倍,更糟糕的是,在此之前会导致显着延迟。附件下载开始。

  3. 子应用程序中的链接,但是将Web服务请求中的流直接提供给FileStreamResult。对我来说,这似乎是最好的选择,因为FileStreamResult读取块而不是在将所有数据发送到客户端之前必须拥有所有数据。我在这里看到的唯一缺点是我不能再直接处理WebResponse,因为在我的操作返回之后不会执行FileStreamResult。

  4. 以下是我对(2)和(3)的API包装器代码的代码:

    private class ResponseModel<T> : IDisposable
    {
        public T Model { get; set; }
        public WebResponse Response { get; set; }
    
        private bool Disposed { get; set; }
        private void Dispose( bool disposing )
        {
            if (!Disposed)
            {
                if (disposing)
                {
                    ((IDisposable)this.Response).Dispose();
                }
                Disposed = true;
            }
        }
    
        public void Dispose()
        {
            Dispose( true );
        }
    }
    
    private ResponseModel<T> GetAttachmentResponse<T>( long id ) where T : IDownloadModel, new()
    {
        var request = GetRequest( string.Format( "{0}/api/getattachment/{1}/{2}", this.BaseUrl, this.Key, id ) );
    
        var response = request.GetResponse();
        var model = (T)Activator.CreateInstance<T>();
        var contentDisposition = response.Headers["Content-Disposition"];
        if (!string.IsNullOrEmpty( contentDisposition ))
        {
            var filename = contentDisposition.Split( new[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries )
                                             .SingleOrDefault( s => s.StartsWith( "filename", StringComparison.OrdinalIgnoreCase ) );
            if (!string.IsNullOrEmpty( filename ))
            {
                model.Name = filename.Split( '=' ).Skip( 1 ).FirstOrDefault();
            }
        }
        if (string.IsNullOrEmpty( model.Name ))
        {
            model.Name = "untitled";
        }
    
        return new ResponseModel<T> { Model = model, Response = response };
    }
    
    public FileDownloadModel GetAttachment( long id )
    {
        using (var response = GetAttachmentResponse<FileDownloadModel>( id ))
        {
            var reader = new BinaryReader( response.Response.GetResponseStream() );
            response.Model.Content = reader.ReadBytes( (int)response.Response.ContentLength );
            return response.Model;
    
        }
    }
    
    public FileStreamDownloadModel GetAttachmentStream( long id )
    {
        // since we're returning the stream, we can't dispose of the response when done.
        var response = GetAttachmentResponse<FileStreamDownloadModel>( id );
        response.Model.Stream = response.Response.GetResponseStream();
        return response.Model;
    }
    
    
    public interface IDownloadModel
    {
        string ContentType { get; }
        string Name { get; set; }
    }
    

    模型类

    public class FileDownloadModel : IDownloadModel
    {
        public byte[] Content { get; set; }
        public string Name { get; set; }
        public string ContentType { get { return "application/octet-stream"; } }
    }
    
    public class FileStreamDownloadModel : IDownloadModel
    {
        public Stream Stream { get; set; }
        public string Name { get; set; }
        public string ContentType { get { return "application/octet-stream"; } }
    }
    

1 个答案:

答案 0 :(得分:0)

我建议选项1的变体[称之为选项1(a)]。

不是生成一次性令牌,而是“借用”MVC AntiForgeryToken类,并让您的父应用程序向子应用程序返回自定义令牌和cookie,以包含在返回给用户的表单中。

如果子应用程序可能在单个页面上具有多个文档的链接,则在对令牌信息的请求中,让子应用程序提交唯一标识符(标识来自用户的页面请求)作为请求的一部分。然后,您可以在生成令牌时使用此标识符,并且可以将标识符存储为验证过程的一部分。这将为您提供一个多用途令牌,对于页面上的每个链接都是唯一的。

在唯一标识符上暂停一个到期时间,你应该好好去。