web-dev-qa-db-ja.com

UPDATE [2]-ASP.NET Core Web APIアップロードおよびダウンロードファイル-ストリーム例外

この質問は以下に関連しています: 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;
}
_

ご覧のとおり、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:Error:リクエストの実行中に未処理の例外が発生しました。

System.NotSupportedException:ストリームは読み取りをサポートしていません。 System.IO.Stream.BeginReadInternal(Byte []バッファー、Int32オフセット、Int32カウント、AsyncCallbackコールバック、オブジェクト状態、ブールserializeAsynchronously、ブールapm)at System.IO.Stream.BeginEndReadAsync(Byte []バッファー、Int32オフセット、Int32 count)System.IO.Stream.ReadAsync(Byte []バッファ、Int32オフセット、Int32カウント、CancellationToken cancelToken)でMicrosoft.AspNetCore.Http.Extensions.StreamCopyOperation.CopyToAsync(Streamソース、ストリーム宛先、Nullable`1カウント、Int32 bufferSize、CancellationToken cancel)at Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase.WriteFileAsync(HttpContext context、Stream fileStream、RangeItemHeaderValue range、Int64 rangeLength)at Microsoft.AspNetCore.Mvc.Infrastructure.FileStreamResultExecutor.ExecuteAsync(ActionContext context、FileStreamResult result)at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsyncTFilterでのMicrosoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result) Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext [TFilter、TFilterAsync](State&next、Scope&scope、Object&state、Boolean&isCompleted)のMicrosoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context)にあるTFilterAsync。 AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters()at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)at Microsoft.AspNetCore.Mvc.Internal。 Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()(Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()(Microsoft.AspNetCore.Builder)のResourceInvoker.Next(State&next、Scope&scope、Object&state、Boolean&isCompleted) .RouterMiddleware.Invoke(HttpContext httpContext)at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

ストリームは読み取りをサポートしていないとあり、次のコマンドを使用してストリームを作成するように要求しているので問題ないことに注意してください: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(「スレッドの終了またはアプリケーションの要求により、I/O操作が中止されました」)を返しましたが、例外はスローされませんでした。コントローラ。

何か案が?

6
Attilio Gelosa

クライアントで取得するストリームは、APIで返すストリームとは異なります。 mvcフレームワークは、コンテンツを取得し、それをbyte []としてクライアントのネットワークを介して送信できるように、読み取り可能なストリームを必要とします。

Azure BLOBストリームに書き込むには、必要なすべてのデータを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の順序を確認する必要があります。 (メタデータ、ファイル)

3
Kalten

NotSupportedException例外は、メソッドに実装がなく、呼び出してはならないことを示します。例外は処理しないでください。代わりに、何をすべきかは、例外の原因によって異なります。つまり、実装が完全に存在しないか、メンバーの呼び出しがオブジェクトの目的(read-の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);

詳細については、この article を参照してください。

0
Joey Cai