web-dev-qa-db-fra.com

UPDATE [2] - Fichier de téléchargement et de téléchargement d'API Web ASP.NET Core - Exception de flux

Cette question est liée à: Fichier de téléchargement et de téléchargement d'API Web ASP.NET Core

Tout d’abord, je voudrais remercier Powel Gerr qui m’a aidé à comprendre certaines nuances dans son message ( http://weblogs.thinktecture.com/pawel/2017/03/aspnet-core-webapi-performance.html ) . J'ai encore un problème à résoudre.

Mon scénario Supposons que nous ayons une application console .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;
}

Comme vous pouvez le constater, MyFile est une classe de support contenant des informations. Par contre, le contrôleur est le suivant: 

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

Le problème

Lorsque le contrôleur revient avec l'instruction return new FileStreamResult(streamResult, contentType);l'exception suivante est générée dans le contrôleur lui-même} _ (pas dans l'application de la console appelante):

Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware: Erreur: Une exception non gérée s'est produite lors de l'exécution de la demande.

System.NotSupportedException: le flux ne prend pas en charge la lecture . sur System.IO.Stream.BeginReadInternal (tampon Byte [], décalage Int32, nombre Int32, rappel AsyncCallback, état de l'objet, Boolean serializeAsynchronously, Boolean apm) sur System.IO.Stream.BeginEndReadAsync (tampon Byte [], décalage Int32, nombre Int32) sur System.IO.Stream.ReadAsync (tampon Byte [], décalage Int32, nombre Int32, CancellationToken cancelToken) sur Microsoft.AspNetCore.Http.Extensions.StreamCopyOperation.CopyToAsync (source de flux, destination de flux, nombre Nullable`1, Int32 bufferSize, cancelToken cancel) sur Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase.WriteFileAsync (contexte HttpContext, Stream fileStream, plage de RangeItemHeaderValue, plage de longueur Int64) at Microsoft.AspNetCore.Mvc.Infrastructure.FileStreamResultExecutor.ExecuteAsync (contexte ActionContext, résultat FileStreamResult) à Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync (résultat IActionResult) sur Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsyncTFilter, TFilterAsync at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow (contexte ResultExecutedContext) at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext [TFilter, TFilterAsync] (État & suivant, Portée et étendue, Objet et état, Booléen & isCompleted) à Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters () à Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter () at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow (contexte ResourceExecutedContext) sur Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next (État et suivants, Portée et étendue, Objet et état, Booléen et est achevé) à Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync () à Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync () à Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke (HttpContext httpContext) chez Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (contexte HttpContext)

Notez que le flux ne prend pas en charge la lecture et c’est correct, car je demande à créer le flux à l’aide de la commande suivante: cloudBlockBlob.OpenWriteAsync(), mais comme vous pouvez le constater, je ne fais aucune lecture Je ne renvoie que le flux à l'application de la console.

Des questions

  • Que penses-tu que cela puisse être? Y a-t-il une opération de lecture cachée dont je ne suis pas au courant?
  • Comment résoudre le problème?

Je vous remercie,

Attilio

METTRE À JOUR

Salut à tous,

enfin nous avons écrit ce qui suit:

Manette

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

Client

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

Nous allons gérer les gros fichiers, donc nous avons un problème ...

Des tests

Nous avons effectué les tests suivants:

  • nous avons essayé de télécharger de "petits" fichiers (moins de 30000000 octets) et tout a bien fonctionné.
  • nous avons essayé de télécharger de "gros" fichiers (plus de 30000000 octets) et le contrôleur a renvoyé une exception Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException ("Corps de la requête trop volumineux".).
  • nous avons changé la ligne de code .UseKestrel() avec .UseKestrel(options => options.Limits.MaxRequestBodySize = null) et avons essayé de télécharger le même fichier. Ce changement de code devrait résoudre le problème, mais le client a renvoyé une System.NET.Sockets.SocketException ("L'opération d'E/S a été abandonnée en raison d'une sortie de thread ou d'une demande d'application") alors qu'aucune exception n'a été levée dans le fichier. manette.

Une idée?

4
Attilio Gelosa

Le flux que vous recevez dans le client n'est pas le même que celui que vous retournez dans votre API. La structure mvc a besoin d’un flux lisible pour pouvoir obtenir le contenu et l’envoyer sous forme d’octet [] à travers le réseau par le haut du client. 

Vous devez envoyer toutes les données requises à votre API pour pouvoir écrire dans le flux de blob Azure.

Côté client

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

Côté 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
}

Voir https://docs.Microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming pour documentation.

Vous pouvez éviter le flux de mémoire temporaire si vous déplacez le code azureblog dans le bloc else if. Mais vous devez vous assurer de l'ordre de FormData. (Métadonnées puis fichier)

2
Kalten

L'exception NotSupportedException indique qu'une méthode n'a pas d'implémentation et qu'elle ne doit pas être appelée. Vous ne devriez pas gérer l'exception. Au lieu de cela, ce que vous devez faire dépend de la cause de l'exception: si une implémentation est complètement absente ou si l'invocation du membre est incompatible avec l'objectif d'un objet (tel qu'un appel à la méthode FileStream.Read sur un objet FileStream en lecture seule.

Vous pouvez vous référer au code suivant:

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

Pour plus de détails, vous pouvez vous référer à cet article article .

0
Joey Cai