Невозможно передать потоковую загрузку файла через метод контроллера

#c# #asp.net-core #file-upload

#c# #asp.net-core #загрузка файла

Вопрос:

Я пытаюсь эффективно прокси-сервер для загрузки файлов через ASP.NET Контроллер Core 5 MVC для другого API:

 [DisableFormValueModelBinding]
[HttpPost]
public async Task<IActionResult> Upload()
{
    var reader = new MultipartReader(Request.GetMultipartBoundary(), Request.Body);

    MultipartSection section;

    while ((section = await reader.ReadNextSectionAsync().ConfigureAwait(false)) != null)
    {
        if (section.ContentType == "application/json")
        {
            await SendFile(section.Body);
        }
    }

    return View("Upload");
}

private async Task SendFile(Stream stream)
{
    var request = new HttpRequestMessage(HttpMethod.Post, "http://blah/upload");

    request.Content = new StreamContent(stream);

    var response = await httpClient.SendAsync(request);
}
 

Однако принимающий API всегда получает пустой поток.

Я могу подтвердить SendFile , что метод работает, поскольку следующий тест работает из метода контроллера:

 using (var fs = new FileStream("test.json", FileMode.Open))
{
    await SendFile(fs);
}
 

И я могу увидеть загруженный файл, если попытаюсь прочитать его в контроллере:

 var buf = new char[256];

using (var sr = new StreamReader(section.Body))
{
    var x = await sr.ReadAsync(buf, 0, buf.Length);

    while (x > 0)
    {
        log.Debug(new string(buf));
        
        x = await sr.ReadAsync(buf, 0, buf.Length);
    }
}
 

Таким образом, оба конца, похоже, работают, просто не вместе.

Я установил EnableBuffering :

 app.Use(next => context =>
{
    context.Request.EnableBuffering();

    return next(context);
});
 

И я отключаю привязку загруженных файлов к модели, используя DisableFormValueModelBindingAttribute пример из Upload files в ASP.NET Ядро

Я также пытался перемотать поток вручную Seek , но это не имеет значения.

Это работает, если я копирую его через MemoryStream :

 using (var ms = new MemoryStream())
{
    await section.Body.CopyToAsync(ms);
    await ms.FlushAsync();
    ms.Seek(0, SeekOrigin.Begin);

    await SendFile(ms);
}
 

Однако это буферизует файл в памяти, что не подходит для больших файлов.

Это также работает, если я сначала прочитаю загруженный файл, перемотаю назад, а затем попробую:

 var buf = new char[256];

using (var sr = new StreamReader(section.Body))
{
    var x = await sr.ReadAsync(buf, 0, buf.Length);

    while (x > 0)
    {
        log.Debug(new string(buf));
        
        x = await sr.ReadAsync(buf, 0, buf.Length);
    }
}

section.Body.Seek(0, SeekOrigin.Begin);

// this works now:

await SendFile(section.Body);
 

Опять же, это не подходит для больших файлов.

Кажется, что поток находится не в том состоянии, в котором он должен использоваться моим SendFile методом, но я не понимаю, почему.

Обновить

Основываясь на комментариях Джереми Лейкмана, я более внимательно посмотрел на то, что происходит с длиной потока.

Я обнаружил, что удаление EnableBuffering заставляет его работать должным образом, поэтому проблема как бы решена этим.

Однако я наткнулся на этот комментарий aspnetcore Github, в котором участник заявляет, что:

Мы не поддерживаем передачу тела запроса в виде потока в HttpClient.

Этот и другие комментарии в этом выпуске поддерживают комментарии Джереми о CanSeek и длине потока, и неясно (для меня), должно ли это действительно работать и является ли это просто совпадением, что это происходит сейчас (т. Е. Получу ли я еще одну ошибку позже).

В этом конкретном сценарии с MIME multipart, где мы не знаем длину потока без буферизации / подсчета всего файла, есть ли альтернатива StreamContent или другой способ обработки загрузки файла?

Страница документов Microsoft загружает файлы в ASP.NET Core советует использовать только альтернативный подход. В нем говорится о потоковых загрузках, однако он останавливается на правильном использовании потока и просто буферизует файл в a MemoryStream (полностью игнорируя цель потоковой передачи)

Комментарии:

1. Вы получаете исходный поток в виде фрагментированной передачи? В этом случае длина исходного потока будет недоступна, что может вызвать проблемы StreamContent .

2. @JeremyLakeman нет, запрос не обрабатывается. Длина содержимого правильно отправляется и фиксируется в заголовках запроса. Длина потока равна 0 до получения доступа и является ожидаемым значением после обработки потока в обоих случаях (т. Е. Успешное тестовое чтение и неудачная последующая публикация).

3. Я не использовал MultipartReader , это многокомпонентный mime? с граничными строками? Да, невозможно section.Body.Length установить известное значение до чтения потока. Поэтому StreamContent , скорее всего, отображается 0, даже если данные доступны. If section.Body.CanSeek then StreamContent , вероятно, сделает что-то не так.