it-swarm.com.ru

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

Этот вопрос связан с: 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, Размер3232 bufferSize, Отмена CancellationToken) в Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase.WriteFileAsync (контекст HttpContext, поток fileStream, 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(), но, как вы можете видеть, я не выполняю никаких операций чтения, и я возвращаю только поток в консольное приложение.

Вопросы

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

Спасибо,

Аттило

Обновление

Всем привет,

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

Контроллер

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 («Операция ввода-вывода была прервана из-за выхода из потока или запроса приложения»), в то время как исключение не было выдано контроллер.

Любая идея?

4
Attilio Gelosa

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

Чтобы записать в поток BLOB-объектов Azure, вам необходимо отправить все необходимые данные в 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. (Метаданные затем файл)

2
Kalten

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

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

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

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

0
Joey Cai