Этот вопрос относится к: 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, размер Int32 bufferSize, CancellationToken cancel)
в Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase.WriteFileAsync (контекст HttpContext, поток StreamStream, 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()
, но, как вы можете видеть, я не делаю любая операция чтения, и я только возвращаю поток в консольное приложение.
Вопросы
- Как вы думаете, что это может быть? Есть ли какая-то скрытая операция чтения, о которой я не знаю?
- Как решить проблему?
Спасибо,
Аттило
UPDATE
Привет всем,
наконец мы написали следующее:
Контроллер * * тысяча пятьдесят-одна
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 («Операция ввода-вывода была прервана из-за выхода из потока или запроса приложения»), в то время как в контроллер.
Есть идеи?