【发布时间】:2011-11-13 23:28:12
【问题描述】:
总结
我需要从子应用的客户端中的链接检索存储在父应用中的附件。附件可通过 Web 服务调用在父应用程序中使用——它返回内容类型为“application/octet-stream”的标准 FileContentResult。我能想到的最好方法是通过 WebRequest 检索它并将生成的响应流传递给 FileStreamResult,尽管我有一些可用的替代方法。
有谁知道,在发出 WebRequest 时,响应流是否会在响应的第一部分返回后立即可用,或者是否已缓冲,因此在检索到所有数据之前我不会收到响应?
除了下面完整问题中列出的选项之外,是否还有其他我想念的选项? (除了将附件同时保存在子数据库和父数据库中——我真的不想这样做,从那时起我也需要定期同步它们)。
TLDR 版本
我有两个通过 RESTful Web 服务进行通信的相关应用程序。父应用程序维护可能具有附件的实体集合。例如,一个请求可能有一个 Excel 电子表格作为附件。实体及其附件存储在数据库中,并且使用与访问请求相同的逻辑来控制对附件的访问。也就是说,如果您无法查看请求,您应该无法下载附件。
在子应用程序中,我为分配给特定机构的实体维护了一些集成胶水——该应用程序用于在我们的董事会和每个 Regents 学校之间进行通信。我不想维护和同步完整的实体/附件。我只想保留足够的信息以允许我连接到父应用程序中的 Web 服务并获取子应用程序的特定实例可以访问的实体的详细信息。
这适用于实体数据本身。数据量很小,并且子应用程序中的缓冲开销在访问数据时不会出现明显的延迟。如有必要,我可以在本地缓存数据以避免性能损失。
我关心的是附件。我考虑了三种不同的机制来提供从子应用程序的客户端访问附件的权限。
生成一次性使用令牌和关联的 url,允许客户端直接从父应用程序下载附件。令牌生成 Web 服务调用将确保子应用程序的用户应该有权访问附件。这样做的缺点是您只能在客户端中单击一次链接。再次点击会导致错误而不是获取附件。
在子应用中缓冲附件。在这种情况下,我将提供一个控制器/操作来在子应用程序中下载附件,然后调用 Web 服务方法来获取附件并让子应用程序将附件作为 FileContentResult 发送。这消除了只能单击一次链接的问题,但附件可能相当大,并且在子应用程序中缓冲数据可能会使下载附件的时间增加一倍,更糟糕的是,在附件下载开始。
在子应用程序中链接,但将来自 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"; } }
}
【问题讨论】:
-
比勒?布勒?我得到的只是这个蟋蟀。
-
如果这两个应用程序都在您的控制之下,您可以允许来自另一个域的跨站点脚本,并使用 Javascript 显示您喜欢的任何附件。请参阅:HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "
");
标签: asp.net-mvc web-services download