Можно ли переопределить MultipartFormDataStreamProvider, чтобы не сохранять загрузки в файловую систему?

У меня есть приложение ASP.Net Web API, которое позволяет клиентам (html-страницам и приложениям для iPhone) загружать изображения. Я использую задачу async upload, описанную в этой статье .

Все отлично работает, когда я хочу сохранить в файловой системе, потому что это то, что этот код делает автоматически, за кулисами, похоже. Но я не хочу сохранять загруженные файлы в файловую систему. Вместо этого я хочу загрузить загруженный поток и передать его в ведро Amazon S3 с помощью AWS SDK для .Net.

У меня есть код, настроенный для отправки потока до AWS. Проблема, которую я не могу понять, заключается в том, как получить загруженный поток контента из метода Web API вместо автоматического сохранения на диск.

Я надеялся, что будет виртуальный метод, который я мог бы переопределить в MultipartFormDataStreamProvider, который позволил бы мне сделать что-то еще с загруженным контентом, кроме сохранения на диск, но, похоже, не существует.

Любые предложения?

Ответ 1

Вы можете переопределить метод MultipartFormDataStreamProvider GetStream, чтобы вернуть поток, который не является файловым потоком, а потоком AWS, но есть некоторые проблемы (я не буду здесь останавливаться). Вместо этого вы можете создать поставщика, исходя из абстрактного базового класса MultipartStreamProvider. Следующий образец в значительной степени основан на фактическом исходном коде MultipartFormDataStreamProvider и MultipartFileStreamProvider. Вы можете проверить здесь и здесь для более подробной информации. Пример ниже:

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; }
}

Ответ 2

Так как @KiranChalla отправил свой ответ, новый абстрактный класс MultipartFormDataRemoteStreamProvider был представлен в Исправить 1760: Сделать MultipartFormDataStreamProvider проще для работы с файлами без файлов., чтобы сделать это проще.

Резюме класса отлично справляется с объяснением, как его использовать:

A MultipartStreamProvider реализация, подходящая для использования с загрузкой файлов HTML для записи содержимого файла в удаленное хранилище Stream. Поставщик потока ищет поле заголовка Content-Disposition и определяет выходной удаленный Stream на основе наличия параметра имени файла. Если в поле заголовка Content-Disposition присутствует параметр имени файла, то часть тела записывается в удаленный Stream, предоставляемый GetRemoteStream. В противном случае он записывается в MemoryStream.