2011-09-09 113 views
1

摘要从远程Web服务

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

有没有人知道是否在制作WebRequest时,一旦响应的第一部分被返回或缓冲后立即响应流变为可用,所以我没有得到响应,直到检索到所有数据?

除了下面完整问题中列出的那些选项以外,还有其他选择吗? (除了将附件保留在儿童和父母的数据库中 - 我真的不想这样做,因为我需要定期同步它们)。

TLDR版本

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

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

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

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

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

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

  3. 链接在子应用程序中,但直接将来自Web服务请求的流提供给FileStreamResult。对我来说,这看起来是FileStreamResult读入块的最佳选择,而不是在将所有数据发送到客户端之前都可用。我在这里可以看到的唯一缺点是我无法直接处理WebResponse,因为直到我的操作返回后才会执行FileStreamResult。

以下是我对为(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"; } } 
} 
+0

Bueller? Bueller?我所得到的就是这个蟋蟀。 – tvanfosson

+0

如果两个应用程序都在您的控制之下,您可以允许来自其他域的跨站点脚本,并使用Javascript显示您喜欢的任何附件。请参阅:HttpContext.Current.Response.AddHeader(“Access-Control-Allow-Origin”,“”); – wilsotc

回答

0

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

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

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

在独特的标识符上拍一段过期时间,你应该很好。

+0

我喜欢使用过期日期而不是使其成为一次性唯一标记的想法。对于如何处理与其他人分享链接的用例,您有什么建议吗?假定后一个人被授权使用该应用程序并使用该链接。我并非试图解决授权人员与未经授权人员分享附件的问题;一旦他们下载它,我认为他们不会不恰当地分享它,但他们可能想要与授权人员分享链接。 – tvanfosson

+0

如果链接令牌基于AntiForgeryToken类,则它不能共享,因为它需要cookie进行验证。如果授权用户被允许共享链接,那么只需将您的一次性使用令牌转换为即将到期的令牌。但是,我不希望允许共享链接,而是希望让用户能够与下载链接共享页面链接,因为这会加强您的安全模型。 – counsellorben