wake-up-neo.com

UPDATE [2] - ASP.NET Core Web API-Datei hochladen und herunterladen - Stream-Ausnahme

Diese Frage bezieht sich auf: ASP.NET Core Web API Datei hochladen und herunterladen

Zunächst möchte ich mich bei Powel Gerr bedanken, der mir geholfen hat, einige Nuancen mit seinem Beitrag zu verstehen ( http://weblogs.thinktecture.com/pawel/2017/03/aspnet-core-webapi-performance.html ) . Ich habe noch ein Problem zu lösen.

Mein Szenario Angenommen, wir haben eine .NET Core-Konsolenanwendung:

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;
}

Wie Sie sehen, ist MyFile eine Support-Klasse, die einige Informationen enthält. Auf der anderen Seite sieht der Controller folgendermaßen aus:

[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);
}

Das Problem

Wenn der Controller mit der Anweisung return new FileStreamResult(streamResult, contentType);zurückkehrt, wird die folgende Ausnahme im Controller selbst generiert(nicht in der aufrufenden Konsolen-App):

Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware: Fehler: Beim Ausführen der Anforderung ist eine nicht behandelte Ausnahme aufgetreten.

System.NotSupportedException: Stream unterstützt das Lesen nicht. bei System.IO.Stream.BeginReadInternal (Byte [] -Puffer, Int32-Offset, Int32-Anzahl, AsyncCallback-Rückruf, Objektstatus, Boolean serializeAsynchronously, Boolean apm) bei System.IO.Stream.BeginEndReadAsync (Byte [] -Puffer, Int32-Offset, Int32 count) bei System.IO.Stream.ReadAsync (Byte [] -Puffer, Int32-Offset, Int32-Anzahl, CancellationToken CancellationToken) bei Microsoft.AspNetCore.Http.Extensions.StreamCopyOperation.CopyToAsync (Stream-Quelle, Stream-Ziel, Nullable`1-Anzahl, Int32 bufferSize, CancellationToken cancel) bei Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase.WriteFileAsync (HttpContext-Kontext, Stream fileStream, RangeItemHeaderValue-Bereich, Int64 rangeLength) bei Microsoft.AspNetCore.Mvc.Infrastructure.FileExecuteStreamStreamCore (Mvc. ( Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync (IActionResult-Ergebnis) bei Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsyncTFilter, TFilterAsync bei Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow (ResultExecutedContext-Kontext) bei Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext [TFilter, TFilterAsync] (Status & Next, Umfang & Scope & Scope, Objekt ist & Scope & Scope & AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters () bei Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter () bei Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow (ResourceExecuted) ResourceInvoker.Next (Status & next, Bereich & scope, Objekt & state, Boolean & isCompleted) bei Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync () bei Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync () () .RouterMiddleware.Invoke (HttpContext httpContext) bei Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (HttpContext context)

Bitte beachten Sie, dass die MeldungStream das Lesen vonnicht unterstützt. Dies ist in Ordnung, da ich Sie auffordern möchte, den Stream mit dem folgenden Befehl zu erstellen: cloudBlockBlob.OpenWriteAsync(), aber wie Sie sehen können, lese ich nicht Betrieb und ich kehre nur den Stream an die Konsolen-App zurück.

Fragen

  • Was denkst du kann es sein? Gibt es eine versteckte Leseoperation, die mir nicht bekannt ist?
  • Wie kann das Problem gelöst werden?

Vielen Dank,

Attilio

AKTUALISIEREN

Hallo zusammen,

zum Schluss haben wir folgendes geschrieben:

Regler

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;
        }
    }
}

Klient

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);
        }
    }
}

Wir werden große Dateien verwalten, also haben wir ein Problem ...

Tests

Wir haben folgende Tests durchgeführt:

  • wir haben versucht, "kleine" Dateien (weniger als 30000000 Bytes) hochzuladen und alle haben einwandfrei funktioniert.
  • wir haben versucht, "große" Dateien (mehr als 30000000 Byte) hochzuladen, und der Controller hat eine Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException ("Request body too large") zurückgegeben.
  • wir haben die Codezeile .UseKestrel() durch .UseKestrel(options => options.Limits.MaxRequestBodySize = null) geändert und versucht, dieselbe Datei hochzuladen. Diese Codeänderung sollte das Problem lösen müssen, aber der Client hat eine System.NET.Sockets.SocketException ("Die E/A-Operation wurde wegen eines Thread-Exits oder einer Anwendungsanforderung abgebrochen") zurückgegeben, während in der keine Ausnahme ausgelöst wurde Regler.

Irgendeine Idee?

4
Attilio Gelosa

Der Stream, den Sie im Client erhalten, ist nicht der gleiche, den Sie in Ihrer API zurückgeben. Das mvc-Framework benötigt einen lesbaren Stream, um den Inhalt abzurufen und als Byte [] über das Netzwerk an den Client zu senden. 

Sie müssen alle erforderlichen Daten an Ihre API senden, um in den Azure-Blob-Stream schreiben zu können.

Client-Seite

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-Seite:

[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
}

Unter https://docs.Microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming finden Sie Dokumentation

Sie können den temporären Speicherstrom vermeiden, wenn Sie den Azureblog-Code innerhalb des else if-Blocks verschieben. Sie müssen jedoch die Reihenfolge von FormData sicherstellen. (Metadaten dann Datei)

2
Kalten

Die NotSupportedException-Ausnahme zeigt an, dass eine Methode keine Implementierung hat und Sie sie nicht aufrufen sollten. Sie sollten die Ausnahme nicht behandeln. Was Sie dagegen tun sollten, hängt von der Ursache der Ausnahme ab: Ob eine Implementierung vollständig fehlt oder der Memberaufruf mit dem Zweck eines Objekts nicht übereinstimmt (z. B. ein Aufruf der FileStream.Read-Methode für ein schreibgeschütztes FileStream-Objekt.

Sie könnten sich auf den folgenden Code beziehen:

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

Für weitere Details können Sie sich auf diesen Artikel beziehen.

0
Joey Cai