如何在没有临时文件的情况下将流从Web API传递到Azure Blob存储?

时间:2015-05-04 13:30:50

标签: c# asp.net azure asp.net-web-api azure-storage

我正在开发一个文件上传经常发生的应用程序,并且可能非常大。

这些文件正在上传到Web API,然后Web API将从请求中获取流,并将其传递给我的存储服务,然后将其上传到Azure Blob存储。

我需要确保:

  • 没有温度。文件写在Web API实例
  • 在将请求流传递给存储服务之前,请求流未完全读入内存(以防止内存不足异常)。

我看过this article,它描述了如何禁用输入流缓冲,但是由于许多不同用户的许多文件上传同时发生,因此实际上它实际上是在锡上执行的操作。

这就是我目前控制器中的内容:

if (this.Request.Content.IsMimeMultipartContent())
{
    var provider = new MultipartMemoryStreamProvider();
    await this.Request.Content.ReadAsMultipartAsync(provider);
    var fileContent = provider.Contents.SingleOrDefault();

    if (fileContent == null)
    {
        throw new ArgumentException("No filename.");
    }

    var fileName = fileContent.Headers.ContentDisposition.FileName.Replace("\"", string.Empty);

    // I need to make sure this stream is ready to be processed by 
    // the Azure client lib, but not buffered fully, to prevent OoM.
    var stream = await fileContent.ReadAsStreamAsync();
}

我不知道如何可靠地测试它。

编辑:我忘了提到直接上传到Blob存储(绕过我的API)是行不通的,因为我正在做一些大小检查(例如,这个用户可以上传500mb吗?有这个用户吗?用过他的配额?)。

2 个答案:

答案 0 :(得分:8)

this Gist的帮助下解决了这个问题。

以下是我如何使用它,以及一个聪明的“黑客”来获取实际的文件大小,而不是先将文件复制到内存中。哦,它的速度是原来的两倍 (显然)。

// Create an instance of our provider.
// See https://gist.github.com/JamesRandall/11088079#file-blobstoragemultipartstreamprovider-cs for implementation.
var provider = new BlobStorageMultipartStreamProvider ();

// This is where the uploading is happening, by writing to the Azure stream
// as the file stream from the request is being read, leaving almost no memory footprint.
await this.Request.Content.ReadAsMultipartAsync(provider);

// We want to know the exact size of the file, but this info is not available to us before
// we've uploaded everything - which has just happened.
// We get the stream from the content (and that stream is the same instance we wrote to).
var stream = await provider.Contents.First().ReadAsStreamAsync();

// Problem: If you try to use stream.Length, you'll get an exception, because BlobWriteStream
// does not support it.

// But this is where we get fancy.

// Position == size, because the file has just been written to it, leaving the
// position at the end of the file.
var sizeInBytes = stream.Position;

Voilá,您获得了上传文件的大小,无需将文件复制到Web实例的内存中。

至于上传文件长度文件,这并不容易,我不得不采用一些非常令人愉快的方法来获得近似值。

BlobStorageMultipartStreamProvider

var approxSize = parent.Headers.ContentLength.Value - parent.Headers.ToString().Length;

这给了我一个非常接近的文件大小,减去几百个字节(取决于我猜的HTTP头)。这对我来说已经足够了,因为我的配额强制执行可以接受削减的几个字节。

只是为了炫耀,这是内存占用,由任务管理器中疯狂准确和高级性能选项卡报告。

之前 - 使用MemoryStream,在上传

之前将其读入内存

Before

之后 - 直接写入Blob存储

After

答案 1 :(得分:5)

我认为更好的方法是从您的客户端直接转到Azure Blob存储。通过利用Azure存储中的CORS支持,您可以消除Web API服务器上的负载,从而为您的应用程序提供更好的整体扩展。

基本上,您将创建一个共享访问签名(SAS)URL,客户端可以使用该URL将文件直接上载到Azure存储。出于安全原因,建议您限制SAS有效的时间段。有关生成SAS URL的最佳做法指南here

对于您的特定方案,请查看Azure存储团队中的this blog,他们将讨论如何使用CORS和SAS进行此确切方案。还有一个示例应用程序,因此这应该为您提供所需的一切。