【问题标题】:Is it possible to override MultipartFormDataStreamProvider so that is doesn't save uploads to the file system?是否可以覆盖 MultipartFormDataStreamProvider 以便不将上传保存到文件系统?
【发布时间】:2013-04-05 20:00:08
【问题描述】:

我有一个 ASP.Net Web API 应用程序,它允许客户端(html 页面和 iPhone 应用程序)将图像上传到。我正在使用article 中所述的异步上传任务。

当我想保存到文件系统时,一切都很好,因为这就是这段代码自动执行的操作,似乎在幕后。但是,我不想将上传的文件保存到文件系统中。相反,我想获取上传的流并使用适用于 .Net 的 AWS 开发工具包将其传递到 Amazon S3 存储桶。

我设置了将流发送到 AWS 的代码。我想不通的问题是如何从 Web API 方法获取上传的内容流,而不是让它自动保存到磁盘。

我希望有一个我可以在 MultipartFormDataStreamProvider 中覆盖的虚拟方法,这将允许我对上传的内容执行其他操作,而不是保存到磁盘,但似乎没有.

有什么建议吗?

【问题讨论】:

  • 嗨,我知道这是一个老问题,但它对我帮助很大。您是否也可以发布用于创建 AWS 流的完整代码?我不知道如何在下面接受的答案中返回 AWS 流,或者之后如何处理它。

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


【解决方案1】:

您可以覆盖 MultipartFormDataStreamProvider 的 GetStream 方法以返回一个不是文件流而是您的 AWS 流的流,但是这样做会出现一些问题(我不会在这里详细说明)。相反,您可以创建一个派生自抽象基类 MultipartStreamProvider 的提供程序。以下示例很大程度上基于 MultipartFormDataStreamProvider 和 MultipartFileStreamProvider 的实际源代码。您可以查看herehere了解更多详情。示例如下:

public class CustomMultipartFormDataStreamProvider : MultipartStreamProvider
{
    private NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase);

    private Collection<bool> _isFormData = new Collection<bool>();

    private Collection<MyMultipartFileData> _fileData = new Collection<MyMultipartFileData>();

    public NameValueCollection FormData
    {
        get { return _formData; }
    }

    public Collection<MultipartFileData> FileData
    {
        get { return _fileData; }
    }

    public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
    {
        // For form data, Content-Disposition header is a requirement
        ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
        if (contentDisposition != null)
        {
            // If we have a file name then write contents out to AWS stream. Otherwise just write to MemoryStream
            if (!String.IsNullOrEmpty(contentDisposition.FileName))
            {
                // We won't post process files as form data
                _isFormData.Add(false);

                 MyMultipartFileData fileData = new MyMultipartFileData(headers, your-aws-filelocation-url-maybe);
                 _fileData.Add(fileData);

                return myAWSStream;//**return you AWS stream here**
            }

            // We will post process this as form data
            _isFormData.Add(true);

            // If no filename parameter was found in the Content-Disposition header then return a memory stream.
            return new MemoryStream();
        }

        throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part..");
    }

    /// <summary>
    /// Read the non-file contents as form data.
    /// </summary>
    /// <returns></returns>
    public override async Task ExecutePostProcessingAsync()
    {
        // Find instances of HttpContent for which we created a memory stream and read them asynchronously
        // to get the string content and then add that as form data
        for (int index = 0; index < Contents.Count; index++)
        {
            if (_isFormData[index])
            {
                HttpContent formContent = Contents[index];
                // Extract name from Content-Disposition header. We know from earlier that the header is present.
                ContentDispositionHeaderValue contentDisposition = formContent.Headers.ContentDisposition;
                string formFieldName = UnquoteToken(contentDisposition.Name) ?? String.Empty;

                // Read the contents as string data and add to form data
                string formFieldValue = await formContent.ReadAsStringAsync();
                FormData.Add(formFieldName, formFieldValue);
            }
        }
    }

    /// <summary>
    /// Remove bounding quotes on a token if present
    /// </summary>
    /// <param name="token">Token to unquote.</param>
    /// <returns>Unquoted token.</returns>
    private static string UnquoteToken(string token)
    {
        if (String.IsNullOrWhiteSpace(token))
        {
            return token;
        }

        if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)
        {
            return token.Substring(1, token.Length - 2);
        }

        return token;
    }
}

public class MyMultipartFileData
{
    public MultipartFileData(HttpContentHeaders headers, string awsFileUrl)
    {
        Headers = headers;
        AwsFileUrl = awsFileUrl;
    }

    public HttpContentHeaders Headers { get; private set; }

    public string AwsFileUrl { get; private set; }
}

【讨论】:

  • 太棒了!你帮助我理解了很多事情。首先,我现在看到文件“如何”实际保存到磁盘(这以前我不知道)。它发生在 MultipartFileStreamProvider 的 GetStream 中(它是 MultipartFormDataStreamProvider 的基础,我的自定义提供程序从中派生)。现在,我可以做的是直接从 MultipartStreamProvider 派生并覆盖 GetStream 以便它不会保存到磁盘而是提交到 S3,正如您所建议的那样。非常感谢!
  • 我真的很想看到将流写入 myAWSStream 的位置得到澄清。我想写一些东西到 Azure Blob 存储而不是 AWS。但我不知道如何(在 GetStream 方法中)访问流。在这种方法中,我想做: BlobService.StoreImageToBlobFromStream(stream)
  • 解决方案似乎是“让我们通过从该类复制大量代码并从该类复制其余代码来创建我们自己的类”几乎让我哭了。
  • 是的,无法解释 MS 会发布一个 API,该 API 需要访问文件系统来处理这种基本场景。他们可能不会想到人们可能不想访问文件写入并执行磁盘 IO 来处理表单 POST。 Grr.
  • @Kiran 您能否详细说明完整的解决方案,这对我来说是不可用的,因为我不明白您从哪里获得 myAWSStream var。
【解决方案2】:

自从@KiranChalla 发布了他们的答案后,Fix 1760: Make MultipartFormDataStreamProvider easier to work with non-FileStreams. 中引入了一个新的抽象类MultipartFormDataRemoteStreamProvider 以简化此操作。

类的摘要很好地解释了如何使用它:

一个MultipartStreamProvider 实现适用于HTML 文件上传,用于将文件内容写入远程存储Stream。流提供程序查看 Content-Disposition 标头字段并根据文件名参数的存在确定输出远程Stream。如果 Content-Disposition 标头字段中存在文件名参数,则正文部分将写入由GetRemoteStream 提供的远程Stream。否则会写入MemoryStream

【讨论】:

    猜你喜欢
    • 2015-02-16
    • 1970-01-01
    • 2018-08-04
    • 2016-06-14
    • 1970-01-01
    • 1970-01-01
    • 2017-12-14
    • 2013-03-30
    • 2017-08-29
    相关资源
    最近更新 更多