更新[2]-ASP.NET Core Web API上传和下载文件-流异常

Att*_*osa 6 c# azure azure-storage asp.net-core

此问题与以下内容有关:ASP.NET Core Web API上传和下载文件

首先,我要感谢Powel Gerr,他帮助我理解了他的帖子(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;
}
Run Code Online (Sandbox Code Playgroud)

如您所见,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);
}
Run Code Online (Sandbox Code Playgroud)

问题

当控制器返回指令return new FileStreamResult(streamResult, contentType); 时,控制器本身(而不是调用控制台应用程序)会生成以下异常

Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware:Error:执行请求时发生未处理的异常。

System.NotSupportedException:流不支持读取。在System.IO.Stream.BeginEndReadAsync(Byte []缓冲区,Int32偏移量,Int32,位于System.IO.Stream.BeginReadInternal(Byte []缓冲区,Int32偏移量,Int32计数,AsyncCallback回调,对象状态,布尔序列化,布尔apm)在Microsoft.AspNetCore.Http.Extensions.StreamCopyOperation.CopyToAsync(System.IO.Stream.ReadAsync(Byte [] buffer,Int32 offset,Int32 count,CancellationToken cancelToken)处)(Stream source,Stream destination,Nullable`1 count,Int32) Microsoft.AspNetCore.Mvc.Infrastructure.FileStreamResultExecutor上的Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase.WriteFileAsync(HttpContext context,Stream fileStream,RangeItemHeaderValue range,Int64 rangeLength)中的bufferSize,CancellationToken cancel)。

请注意,它说Stream不支持读取,这是可以的,因为我要求使用以下命令创建流:cloudBlockBlob.OpenWriteAsync(),但是如您所见,我没有进行任何读取操作,而只是将流返回到控制台应用程序。

问题

  • 您认为这可能是什么?有我不知道的隐藏阅读操作吗?
  • 如何解决问题?

谢谢,

Attilio

更新

大家好,

最后我们写了以下内容:

控制者

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

客户

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我们将管理大文件,所以有一个问题...

测验

我们进行了以下测试:

  • 我们尝试上传“小”文件(小于30000000字节),并且一切正常。
  • 我们尝试上传“大”文件(超过30000000个字节),并且控制器返回了Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException(“请求正文太大”。)。
  • 我们改变的代码行.UseKestrel().UseKestrel(options => options.Limits.MaxRequestBodySize = null)和尝试上传同一文件。更改此代码应该可以解决问题,但是客户端返回了System.NET.Sockets.SocketException(“由于线程退出或应用程序请求,I / O操作已中止”),而在控制器。

任何的想法?

Joe*_*Cai 0

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);
Run Code Online (Sandbox Code Playgroud)

欲了解更多详情,您可以参考这篇文章