ОБНОВЛЕНИЕ [2] - файл загрузки и выгрузки ASP.NET Core Web API - исключение потока - PullRequest
0 голосов
/ 05 сентября 2018

Этот вопрос относится к: ASP.NET Core Web API выгрузка и загрузка файла

Во-первых, я хотел бы поблагодарить Пауэла Герра, который помог мне разобраться в некоторых нюансах с его постом (http://weblogs.thinktecture.com/pawel/2017/03/aspnet-core-webapi-performance.html). У меня все еще есть проблема, чтобы решить.

Мой сценарий Предположим, у нас есть консольное приложение .NET Core:

private static void Main(string[] args)
{
    Console.WriteLine($"Test starts at {DateTime.Now.ToString("o")}");
    FileStream fileStream = new FileStream(@"C:\Windows10Upgrade\Windows10UpgraderApp.exe", FileMode.Open);

    MyFile vFile = new MyFile()
    {
        Lenght = 0,
        Path = "https://c2calrsbackup.blob.core.windows.net/containername/Windows10UpgraderApp.exe",
        RelativePath = "Windows10UpgraderApp.exe"
    };
    Stream uploadStream = GetUploadStream(vFile).GetAwaiter().GetResult();

    fileStream.CopyTo(uploadStream);

    Console.WriteLine($"Test ends at {DateTime.Now.ToString("o")}");
    Console.Write("Press any key to exit...");
    Console.ReadKey();
}

private static async Task<Stream> GetUploadStream(MyFile myFile)
{
    Stream stream = null;

    try
    {
        using (HttpClient httpClient = new HttpClient())
        {
            httpClient.BaseAddress = new Uri("https://localhost:5000");
            using (MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent())
            {
                multipartFormDataContent.Add(new StringContent(JsonConvert.SerializeObject(myFile), Encoding.UTF8, "application/json"), nameof(MyFile));

                HttpResponseMessage httpResult = await httpClient.PostAsync("api/uploaddownload/upload", multipartFormDataContent).ConfigureAwait(false);

                httpResult.EnsureSuccessStatusCode();
                stream = await httpResult.Content.ReadAsStreamAsync().ConfigureAwait(false);
            }
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
    return stream;
}

Как видите, MyFile - это класс поддержки, который содержит некоторую информацию. С другой стороны, контроллер выглядит следующим образом:

[HttpPost("upload")]
public async Task<IActionResult> GetUploadStream()
{
    const string contentType = "application/octet-stream";
    string boundary = GetBoundary(Request.ContentType);
    MultipartReader reader = new MultipartReader(boundary, Request.Body, 80 * 1024);
    Dictionary<string, string> sectionDictionary = new Dictionary<string, string>();
    FileMultipartSection fileMultipartSection;
    MultipartSection section;

    while ((section = await reader.ReadNextSectionAsync().ConfigureAwait(false)) != null)
    {
        ContentDispositionHeaderValue contentDispositionHeaderValue = section.GetContentDispositionHeader();

        if (contentDispositionHeaderValue.IsFormDisposition())
        {
            FormMultipartSection formMultipartSection = section.AsFormDataSection();
            string value = await formMultipartSection.GetValueAsync().ConfigureAwait(false);

            sectionDictionary.Add(formMultipartSection.Name, value);
        }
        else if (contentDispositionHeaderValue.IsFileDisposition())
        {
            fileMultipartSection = section.AsFileSection();
        }
    }

    CloudStorageAccount.TryParse(connectionString, out CloudStorageAccount cloudStorageAccount);
    CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
    CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference(containerName);

    if (await cloudBlobContainer.CreateIfNotExistsAsync().ConfigureAwait(false))
    {
        BlobContainerPermissions blobContainerPermission = new BlobContainerPermissions()
        {
            PublicAccess = BlobContainerPublicAccessType.Container
        };

        await cloudBlobContainer.SetPermissionsAsync(blobContainerPermission).ConfigureAwait(false);
    }

    MyFile myFile = JsonConvert.DeserializeObject<MyFile>(sectionDictionary.GetValueOrDefault(nameof(MyFile)));
    CloudBlockBlob cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(myFile.RelativePath);
    Stream streamResult = await cloudBlockBlob.OpenWriteAsync().ConfigureAwait(false);

    return new FileStreamResult(streamResult, contentType);
}

Проблема

Когда контроллер возвращается с инструкцией return new FileStreamResult(streamResult, contentType); , в самом контроллере генерируется следующее исключение (не в приложении вызывающей консоли):

Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware: ошибка: во время выполнения запроса произошло необработанное исключение.

System.NotSupportedException: поток не поддерживает чтение. в System.IO.Stream.BeginReadInternal (буфер Byte [], смещение Int32, число Int32, обратный вызов AsyncCallback, состояние объекта, логический serializeAsynchronously, логический apm) в System.IO.Stream.BeginEndReadAsync (буфер Byte [], смещение Int32, количество Int32) в System.IO.Stream.ReadAsync (буфер Byte [], смещение Int32, число Int32, CancellationToken cancellationToken) в Microsoft.AspNetCore.Http.Extensions.StreamCopyOperation.CopyToAsync (источник потока, место назначения потока, счетчик Nullable`1, размер Int32 bufferSize, CancellationToken cancel) в Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase.WriteFileAsync (контекст HttpContext, поток StreamStream, RangeItemHeaderValue range, Int64 rangeLength) в Microsoft.AspNetCore.Mvc.Infrastructure.FileStreamResultExecutor.ExecuteAsync (контекст ActionContext, результат FileStreamResult) в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync (результат IActionResult) в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsyncTFilter, TFilterAsync в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow (контекст ResultExecutedContext) в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext [TFilter, TFilterAsync] (Состояние и следующее, Область и область действия, Объект и состояние, Логическое значение и isCompleted) в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters () в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter () в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow (контекст ResourceExecutedContext) в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next (State & next, Scope & scope, Object & state, Boolean & isCompleted) в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync () в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync () в Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke (HttpContext httpContext) в Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (контекст HttpContext)

Обратите внимание, что в нем указано Stream не поддерживает чтение , и это нормально, потому что я прошу создать поток с помощью следующей команды: cloudBlockBlob.OpenWriteAsync(), но, как вы можете видеть, я не делаю любая операция чтения, и я только возвращаю поток в консольное приложение.

Вопросы

  • Как вы думаете, что это может быть? Есть ли какая-то скрытая операция чтения, о которой я не знаю?
  • Как решить проблему?

Спасибо,

Аттило

UPDATE

Привет всем,

наконец мы написали следующее:

Контроллер * * тысяча пятьдесят-одна public static class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseKestrel(); } [Route("api/[controller]")] [ApiController] public class ValuesController : Controller { [HttpPost("upload")] public async Task<IActionResult> Upload() { try { CloudBlobContainer vCloudBlobContainer = await GetCloudBlobContainer().ConfigureAwait(false); string boundary = GetBoundary(Request.ContentType); MultipartReader reader = new MultipartReader(boundary, Request.Body, 80 * 1024); Dictionary<string, string> sectionDictionary = new Dictionary<string, string>(); MultipartSection section; MyFile myFile; while ((section = await reader.ReadNextSectionAsync().ConfigureAwait(false)) != null) { ContentDispositionHeaderValue contentDispositionHeaderValue = section.GetContentDispositionHeader(); if (contentDispositionHeaderValue.IsFormDisposition()) { FormMultipartSection formMultipartSection = section.AsFormDataSection(); string value = await formMultipartSection.GetValueAsync().ConfigureAwait(false); sectionDictionary.Add(formMultipartSection.Name, value); } else if (contentDispositionHeaderValue.IsFileDisposition()) { myFile = JsonConvert.DeserializeObject<MyFile>(sectionDictionary.GetValueOrDefault(nameof(MyFile))); if (myFile == default(object)) { throw new InvalidOperationException(); } CloudBlockBlob cloudBlockBlob = vCloudBlobContainer.GetBlockBlobReference(myFile.RelativePath); Stream stream = await cloudBlockBlob.OpenWriteAsync().ConfigureAwait(false); FileMultipartSection fileMultipartSection = section.AsFileSection(); await cloudBlockBlob.UploadFromStreamAsync(fileMultipartSection.FileStream).ConfigureAwait(false); } } return Ok(); } catch (Exception e) { throw e; } } private string GetBoundary(string contentType) { if (contentType == null) { throw new ArgumentNullException(nameof(contentType)); } string[] elements = contentType.Split(' '); string element = elements.First(entry => entry.StartsWith("boundary=")); string boundary = element.Substring("boundary=".Length); return HeaderUtilities.RemoveQuotes(boundary).Value; } private async Task<CloudBlobContainer> GetCloudBlobContainer() { const string connectionString = "[Your connection string]"; const string containerName = "container"; try { CloudStorageAccount.TryParse(connectionString, out CloudStorageAccount cloudStorageAccount); CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient(); CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference(containerName); if (await cloudBlobContainer.CreateIfNotExistsAsync().ConfigureAwait(false)) { BlobContainerPermissions blobContainerPermission = new BlobContainerPermissions() { PublicAccess = BlobContainerPublicAccessType.Container }; await cloudBlobContainer.SetPermissionsAsync(blobContainerPermission).ConfigureAwait(false); } return cloudBlobContainer; } catch (Exception) { throw; } } } Клиент

internal static class Program
{
    private const string filePath = @"D:\Test.txt";
    private const string baseAddress = "http://localhost:5000";

    private static async Task Main(string[] args)
    {
        Console.WriteLine($"Test starts at {DateTime.Now.ToString("o")}");
        FileStream fileStream = new FileStream(filePath, FileMode.Open);
        MyFile vFile = new MyFile()
        {
            Lenght = 0,
            RelativePath = "Test.txt"
        };

        await UploadStream(vFile, fileStream).ConfigureAwait(false);

        Console.WriteLine($"Test ends at {DateTime.Now.ToString("o")}");
        Console.Write("Press any key to exit...");
        Console.ReadKey();
    }

    private static async Task UploadStream(MyFile myFile, Stream stream)
    {
        try
        {
            using (HttpClient httpClient = new HttpClient())
            {
                httpClient.BaseAddress = new Uri(baseAddress);
                using (MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent())
                {
                    multipartFormDataContent.Add(new StringContent(JsonConvert.SerializeObject(myFile), Encoding.UTF8, "application/json"), nameof(MyFile));
                    multipartFormDataContent.Add(new StreamContent(stream), "stream", nameof(MyFile));

                    HttpResponseMessage httpResult = await httpClient.PostAsync("api/values/upload", multipartFormDataContent).ConfigureAwait(false);
                    httpResult.EnsureSuccessStatusCode();
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

Мы будем управлять большими файлами, поэтому у нас проблема ...

Тесты

Мы провели следующие тесты:

  • мы попытались загрузить «маленькие» файлы (менее 30000000 байт) и все работало нормально.
  • мы попытались загрузить «большие» файлы (более 30000000 байт), и контроллер вернул исключение Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException («Тело запроса слишком большое.»).
  • мы изменили строку кода .UseKestrel() с помощью .UseKestrel(options => options.Limits.MaxRequestBodySize = null) и попытались загрузить тот же файл. Это изменение кода должно решить проблему, но клиент вернул исключение System.NET.Sockets.SocketException («Операция ввода-вывода была прервана из-за выхода из потока или запроса приложения»), в то время как в контроллер.

Есть идеи?

Ответы [ 2 ]

0 голосов
/ 13 сентября 2018

Поток, который вы получаете в клиенте, отличается от потока, который вы возвращаете в своем API. Каркас mvc нуждается в читаемом потоке, чтобы иметь возможность получать контент и отправлять его в виде байта [] через сеть поверх клиента.

Вам необходимо отправить все необходимые данные в ваш API, чтобы иметь возможность записи в поток голубых двоичных объектов.

Клиентская сторона

private static async Task Main(string[] args) // async main available in c# 7.1 
{
    Console.WriteLine($"Test starts at {DateTime.Now.ToString("o")}");
    FileStream fileStream = new FileStream(@"C:\Windows10Upgrade\Windows10UpgraderApp.exe", FileMode.Open);
    MyFile vFile = new MyFile()
    {
        Lenght = 0,
        Path = "https://c2calrsbackup.blob.core.windows.net/containername/Windows10UpgraderApp.exe",
        RelativePath = "Windows10UpgraderApp.exe"
    };

    await UploadStream(vFile, fileStream);

    Console.WriteLine($"Test ends at {DateTime.Now.ToString("o")}");
    Console.Write("Press any key to exit...");
    Console.ReadKey();
}

private static async Task UploadStream(MyFile myFile, Stream stream)
{
    try
    {
        using (HttpClient httpClient = new HttpClient()) // instance should be shared
        {
            httpClient.BaseAddress = new Uri("https://localhost:5000");
            using (MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent())
            {
                multipartFormDataContent.Add(new StringContent(JsonConvert.SerializeObject(myFile), Encoding.UTF8, "application/json"), nameof(MyFile));
                // Here we add the file to the multipart content.
                // The tird parameter is required to match the `IsFileDisposition()` but could be anything
                multipartFormDataContent.Add(new StreamContent(stream), "stream", "myfile");

                HttpResponseMessage httpResult = await httpClient.PostAsync("api/uploaddownload/upload", multipartFormDataContent).ConfigureAwait(false);
                httpResult.EnsureSuccessStatusCode();
                // We don't need any result stream anymore
            }
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
}

Сторона API:

[HttpPost("upload")]
public async Task<IActionResult> GetUploadStream()
{
    const string contentType = "application/octet-stream";
    string boundary = GetBoundary(Request.ContentType);
    MultipartReader reader = new MultipartReader(boundary, Request.Body, 80 * 1024);
    Dictionary<string, string> sectionDictionary = new Dictionary<string, string>();
    var memoryStream = new MemoryStream();
    MultipartSection section;

    while ((section = await reader.ReadNextSectionAsync()) != null)
    {
        ContentDispositionHeaderValue contentDispositionHeaderValue = section.GetContentDispositionHeader();

        if (contentDispositionHeaderValue.IsFormDisposition())
        {
            FormMultipartSection formMultipartSection = section.AsFormDataSection();
            string value = await formMultipartSection.GetValueAsync();

            sectionDictionary.Add(formMultipartSection.Name, value);
        }
        else if (contentDispositionHeaderValue.IsFileDisposition())
        {
            // we save the file in a temporary stream
            var fileMultipartSection = section.AsFileSection();
            await fileMultipartSection.FileStream.CopyToAsync(memoryStream);
            memoryStream.Position = 0;
        }
    }

    CloudStorageAccount.TryParse(connectionString, out CloudStorageAccount cloudStorageAccount);
    CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
    CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference(containerName);

    if (await cloudBlobContainer.CreateIfNotExistsAsync())
    {
        BlobContainerPermissions blobContainerPermission = new BlobContainerPermissions()
        {
            PublicAccess = BlobContainerPublicAccessType.Container
        };

        await cloudBlobContainer.SetPermissionsAsync(blobContainerPermission);
    }

    MyFile myFile = JsonConvert.DeserializeObject<MyFile>(sectionDictionary.GetValueOrDefault(nameof(MyFile)));
    CloudBlockBlob cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(myFile.RelativePath);
    using(Stream blobStream = await cloudBlockBlob.OpenWriteAsync())
    {
        // Finally copy the file into the blob writable stream
        await memoryStream.CopyToAsync(blobStream);
    }

    // you can replace OpenWriteAsync by 
    // await cloudBlockBlob.UploadFromStreamAsync(memoryStream);

    return Ok(); // return httpcode 200
}

См. https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming для документации

Вы можете избежать потока временной памяти, если переместите код azureblog в блок else if. Но вам нужно обеспечить порядок FormData. (Метаданные затем файл)

0 голосов
/ 06 сентября 2018

Исключение NotSupportedException указывает на то, что метод не имеет реализации и его не следует вызывать. Вы не должны обрабатывать исключение. Вместо этого то, что вы должны сделать, зависит от причины исключения: полностью ли отсутствует реализация или вызов члена несовместим с назначением объекта (например, вызов метода FileStream.Read только для чтения *). 1004 * объект.

Вы можете обратиться к следующему коду:

CloudBlockBlob cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(myFile.RelativePath);
MemoryStream mem = new MemoryStream();
await cloudBlockBlob.DownloadToStreamAsync(mem);
mem.Position = 0;
return new FileStreamResult(mem, contentType);

Для получения более подробной информации вы можете обратиться к этой статье .

...